diff --git a/.DS_Store b/.DS_Store index 6fd4c5f3..5ab5cbef 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml deleted file mode 100644 index f96c027a..00000000 --- a/.github/workflows/publish-to-pypi.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Publish to PyPI - -on: - workflow_dispatch: - push: - branches: - - draft-v4 - paths: - - "pyproject.toml" - -jobs: - build-and-publish: - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: Install build dependencies - run: | - python -m pip install --upgrade pip - python -m pip install build twine - - - name: Get current version - id: current_version - run: | - CURRENT_VERSION=$(grep -oP 'version = "\K[^"]+' pyproject.toml) - echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT - echo "Current version: $CURRENT_VERSION" - - - name: Build package - run: python -m build - - - name: Create GitHub Release - id: create_release - uses: softprops/action-gh-release@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - files: dist/* - tag_name: v${{ steps.current_version.outputs.version }} - draft: false - prerelease: false - generate_release_notes: true - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_TOKEN }} - skip-existing: true - verbose: true \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 463ecca9..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -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' }} - 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 }} diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml deleted file mode 100644 index 4c1a0259..00000000 --- a/.github/workflows/ruff.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Python Linting - -on: [push, pull_request] - -jobs: - ruff: - name: Run Ruff - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.x - - - name: Install Ruff - run: pip install ruff - - - name: Run Ruff - run: ruff check . diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 33ee743b..00000000 --- a/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -__pycache__/ -.idea/ -.vscode/ -.history/ -*.code-workspace -.tmp -.cache -config.ini -snapshots/** -startup-scripts/** -.openart_key -.youml -matrix_auth -channels.list -comfyworkflows_sharekey -github-stats-cache.json -pip_overrides.json -*.json -check2.sh -/venv/ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index f288702d..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README.md b/README.md deleted file mode 100644 index ea4919a7..00000000 --- a/README.md +++ /dev/null @@ -1,409 +0,0 @@ -# ComfyUI Manager - -**ComfyUI-Manager** is an extension designed to enhance the usability of [ComfyUI](https://github.com/comfyanonymous/ComfyUI). It offers management functions to **install, remove, disable, and enable** various custom nodes of ComfyUI. Furthermore, this extension provides a hub feature and convenience functions to access a wide range of information within ComfyUI. - -![menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/refs/heads/Main/ComfyUI-Manager/images/dialog.jpg) - -## NOTICE -* V3.38: **Security patch** - Manager data migrated to protected path. See [Migration Guide](docs/en/v3.38-userdata-security-migration.md). -* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`. -* V3.10: `double-click feature` is removed - * This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper -* V3.3.2: Overhauled. Officially supports [https://registry.comfy.org/](https://registry.comfy.org/). -* You can see whole nodes info on [ComfyUI Nodes Info](https://ltdrdata.github.io/) page. - -## Installation - -### Installation[method1] (General installation method: ComfyUI-Manager only) - -To install ComfyUI-Manager in addition to an existing installation of ComfyUI, you can follow the following steps: - -1. Go to `ComfyUI/custom_nodes` dir in terminal (cmd) -2. `git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager` -3. Restart ComfyUI - - -### Installation[method2] (Installation for portable ComfyUI version: ComfyUI-Manager only) -1. install git -- https://git-scm.com/download/win -- standalone version -- select option: use windows default console window -2. Download [scripts/install-manager-for-portable-version.bat](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-manager-for-portable-version.bat) into installed `"ComfyUI_windows_portable"` directory -- Don't click. Right-click the link and choose 'Save As...' -3. Double-click `install-manager-for-portable-version.bat` batch file - -![portable-install](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/portable-install.jpg) - - -### Installation[method3] (Installation through comfy-cli: install ComfyUI and ComfyUI-Manager at once.) -> RECOMMENDED: comfy-cli provides various features to manage ComfyUI from the CLI. - -* **prerequisite: python 3, git** - -Windows: -```commandline -python -m venv venv -venv\Scripts\activate -pip install comfy-cli -comfy install -``` - -Linux/macOS: -```commandline -python -m venv venv -. venv/bin/activate -pip install comfy-cli -comfy install -``` -* See also: https://github.com/Comfy-Org/comfy-cli - - -### Installation[method4] (Installation for Linux+venv: ComfyUI + ComfyUI-Manager) - -To install ComfyUI with ComfyUI-Manager on Linux using a venv environment, you can follow these steps: -* **prerequisite: python-is-python3, python3-venv, git** - -1. Download [scripts/install-comfyui-venv-linux.sh](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-comfyui-venv-linux.sh) into empty install directory -- Don't click. Right-click the link and choose 'Save As...' -- ComfyUI will be installed in the subdirectory of the specified directory, and the directory will contain the generated executable script. -2. `chmod +x install-comfyui-venv-linux.sh` -3. `./install-comfyui-venv-linux.sh` - -### Installation Precautions -* **DO**: `ComfyUI-Manager` files must be accurately located in the path `ComfyUI/custom_nodes/comfyui-manager` - * Installing in a compressed file format is not recommended. -* **DON'T**: Decompress directly into the `ComfyUI/custom_nodes` location, resulting in the Manager contents like `__init__.py` being placed directly in that directory. - * You have to remove all ComfyUI-Manager files from `ComfyUI/custom_nodes` -* **DON'T**: In a form where decompression occurs in a path such as `ComfyUI/custom_nodes/ComfyUI-Manager/ComfyUI-Manager`. -* **DON'T**: In a form where decompression occurs in a path such as `ComfyUI/custom_nodes/ComfyUI-Manager-main`. - * In such cases, `ComfyUI-Manager` may operate, but it won't be recognized within `ComfyUI-Manager`, and updates cannot be performed. It also poses the risk of duplicate installations. Remove it and install properly via `git clone` method. - - -You can execute ComfyUI by running either `./run_gpu.sh` or `./run_cpu.sh` depending on your system configuration. - -## Colab Notebook -This repository provides Colab notebooks that allow you to install and use ComfyUI, including ComfyUI-Manager. To use ComfyUI, [click on this link](https://colab.research.google.com/github/ltdrdata/ComfyUI-Manager/blob/main/notebooks/comfyui_colab_with_manager.ipynb). -* Support for installing ComfyUI -* Support for basic installation of ComfyUI-Manager -* Support for automatically installing dependencies of custom nodes upon restarting Colab notebooks. - - -## How To Use - -1. Click "Manager" button on main menu - - ![mainmenu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/topbar.jpg) - - -2. If you click on 'Install Custom Nodes' or 'Install Models', an installer dialog will open. - - ![menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/refs/heads/Main/ComfyUI-Manager/images/dialog.jpg) - - * There are three DB modes: `DB: Channel (1day cache)`, `DB: Local`, and `DB: Channel (remote)`. - * `Channel (1day cache)` utilizes Channel cache information with a validity period of one day to quickly display the list. - * This information will be updated when there is no cache, when the cache expires, or when external information is retrieved through the Channel (remote). - * Whenever you start ComfyUI anew, this mode is always set as the **default** mode. - * `Local` uses information stored locally in ComfyUI-Manager. - * This information will be updated only when you update ComfyUI-Manager. - * For custom node developers, they should use this mode when registering their nodes in `custom-node-list.json` and testing them. - * `Channel (remote)` retrieves information from the remote channel, always displaying the latest list. - * In cases where retrieval is not possible due to network errors, it will forcibly use local information. - - * The ```Fetch Updates``` menu retrieves update data for custom nodes locally. Actual updates are applied by clicking the ```Update``` button in the ```Install Custom Nodes``` menu. - -3. Click 'Install' or 'Try Install' button. - - ![node-install-dialog](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/custom-nodes.jpg) - - ![model-install-dialog](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/models.jpg) - - * Installed: This item is already installed. - * Install: Clicking this button will install the item. - * Try Install: This is a custom node of which installation information cannot be confirmed. Click the button to try installing it. - - * If a red background `Channel` indicator appears at the top, it means it is not the default channel. Since the amount of information held is different from the default channel, many custom nodes may not appear in this channel state. - * Channel settings have a broad impact, affecting not only the node list but also all functions like "Update all." - * Conflicted Nodes with a yellow background show a list of nodes conflicting with other extensions in the respective extension. This issue needs to be addressed by the developer, and users should be aware that due to these conflicts, some nodes may not function correctly and may need to be installed accordingly. - -4. Share - ![menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/topbar.jpg) ![share](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/share.jpg) - - * You can share the workflow by clicking the Share button at the bottom of the main menu or selecting Share Output from the Context Menu of the Image node. - * Currently, it supports sharing via [https://comfyworkflows.com/](https://comfyworkflows.com/), - [https://openart.ai](https://openart.ai/workflows/dev), [https://youml.com](https://youml.com) - as well as through the Matrix channel. - - ![menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/share-setting.jpg) - - * Through the Share settings in the Manager menu, you can configure the behavior of the Share button in the Main menu or Share Output button on Context Menu. - * `None`: hide from Main menu - * `All`: Show a dialog where the user can select a title for sharing. - - -## Paths -Starting from V3.38, Manager uses a protected system path for enhanced security. - -* - * If executed without any options, the path defaults to ComfyUI/user. - * It can be set using --user-directory . - -| ComfyUI Version | Manager Path | -|-----------------|--------------| -| v0.3.76+ (with System User API) | `/__manager/` | -| Older versions | `/default/ComfyUI-Manager/` | - -* Basic config files: `config.ini` -* Configurable channel lists: `channels.list` -* Configurable pip overrides: `pip_overrides.json` -* Configurable pip blacklist: `pip_blacklist.list` -* Configurable pip auto fix: `pip_auto_fix.list` -* Saved snapshot files: `snapshots/` -* Startup script files: `startup-scripts/` -* Component files: `components/` - -> **Note**: See [Migration Guide](docs/en/v3.38-userdata-security-migration.md) for upgrade details. - - -## `extra_model_paths.yaml` Configuration -The following settings are applied based on the section marked as `is_default`. - -* `custom_nodes`: Path for installing custom nodes - * Importing does not need to adhere to the path set as `is_default`, but this is the path where custom nodes are installed by the `ComfyUI Nodes Manager`. -* `download_model_base`: Path for downloading models - - -## Snapshot-Manager -* When you press `Save snapshot` or use `Update All` on `Manager Menu`, the current installation status snapshot is saved. - * Snapshot file dir: `/default/ComfyUI-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 `/default/ComfyUI-Manager/startup-scripts/restore-snapshot.json`, and upon restarting ComfyUI, the snapshot is applied and then deleted. - -![model-install-dialog](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/snapshot.jpg) - - -## 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). - - -## How to register your custom node into ComfyUI-Manager - -* Add an entry to `custom-node-list.json` located in the root of ComfyUI-Manager and submit a Pull Request. -* NOTE: Before submitting the PR after making changes, please check `Use local DB` and ensure that the extension list loads without any issues in the `Install custom nodes` dialog. Occasionally, missing or extra commas can lead to JSON syntax errors. -* The remaining JSON will be updated through scripts in the future, so you don't need to worry about it. - - -## Custom node support guide - -* **NOTICE:** - - You should no longer assume that the GitHub repository name will match the subdirectory name under `custom_nodes`. The name of the subdirectory under `custom_nodes` will now use the normalized name from the `name` field in `pyproject.toml`. - - Avoid relying on directory names for imports whenever possible. - -* https://docs.comfy.org/registry/overview -* https://github.com/Comfy-Org/rfcs - -**Special purpose files** (optional) - * `pyproject.toml` - Spec file for comfyregistry. - * `node_list.json` - When your custom nodes pattern of NODE_CLASS_MAPPINGS is not conventional, it is used to manually provide a list of nodes for reference. ([example](https://github.com/melMass/comfy_mtb/raw/main/node_list.json)) - * `requirements.txt` - When installing, this pip requirements will be installed automatically - * `install.py` - When installing, it is automatically called - * **All scripts are executed from the root path of the corresponding custom node.** - - -## Component Sharing -* **Copy & Paste** - * [Demo Page](https://ltdrdata.github.io/component-demo/) - * When pasting a component from the clipboard, it supports text in the following JSON format. (text/plain) - ``` - { - "kind": "ComfyUI Components", - "timestamp": , - "components": - { - : - } - } - ``` - * `` Ensure that the timestamp is always unique. - * "components" should have the same structure as the content of the file stored in `/default/ComfyUI-Manager/components`. - * ``: The name should be in the format `::`. - * ``: In the node data of the group node. - * ``: Only two formats are allowed: `major.minor.patch` or `major.minor`. (e.g. `1.0`, `2.2.1`) - * ``: Saved time - * ``: If the packname is not empty, the category becomes packname/workflow, and it is saved in the .pack file in `/default/ComfyUI-Manager/components`. - * ``: If there is neither a category nor a packname, it is saved in the components category. - ``` - "version":"1.0", - "datetime": 1705390656516, - "packname": "mypack", - "category": "util/pipe", - ``` -* **Drag & Drop** - * Dragging and dropping a `.pack` or `.json` file will add the corresponding components. - * Example pack: [Impact.pack](misc/Impact.pack) - -* Dragging and dropping or pasting a single component will add a node. However, when adding multiple components, nodes will not be added. - - -## Support for installing missing nodes - -![missing-menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/missing-menu.jpg) - -* When you click on the ```Install Missing Custom Nodes``` button in the menu, it displays a list of extension nodes that contain nodes not currently present in the workflow. - -![missing-list](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/missing-list.jpg) - - -# Config -* You can modify the `config.ini` file to apply the settings for ComfyUI-Manager. - * The path to the `config.ini` used by ComfyUI-Manager is displayed in the startup log messages. - * See also: [https://github.com/ltdrdata/ComfyUI-Manager#paths] -* Configuration options: - ``` - [default] - git_exe = - use_uv = - default_cache_as_channel_url = - bypass_ssl = - file_logging = - windows_selector_event_loop_policy = - model_download_by_agent = - downgrade_blacklist = - security_level = strong|normal|normal-|weak> - always_lazy_install = - network_mode = public|private|offline> - ``` - - * network_mode: - - public: An environment that uses a typical public network. - - private: An environment that uses a closed network, where a private node DB is configured via `channel_url`. (Uses cache if available) - - offline: An environment that does not use any external connections when using an offline network. (Uses cache if available) - - -## Additional Feature -* 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. - * 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. - * `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. - * This feature copies only the input and output that match the names. - - * `Possible Input Connections`: It connects all outputs that match the closest type within the specified range. - * This connection links to the closest outputs among the nodes located on the left side of the target node. - - * `Possible(left) + Copy(right)`: When you Double-Click on the left half of the title, it operates as `Possible Input Connections`, and when you Double-Click on the right half, it operates as `Copy All Connections`. - -* Prevent downgrade of specific packages - * List the package names in the `downgrade_blacklist` section of the `config.ini` file, separating them with commas. - * e.g - ``` - downgrade_blacklist = diffusers, kornia - ``` - -* Custom pip mapping - * When you create the `pip_overrides.json` file, it changes the installation of specific pip packages to installations defined by the user. - * Please refer to the `pip_overrides.json.template` file. - -* Prevent the installation of specific pip packages - * List the package names one per line in the `pip_blacklist.list` file. - -* Automatically Restoring pip Installation - * If you list pip spec requirements in `pip_auto_fix.list`, similar to `requirements.txt`, it will automatically restore the specified versions when starting ComfyUI or when versions get mismatched during various custom node installations. - * `--index-url` can be used. - -* Use `aria2` as downloader - * [howto](docs/en/use_aria2.md) - - -## Environment Variables - -The following features can be configured using environment variables: - -* **COMFYUI_PATH**: The installation path of ComfyUI -* **GITHUB_ENDPOINT**: Reverse proxy configuration for environments with limited access to GitHub -* **HF_ENDPOINT**: Reverse proxy configuration for environments with limited access to Hugging Face - - -### Example 1: -Redirecting `https://github.com/ltdrdata/ComfyUI-Impact-Pack` to `https://mirror.ghproxy.com/https://github.com/ltdrdata/ComfyUI-Impact-Pack` - -``` -GITHUB_ENDPOINT=https://mirror.ghproxy.com/https://github.com -``` - -#### Example 2: -Changing `https://huggingface.co/path/to/somewhere` to `https://some-hf-mirror.com/path/to/somewhere` - -``` -HF_ENDPOINT=https://some-hf-mirror.com -``` - -## Scanner -When you run the `scan.sh` script: - -* It updates the `extension-node-map.json`. - * To do this, it pulls or clones the custom nodes listed in `custom-node-list.json` into `~/.tmp/default`. - * To skip this step, add the `--skip-update` option. - * If you want to specify a different path instead of `~/.tmp/default`, run `python scanner.py [path]` directly instead of `scan.sh`. - -* 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-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 `/default/ComfyUI-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 - * Edit `config.ini` file: add `windows_selector_event_loop_policy = True` -* If the `SSL: CERTIFICATE_VERIFY_FAILED` error occurs. - * Edit `config.ini` file: add `bypass_ssl = True` - - -## Security policy - * Edit `config.ini` file: add `security_level = ` - * `strong` - * doesn't allow `high` and `middle` level risky feature - * `normal` - * doesn't allow `high` level risky feature - * `middle` level risky feature is available - * `normal-` - * doesn't allow `high` level risky feature if `--listen` is specified and not starts with `127.` - * `middle` level risky feature is available - * `weak` - * all feature is available - - * `high` level risky features - * `Install via git url`, `pip install` - * Installation of custom nodes registered not in the `default channel`. - * Fix custom nodes - - * `middle` level risky features - * Uninstall/Update - * Installation of custom nodes registered in the `default channel`. - * Restore/Remove Snapshot - * Restart - - * `low` level risky features - * Update ComfyUI - - -# Disclaimer - -* This extension simply provides the convenience of installing custom nodes and does not guarantee their proper functioning. - - -## Credit -ComfyUI/[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - A powerful and modular stable diffusion GUI. - -**And, for all ComfyUI custom node developers** diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 65aae69c..00000000 --- a/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -This file is the entry point for the ComfyUI-Manager package, handling CLI-only mode and initial setup. -""" - -import os -import sys - -cli_mode_flag = os.path.join(os.path.dirname(__file__), '.enable-cli-only-mode') - -if not os.path.exists(cli_mode_flag): - sys.path.append(os.path.join(os.path.dirname(__file__), "glob")) - import manager_server # noqa: F401 - import share_3rdparty # noqa: F401 - import cm_global - - if not cm_global.disable_front and not 'DISABLE_COMFYUI_MANAGER_FRONT' in os.environ: - WEB_DIRECTORY = "js" -else: - print("\n[ComfyUI-Manager] !! cli-only-mode is enabled !!\n") - -NODE_CLASS_MAPPINGS = {} -__all__ = ['NODE_CLASS_MAPPINGS'] - - - diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..1a282260 Binary files /dev/null and b/__pycache__/__init__.cpython-312.pyc differ diff --git a/__pycache__/prestartup_script.cpython-312.pyc b/__pycache__/prestartup_script.cpython-312.pyc new file mode 100644 index 00000000..a20fe30e Binary files /dev/null and b/__pycache__/prestartup_script.cpython-312.pyc differ diff --git a/alter-list.json b/alter-list.json deleted file mode 100644 index 33398277..00000000 --- a/alter-list.json +++ /dev/null @@ -1,224 +0,0 @@ -{ - "items": [ - { - "id":"https://github.com/Fannovel16/comfyui_controlnet_aux", - "tags":"controlnet", - "description": "This extension provides preprocessor nodes for using controlnet." - }, - { - "id":"https://github.com/comfyanonymous/ComfyUI_experiments", - "tags":"Dynamic Thresholding, DT, CFG, controlnet, reference only", - "description": "This experimental nodes contains a 'Reference Only' node and a 'ModelSamplerTonemapNoiseTest' node corresponding to the 'Dynamic Threshold'." - }, - { - "id":"https://github.com/ltdrdata/ComfyUI-Impact-Pack", - "tags":"ddetailer, adetailer, ddsd, DD, loopback scaler, prompt, wildcard, dynamic prompt", - "description": "To implement the feature of automatically detecting faces and enhancing details, various detection nodes and detailers provided by the Impact Pack can be applied. Similarly to Loopback Scaler, it also provides various custom workflows that can apply Ksampler while gradually scaling up." - }, - { - "id":"https://github.com/ltdrdata/ComfyUI-Inspire-Pack", - "tags":"lora block weight, effective block analyzer, lbw, variation seed", - "description": "The Inspire Pack provides the functionality of Lora Block Weight, Variation Seed." - }, - { - "id":"https://github.com/biegert/ComfyUI-CLIPSeg/raw/main/custom_nodes/clipseg.py", - "tags":"ddsd", - "description": "This extension provides a feature that generates segment masks on an image using a text prompt. When used in conjunction with Impact Pack, it enables applications such as DDSD." - }, - { - "id":"https://github.com/BadCafeCode/masquerade-nodes-comfyui", - "tags":"ddetailer", - "description": "This extension is a less feature-rich and well-maintained alternative to Impact Pack, but it has fewer dependencies and may be easier to install on abnormal configurations. The author recommends trying Impact Pack first." - }, - { - "id":"https://github.com/BlenderNeko/ComfyUI_Cutoff", - "tags":"cutoff", - "description": "By using this extension, prompts like 'blue hair' can be prevented from interfering with other prompts by blocking the attribute 'blue' from being used in prompts other than 'hair'." - }, - { - "id":"https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb", - "tags":"prompt, weight", - "description": "There are differences in the processing methods of prompts, such as weighting and scheduling, between A1111 and ComfyUI. With this extension, various settings can be used to implement prompt processing methods similar to A1111. As this feature is also integrated into ComfyUI Cutoff, please download the Cutoff extension if you plan to use it in conjunction with Cutoff." - }, - { - "id":"https://github.com/shiimizu/ComfyUI_smZNodes", - "tags":"prompt, weight", - "description": "There are differences in the processing methods of prompts, such as weighting and scheduling, between A1111 and ComfyUI. This extension helps to reproduce the same embedding as A1111." - }, - { - "id":"https://github.com/BlenderNeko/ComfyUI_Noise", - "tags":"img2img alt, random", - "description": "The extension provides an unsampler that reverses the sampling process, allowing for a function similar to img2img alt to be implemented. Furthermore, ComfyUI uses CPU's Random instead of GPU's Random for better reproducibility compared to A1111. This extension provides the ability to use GPU's Random for Latent Noise. However, since GPU's Random may vary depending on the GPU model, reproducibility on different devices cannot be guaranteed." - }, - { - "id":"https://github.com/BlenderNeko/ComfyUI_SeeCoder", - "tags":"seecoder, prompt-free-diffusion", - "description": "The extension provides seecoder feature." - }, - { - "id":"https://github.com/lilly1987/ComfyUI_node_Lilly", - "tags":"prompt, wildcard", - "description": "This extension provides features such as a wildcard function that randomly selects prompts belonging to a category and the ability to directly load lora from prompts." - }, - { - "id":"https://github.com/Davemane42/ComfyUI_Dave_CustomNode", - "tags":"latent couple", - "description": "ComfyUI already provides the ability to composite latents by default. However, this extension makes it more convenient to use by visualizing the composite area." - }, - { - "id":"https://github.com/LEv145/images-grid-comfy-plugin", - "tags":"X/Y Plot", - "description": "This tool provides a viewer node that allows for checking multiple outputs in a grid, similar to the X/Y Plot extension." - }, - { - "id":"https://github.com/pythongosssss/ComfyUI-WD14-Tagger", - "tags":"deepbooru, clip interrogation", - "description": "This extension generates clip text by taking an image as input and using the Deepbooru model." - }, - { - "id":"https://github.com/szhublox/ambw_comfyui", - "tags":"supermerger", - "description": "This node takes two models, merges individual blocks together at various ratios, and automatically rates each merge, keeping the ratio with the highest score. " - }, - { - "id":"https://github.com/ssitu/ComfyUI_UltimateSDUpscale", - "tags":"upscaler, Ultimate SD Upscale", - "description": "ComfyUI nodes for the Ultimate Stable Diffusion Upscale script by Coyote-A. Uses the same script used in the A1111 extension to hopefully replicate images generated using the A1111 webui." - }, - { - "id":"https://github.com/dawangraoming/ComfyUI_ksampler_gpu/raw/main/ksampler_gpu.py", - "tags":"random, noise", - "description": "A1111 provides KSampler that uses GPU-based random noise. This extension offers KSampler utilizing GPU-based random noise." - }, - { - "id":"https://github.com/space-nuko/nui-suite", - "tags":"prompt, dynamic prompt", - "description": "This extension provides nodes with the functionality of dynamic prompts." - }, - { - "id":"https://github.com/melMass/comfy_mtb", - "tags":"roop", - "description": "This extension provides bunch of nodes including roop" - }, - { - "id":"https://github.com/ssitu/ComfyUI_roop", - "tags":"roop", - "description": "This extension provides nodes for the roop A1111 webui script." - }, - { - "id":"https://github.com/asagi4/comfyui-prompt-control", - "tags":"prompt, prompt editing", - "description": "This extension provides the ability to use prompts like \n\n**a [large::0.1] [cat|dog:0.05] [::0.5] [in a park:in space:0.4]**\n\n" - }, - { - "id":"https://github.com/adieyal/comfyui-dynamicprompts", - "tags":"prompt, dynamic prompt", - "description": "This extension is a port of sd-dynamic-prompt to ComfyUI." - }, - { - "id":"https://github.com/kwaroran/abg-comfyui", - "tags":"abg, background remover", - "description": "A Anime Background Remover node for comfyui, based on this hf space, works same as AGB extention in automatic1111." - }, - { - "id":"https://github.com/Gourieff/comfyui-reactor-node", - "tags":"reactor, sd-webui-roop-nsfw", - "description": "This is a ported version of ComfyUI for the sd-webui-roop-nsfw extension." - }, - { - "id":"https://github.com/laksjdjf/cgem156-ComfyUI", - "tags":"regional prompt, latent couple, prompt", - "description": "This custom nodes provide a functionality similar to regional prompts, offering couple features at the attention level." - }, - { - "id":"https://github.com/FizzleDorf/ComfyUI_FizzNodes", - "tags":"deforum", - "description": "This custom nodes provide functionality that assists in animation creation, similar to deforum." - }, - { - "id":"https://github.com/seanlynch/comfyui-optical-flow", - "tags":"deforum, vid2vid", - "description": "This custom nodes provide functionality that assists in animation creation, similar to deforum." - }, - { - "id":"https://github.com/ssitu/ComfyUI_fabric", - "tags":"fabric", - "description": "Similar to sd-webui-fabric, this custom nodes provide the functionality of [a/FABRIC](https://github.com/sd-fabric/fabric)." - }, - { - "id":"https://github.com/Zuellni/ComfyUI-ExLlama", - "tags":"ExLlama, prompt, language model", - "description": "Similar to text-generation-webui, this custom nodes provide the functionality of [a/exllama](https://github.com/turboderp/exllama)." - }, - { - "id":"https://github.com/spinagon/ComfyUI-seamless-tiling", - "tags":"tiling", - "description": "ComfyUI node for generating seamless textures Replicates 'Tiling' option from A1111" - }, - { - "id":"https://github.com/laksjdjf/cd-tuner_negpip-ComfyUI", - "tags":"cd-tuner, negpip", - "description": "This extension is a port of the [a/sd-webui-cd-tuner](https://github.com/hako-mikan/sd-webui-cd-tuner)(a.k.a. CD(color/Detail) Tuner )and [a/sd-webui-negpip](https://github.com/hako-mikan/sd-webui-negpip)(a.k.a. NegPiP) extensions of A1111 to ComfyUI." - }, - { - "id":"https://github.com/mcmonkeyprojects/sd-dynamic-thresholding", - "tags":"DT, dynamic thresholding", - "description": "This custom node is a port of the Dynamic Thresholding extension from A1111 to make it available for use in ComfyUI." - }, - { - "id":"https://github.com/hhhzzyang/Comfyui_Lama", - "tags":"lama, inpainting anything", - "description": "This extension provides custom nodes developed based on [a/LaMa](https://github.com/advimman/lama) and [a/Inpainting anything](https://github.com/geekyutao/Inpaint-Anything)." - }, - { - "id":"https://github.com/mlinmg/ComfyUI-LaMA-Preprocessor", - "tags":"lama", - "description": "This extension provides custom nodes for [a/LaMa](https://github.com/advimman/lama) functionality." - }, - { - "id":"https://github.com/Haoming02/comfyui-diffusion-cg", - "tags":"diffusion-cg", - "description": "This extension provides custom nodes for [a/SD Webui Diffusion Color Grading](https://github.com/Haoming02/sd-webui-diffusion-cg) functionality." - }, - { - "id":"https://github.com/asagi4/ComfyUI-CADS", - "tags":"diffusion-cg", - "description": "This extension provides custom nodes for [a/sd-webui-cads](https://github.com/v0xie/sd-webui-cads) functionality." - }, - { - "id":"https://git.mmaker.moe/mmaker/sd-webui-color-enhance", - "tags":"color-enhance", - "description": "This extension supports both A1111 and ComfyUI simultaneously." - }, - { - "id":"https://github.com/shiimizu/ComfyUI-TiledDiffusion", - "tags":"multidiffusion", - "description": "This extension provides custom nodes for [a/Mixture of Diffusers](https://github.com/albarji/mixture-of-diffusers) and [a/MultiDiffusion](https://github.com/omerbt/MultiDiffusion)" - }, - { - "id":"https://github.com/abyz22/image_control", - "tags":"BMAB", - "description": "This extension provides some alternative functionalities of the [a/sd-webui-bmab](https://github.com/portu-sim/sd-webui-bmab) extension." - }, - { - "id":"https://github.com/blepping/ComfyUI-sonar", - "tags":"sonar", - "description": "This extension provides some alternative functionalities of the [a/stable-diffusion-webui-sonar](https://github.com/Kahsolt/stable-diffusion-webui-sonar) extension." - }, - { - "id":"https://github.com/AIFSH/ComfyUI-RVC", - "tags":"sonar", - "description": "a comfyui custom node for [a/Retrieval-based-Voice-Conversion-WebUI](https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git), you can Voice-Conversion in comfyui now!" - }, - { - "id":"https://github.com/portu-sim/comfyui-bmab", - "tags":"bmab", - "description": "a comfyui custom node for [a/sd-webui-bmab](https://github.com/portu-sim/sd-webui-bmab)" - }, - { - "id":"https://github.com/ThereforeGames/ComfyUI-Unprompted", - "tags":"unprompted", - "description": "This extension is a port of [a/unprompted](https://github.com/ThereforeGames/unprompted)" - } - ] -} \ No newline at end of file diff --git a/channels.list.template b/channels.list.template deleted file mode 100644 index 9a8d6877..00000000 --- a/channels.list.template +++ /dev/null @@ -1,6 +0,0 @@ -default::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main -recent::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/new -legacy::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/legacy -forked::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/forked -dev::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/dev -tutorial::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/tutorial \ No newline at end of file diff --git a/check.bat b/check.bat deleted file mode 100644 index e7a3b09f..00000000 --- a/check.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off - -python json-checker.py "custom-node-list.json" -python json-checker.py "model-list.json" -python json-checker.py "alter-list.json" -python json-checker.py "extension-node-map.json" -python json-checker.py "node_db\new\custom-node-list.json" -python json-checker.py "node_db\new\model-list.json" -python json-checker.py "node_db\new\extension-node-map.json" -python json-checker.py "node_db\dev\custom-node-list.json" -python json-checker.py "node_db\dev\model-list.json" -python json-checker.py "node_db\dev\extension-node-map.json" -python json-checker.py "node_db\tutorial\custom-node-list.json" -python json-checker.py "node_db\tutorial\model-list.json" -python json-checker.py "node_db\tutorial\extension-node-map.json" -python json-checker.py "node_db\legacy\custom-node-list.json" -python json-checker.py "node_db\legacy\model-list.json" -python json-checker.py "node_db\legacy\extension-node-map.json" -python json-checker.py "node_db\forked\custom-node-list.json" -python json-checker.py "node_db\forked\model-list.json" -python json-checker.py "node_db\forked\extension-node-map.json" \ No newline at end of file diff --git a/check.sh b/check.sh deleted file mode 100755 index c3ef41c2..00000000 --- a/check.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -echo -echo CHECK1 - -files=( - "custom-node-list.json" - "model-list.json" - "alter-list.json" - "extension-node-map.json" - "github-stats.json" - "extras.json" - "node_db/new/custom-node-list.json" - "node_db/new/model-list.json" - "node_db/new/extension-node-map.json" - "node_db/dev/custom-node-list.json" - "node_db/dev/model-list.json" - "node_db/dev/extension-node-map.json" - "node_db/tutorial/custom-node-list.json" - "node_db/tutorial/model-list.json" - "node_db/tutorial/extension-node-map.json" - "node_db/legacy/custom-node-list.json" - "node_db/legacy/model-list.json" - "node_db/legacy/extension-node-map.json" - "node_db/forked/custom-node-list.json" - "node_db/forked/model-list.json" - "node_db/forked/extension-node-map.json" -) - -for file in "${files[@]}"; do - python json-checker.py "$file" -done - -echo -echo CHECK2 -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 "^\s*[^#].*\.whl" - -echo diff --git a/cm-cli.py b/cm-cli.py deleted file mode 100644 index 04043423..00000000 --- a/cm-cli.py +++ /dev/null @@ -1,1288 +0,0 @@ -import os -import sys -import traceback -import json -import asyncio -import concurrent -import threading -from typing import Optional - -import typer -from rich import print -from typing_extensions import List, Annotated -import re -import git -import importlib - - -sys.path.append(os.path.dirname(__file__)) -sys.path.append(os.path.join(os.path.dirname(__file__), "glob")) - -import manager_util - -# read env vars -# COMFYUI_FOLDERS_BASE_PATH is not required in cm-cli.py -# `comfy_path` should be resolved before importing manager_core -comfy_path = os.environ.get('COMFYUI_PATH') -if comfy_path is None: - try: - import folder_paths - comfy_path = os.path.join(os.path.dirname(folder_paths.__file__)) - except: - print("\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr) - comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..')) - -# This should be placed here -sys.path.append(comfy_path) - -import utils.extra_config -import cm_global -import manager_core as core -from manager_core import unified_manager -import cnr_utils - -comfyui_manager_path = os.path.abspath(os.path.dirname(__file__)) - -cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'} -cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia'] - -cm_global.pip_overrides = {} - -if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json")): - with open(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json"), 'r', encoding="UTF-8", errors="ignore") as json_file: - cm_global.pip_overrides = json.load(json_file) - - -if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list")): - with open(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list"), 'r', encoding="UTF-8", errors="ignore") as f: - for x in f.readlines(): - y = x.strip() - if y != '': - cm_global.pip_blacklist.add(y) - - -def check_comfyui_hash(): - try: - repo = git.Repo(comfy_path) - core.comfy_ui_revision = len(list(repo.iter_commits('HEAD'))) - core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime - except: - print('[bold yellow]INFO: Frozen ComfyUI mode.[/bold yellow]') - core.comfy_ui_revision = 0 - core.comfy_ui_commit_datetime = 0 - - cm_global.variables['comfyui.revision'] = core.comfy_ui_revision - - -check_comfyui_hash() # This is a preparation step for manager_core -core.check_invalid_nodes() - - -def read_downgrade_blacklist(): - try: - import configparser - config = configparser.ConfigParser(strict=False) - config.read(core.manager_config.path) - default_conf = config['default'] - - if 'downgrade_blacklist' in default_conf: - items = default_conf['downgrade_blacklist'].split(',') - items = [x.strip() for x in items if x != ''] - cm_global.pip_downgrade_blacklist += items - cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist)) - except: - pass - - -read_downgrade_blacklist() # This is a preparation step for manager_core - - -class Ctx: - folder_paths = None - - def __init__(self): - self.channel = 'default' - self.no_deps = False - self.mode = 'cache' - self.user_directory = None - self.custom_nodes_paths = [os.path.join(core.comfy_base_path, 'custom_nodes')] - self.manager_files_directory = os.path.dirname(__file__) - - if Ctx.folder_paths is None: - try: - Ctx.folder_paths = importlib.import_module('folder_paths') - except ImportError: - print("Warning: Unable to import folder_paths module") - - def set_channel_mode(self, channel, mode): - if mode is not None: - self.mode = mode - - valid_modes = ["remote", "local", "cache"] - if mode and mode.lower() not in valid_modes: - typer.echo( - f"Invalid mode: {mode}. Allowed modes are 'remote', 'local', 'cache'.", - err=True, - ) - exit(1) - - if channel is not None: - self.channel = channel - - asyncio.run(unified_manager.reload(cache_mode=self.mode, dont_wait=False)) - asyncio.run(unified_manager.load_nightly(self.channel, self.mode)) - - def set_no_deps(self, no_deps): - self.no_deps = no_deps - - def set_user_directory(self, user_directory): - if user_directory is None: - return - - extra_model_paths_yaml = os.path.join(user_directory, 'extra_model_paths.yaml') - if os.path.exists(extra_model_paths_yaml): - utils.extra_config.load_extra_path_config(extra_model_paths_yaml) - - core.update_user_directory(user_directory) - - if os.path.exists(core.manager_pip_overrides_path): - with open(core.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file: - cm_global.pip_overrides = json.load(json_file) - - if os.path.exists(core.manager_pip_blacklist_path): - with open(core.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f: - for x in f.readlines(): - y = x.strip() - if y != '': - cm_global.pip_blacklist.add(y) - - def update_custom_nodes_dir(self, target_dir): - import folder_paths - a, b = folder_paths.folder_names_and_paths['custom_nodes'] - folder_paths.folder_names_and_paths['custom_nodes'] = [os.path.abspath(target_dir)], set() - - @staticmethod - def get_startup_scripts_path(): - return os.path.join(core.manager_startup_script_path, "install-scripts.txt") - - @staticmethod - def get_restore_snapshot_path(): - return os.path.join(core.manager_startup_script_path, "restore-snapshot.json") - - @staticmethod - def get_snapshot_path(): - return core.manager_snapshot_path - - @staticmethod - def get_custom_nodes_paths(): - if Ctx.folder_paths is None: - print("Error: folder_paths module is not available") - return [] - return Ctx.folder_paths.get_folder_paths('custom_nodes') - - -cmd_ctx = Ctx() - - -def install_node(node_spec_str, is_all=False, cnt_msg='', **kwargs): - exit_on_fail = kwargs.get('exit_on_fail', False) - print(f"install_node exit on fail:{exit_on_fail}...") - - if core.is_valid_url(node_spec_str): - # install via urls - res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps)) - if not res.result: - print(res.msg) - print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]") - if exit_on_fail: - sys.exit(1) - else: - print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}") - else: - node_spec = unified_manager.resolve_node_spec(node_spec_str) - - if node_spec is None: - return - - node_name, version_spec, is_specified = node_spec - - # NOTE: install node doesn't allow update if version is not specified - if not is_specified: - version_spec = None - - res = asyncio.run(unified_manager.install_by_id(node_name, version_spec, cmd_ctx.channel, cmd_ctx.mode, instant_execution=True, no_deps=cmd_ctx.no_deps)) - - if res.action == 'skip': - print(f"{cnt_msg} [ SKIP ] {node_name:50} => Already installed") - elif res.action == 'enable': - print(f"{cnt_msg} [ ENABLED ] {node_name:50}") - elif res.action == 'install-git' and res.target == 'nightly': - print(f"{cnt_msg} [INSTALLED] {node_name:50}[NIGHTLY]") - elif res.action == 'install-git' and res.target == 'unknown': - print(f"{cnt_msg} [INSTALLED] {node_name:50}[UNKNOWN]") - elif res.action == 'install-cnr' and res.result: - print(f"{cnt_msg} [INSTALLED] {node_name:50}[{res.target}]") - elif res.action == 'switch-cnr' and res.result: - print(f"{cnt_msg} [INSTALLED] {node_name:50}[{res.target}]") - elif (res.action == 'switch-cnr' or res.action == 'install-cnr') and not res.result and node_name in unified_manager.cnr_map: - print(f"\nAvailable version of '{node_name}'") - show_versions(node_name) - print("") - else: - print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.\n{res.msg}[/bold red]") - if exit_on_fail: - sys.exit(1) - - -def reinstall_node(node_spec_str, is_all=False, cnt_msg=''): - node_spec = unified_manager.resolve_node_spec(node_spec_str) - - node_name, version_spec, _ = node_spec - - unified_manager.unified_uninstall(node_name, version_spec == 'unknown') - install_node(node_name, is_all=is_all, cnt_msg=cnt_msg) - - -def fix_node(node_spec_str, is_all=False, cnt_msg=''): - node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='active') - - if node_spec is None: - if not is_all: - if unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive') is not None: - print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Disabled") - else: - print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Not installed") - - return - - node_name, version_spec, _ = node_spec - - print(f"{cnt_msg} [ FIXING ]: {node_name:50}[{version_spec}]") - res = unified_manager.unified_fix(node_name, version_spec, no_deps=cmd_ctx.no_deps) - - if not res.result: - print(f"[bold red]ERROR: f{res.msg}[/bold red]") - - -def uninstall_node(node_spec_str: str, is_all: bool = False, cnt_msg: str = ''): - spec = node_spec_str.split('@') - if len(spec) == 2 and spec[1] == 'unknown': - node_name = spec[0] - is_unknown = True - else: - node_name = spec[0] - is_unknown = False - - res = unified_manager.unified_uninstall(node_name, is_unknown) - if len(spec) == 1 and res.action == 'skip' and not is_unknown: - res = unified_manager.unified_uninstall(node_name, True) - - if res.action == 'skip': - print(f"{cnt_msg} [ SKIPPED ]: {node_name:50} => Not installed") - - elif res.result: - print(f"{cnt_msg} [UNINSTALLED] {node_name:50}") - else: - print(f"ERROR: An error occurred while uninstalling '{node_name}'.") - - -def update_node(node_spec_str, is_all=False, cnt_msg=''): - node_spec = unified_manager.resolve_node_spec(node_spec_str, 'active') - - if node_spec is None: - if unified_manager.resolve_node_spec(node_spec_str, 'inactive'): - print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Disabled") - else: - print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Not installed") - return None - - node_name, version_spec, _ = node_spec - - res = unified_manager.unified_update(node_name, version_spec, no_deps=cmd_ctx.no_deps, return_postinstall=True) - - if not res.result: - print(f"ERROR: An error occurred while updating '{node_name}'.") - elif res.action == 'skip': - print(f"{cnt_msg} [ SKIPPED ]: {node_name:50} => {res.msg}") - else: - print(f"{cnt_msg} [ UPDATED ]: {node_name:50} => ({version_spec} -> {res.target})") - - return res.with_target(f'{node_name}@{res.target}') - - -def update_parallel(nodes): - is_all = False - if 'all' in nodes: - is_all = True - nodes = [] - for x in unified_manager.active_nodes.keys(): - nodes.append(x) - for x in unified_manager.unknown_active_nodes.keys(): - nodes.append(x+"@unknown") - else: - nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui']] - - total = len(nodes) - - lock = threading.Lock() - processed = [] - - i = 0 - - def process_custom_node(x): - nonlocal i - nonlocal processed - - with lock: - i += 1 - - try: - res = update_node(x, is_all=is_all, cnt_msg=f'{i}/{total}') - with lock: - processed.append(res) - except Exception as e: - print(f"ERROR: {e}") - traceback.print_exc() - - with concurrent.futures.ThreadPoolExecutor(4) as executor: - for item in nodes: - executor.submit(process_custom_node, item) - - i = 1 - for res in processed: - if res is not None: - print(f"[{i}/{total}] Post update: {res.target}") - if res.postinstall is not None: - res.postinstall() - i += 1 - - -def update_comfyui(): - res = core.update_path(comfy_path, instant_execution=True) - if res == 'fail': - print("Updating ComfyUI has failed.") - elif res == 'updated': - print("ComfyUI is updated.") - else: - print("ComfyUI is already up to date.") - - -def enable_node(node_spec_str, is_all=False, cnt_msg=''): - if unified_manager.resolve_node_spec(node_spec_str, guess_mode='active') is not None: - print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Already enabled") - return - - node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive') - - if node_spec is None: - print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Not found") - return - - node_name, version_spec, _ = node_spec - - res = unified_manager.unified_enable(node_name, version_spec) - - if res.action == 'skip': - print(f"{cnt_msg} [ SKIP ] {node_name:50} => {res.msg}") - elif res.result: - print(f"{cnt_msg} [ENABLED] {node_name:50}") - else: - print(f"{cnt_msg} [ FAIL ] {node_name:50} => {res.msg}") - - -def disable_node(node_spec_str: str, is_all=False, cnt_msg=''): - if 'comfyui-manager' in node_spec_str.lower(): - return - - node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='active') - - if node_spec is None: - if unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive') is not None: - print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Already disabled") - else: - print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Not found") - return - - node_name, version_spec, _ = node_spec - - res = unified_manager.unified_disable(node_name, version_spec == 'unknown') - - if res.action == 'skip': - print(f"{cnt_msg} [ SKIP ] {node_name:50} => {res.msg}") - elif res.result: - print(f"{cnt_msg} [DISABLED] {node_name:50}") - else: - print(f"{cnt_msg} [ FAIL ] {node_name:50} => {res.msg}") - - -def show_list(kind, simple=False): - custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=cmd_ctx.channel, mode=cmd_ctx.mode)) - - # collect not-installed unknown nodes - not_installed_unknown_nodes = [] - repo_unknown = {} - - for k, v in custom_nodes.items(): - if 'cnr_latest' not in v: - if len(v['files']) == 1: - repo_url = v['files'][0] - node_name = repo_url.split('/')[-1] - if node_name not in unified_manager.unknown_inactive_nodes and node_name not in unified_manager.unknown_active_nodes: - not_installed_unknown_nodes.append(v) - else: - repo_unknown[node_name] = v - - processed = {} - unknown_processed = [] - - flag = kind in ['all', 'cnr', 'installed', 'enabled'] - for k, v in unified_manager.active_nodes.items(): - if flag: - cnr = unified_manager.cnr_map[k] - processed[k] = "[ ENABLED ] ", cnr['name'], k, cnr['publisher']['name'], v[0] - else: - processed[k] = None - - if flag and kind != 'cnr': - for k, v in unified_manager.unknown_active_nodes.items(): - item = repo_unknown.get(k) - - if item is None: - continue - - log_item = "[ ENABLED ] ", item['title'], k, item['author'] - unknown_processed.append(log_item) - - flag = kind in ['all', 'cnr', 'installed', 'disabled'] - for k, v in unified_manager.cnr_inactive_nodes.items(): - if k in processed: - continue - - if flag: - cnr = unified_manager.cnr_map[k] - processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], ", ".join(list(v.keys())) - else: - processed[k] = None - - for k, v in unified_manager.nightly_inactive_nodes.items(): - if k in processed: - continue - - if flag: - cnr = unified_manager.cnr_map[k] - processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], 'nightly' - else: - processed[k] = None - - if flag and kind != 'cnr': - for k, v in unified_manager.unknown_inactive_nodes.items(): - item = repo_unknown.get(k) - - if item is None: - continue - - log_item = "[ DISABLED ] ", item['title'], k, item['author'] - unknown_processed.append(log_item) - - flag = kind in ['all', 'cnr', 'not-installed'] - for k, v in unified_manager.cnr_map.items(): - if k in processed: - continue - - if flag: - cnr = unified_manager.cnr_map[k] - ver_spec = v['latest_version']['version'] if 'latest_version' in v else '0.0.0' - processed[k] = "[ NOT INSTALLED ] ", cnr['name'], k, cnr['publisher']['name'], ver_spec - else: - processed[k] = None - - if flag and kind != 'cnr': - for x in not_installed_unknown_nodes: - if len(x['files']) == 1: - node_id = os.path.basename(x['files'][0]) - log_item = "[ NOT INSTALLED ] ", x['title'], node_id, x['author'] - unknown_processed.append(log_item) - - for x in processed.values(): - if x is None: - continue - - prefix, title, short_id, author, ver_spec = x - if simple: - print(title+'@'+ver_spec) - else: - print(f"{prefix} {title:50} {short_id:30} (author: {author:20}) \\[{ver_spec}]") - - for x in unknown_processed: - prefix, title, short_id, author = x - if simple: - print(title+'@unknown') - else: - print(f"{prefix} {title:50} {short_id:30} (author: {author:20}) [UNKNOWN]") - - -async def show_snapshot(simple_mode=False): - json_obj = await core.get_current_snapshot() - - if simple_mode: - print(f"[{json_obj['comfyui']}] comfyui") - for k, v in json_obj['git_custom_nodes'].items(): - print(f"[{v['hash']}] {k}") - for v in json_obj['file_custom_nodes']: - print(f"[ N/A ] {v['filename']}") - - else: - formatted_json = json.dumps(json_obj, ensure_ascii=False, indent=4) - print(formatted_json) - - -def show_snapshot_list(simple_mode=False): - snapshot_path = cmd_ctx.get_snapshot_path() - - files = os.listdir(snapshot_path) - json_files = [x for x in files if x.endswith('.json')] - for x in sorted(json_files): - print(x) - - -def cancel(): - if os.path.exists(cmd_ctx.get_startup_scripts_path()): - os.remove(cmd_ctx.get_startup_scripts_path()) - - if os.path.exists(cmd_ctx.get_restore_snapshot_path()): - os.remove(cmd_ctx.get_restore_snapshot_path()) - - -async def auto_save_snapshot(): - path = await core.save_snapshot_with_postfix('cli-autosave') - print(f"Current snapshot is saved as `{path}`") - - -def get_all_installed_node_specs(): - res = [] - processed = set() - for k, v in unified_manager.active_nodes.items(): - node_spec_str = f"{k}@{v[0]}" - res.append(node_spec_str) - processed.add(k) - - for k in unified_manager.cnr_inactive_nodes.keys(): - if k in processed: - continue - - latest = unified_manager.get_from_cnr_inactive_nodes(k) - if latest is not None: - node_spec_str = f"{k}@{str(latest[0])}" - res.append(node_spec_str) - - for k in unified_manager.nightly_inactive_nodes.keys(): - if k in processed: - continue - - node_spec_str = f"{k}@nightly" - res.append(node_spec_str) - - for k in unified_manager.unknown_active_nodes.keys(): - node_spec_str = f"{k}@unknown" - res.append(node_spec_str) - - for k in unified_manager.unknown_inactive_nodes.keys(): - node_spec_str = f"{k}@unknown" - res.append(node_spec_str) - - return res - - -def for_each_nodes(nodes, act, allow_all=True, **kwargs): - is_all = False - if allow_all and 'all' in nodes: - is_all = True - nodes = get_all_installed_node_specs() - else: - nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui', 'all']] - - total = len(nodes) - i = 1 - for x in nodes: - try: - act(x, is_all=is_all, cnt_msg=f'{i}/{total}', **kwargs) - except Exception as e: - print(f"ERROR: {e}") - traceback.print_exc() - i += 1 - - -app = typer.Typer() - - -@app.command(help="Display help for commands") -def help(ctx: typer.Context): - print(ctx.find_root().get_help()) - ctx.exit(0) - - -@app.command(help="Install custom nodes") -def install( - nodes: List[str] = typer.Argument( - ..., help="List of custom nodes to install" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - no_deps: Annotated[ - Optional[bool], - typer.Option( - "--no-deps", - show_default=False, - help="Skip installing any Python dependencies", - ), - ] = False, - user_directory: str = typer.Option( - None, - help="user directory" - ), - exit_on_fail: bool = typer.Option( - False, - help="Exit on failure" - ) -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - cmd_ctx.set_no_deps(no_deps) - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - for_each_nodes(nodes, act=install_node, exit_on_fail=exit_on_fail) - pip_fixer.fix_broken() - - -@app.command(help="Reinstall custom nodes") -def reinstall( - nodes: List[str] = typer.Argument( - ..., help="List of custom nodes to reinstall" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - no_deps: Annotated[ - Optional[bool], - typer.Option( - "--no-deps", - show_default=False, - help="Skip installing any Python dependencies", - ), - ] = False, - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - cmd_ctx.set_no_deps(no_deps) - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - for_each_nodes(nodes, act=reinstall_node) - pip_fixer.fix_broken() - - -@app.command(help="Uninstall custom nodes") -def uninstall( - nodes: List[str] = typer.Argument( - ..., help="List of custom nodes to uninstall" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), -): - cmd_ctx.set_channel_mode(channel, mode) - for_each_nodes(nodes, act=uninstall_node) - - -@app.command(help="Update custom nodes") -def update( - nodes: List[str] = typer.Argument( - ..., - help="[all|List of custom nodes to update]" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - - if 'all' in nodes: - asyncio.run(auto_save_snapshot()) - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - - for x in nodes: - if x.lower() in ['comfyui', 'comfy', 'all']: - update_comfyui() - break - - update_parallel(nodes) - pip_fixer.fix_broken() - - -@app.command(help="Disable custom nodes") -def disable( - nodes: List[str] = typer.Argument( - ..., - help="[all|List of custom nodes to disable]" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - - if 'all' in nodes: - asyncio.run(auto_save_snapshot()) - - for_each_nodes(nodes, disable_node, allow_all=True) - - -@app.command(help="Enable custom nodes") -def enable( - nodes: List[str] = typer.Argument( - ..., - help="[all|List of custom nodes to enable]" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - - if 'all' in nodes: - asyncio.run(auto_save_snapshot()) - - for_each_nodes(nodes, enable_node, allow_all=True) - - -@app.command(help="Fix dependencies of custom nodes") -def fix( - nodes: List[str] = typer.Argument( - ..., - help="[all|List of custom nodes to fix]" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - - if 'all' in nodes: - asyncio.run(auto_save_snapshot()) - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - for_each_nodes(nodes, fix_node, allow_all=True) - pip_fixer.fix_broken() - - -@app.command("show-versions", help="Show all available versions of the node") -def show_versions(node_name: str): - versions = cnr_utils.all_versions_of_node(node_name) - if versions is None: - print(f"Node not found in Comfy Registry: {node_name}") - - for x in versions: - print(f"[{x['createdAt'][:10]}] {x['version']} -- {x['changelog']}") - - -@app.command("show", help="Show node list") -def show( - arg: str = typer.Argument( - help="[installed|enabled|not-installed|disabled|all|cnr|snapshot|snapshot-list]" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - valid_commands = [ - "installed", - "enabled", - "not-installed", - "disabled", - "all", - "cnr", - "snapshot", - "snapshot-list", - ] - if arg not in valid_commands: - typer.echo(f"Invalid command: `show {arg}`", err=True) - exit(1) - - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - if arg == 'snapshot': - show_snapshot() - elif arg == 'snapshot-list': - show_snapshot_list() - else: - show_list(arg) - - -@app.command("simple-show", help="Show node list (simple mode)") -def simple_show( - arg: str = typer.Argument( - help="[installed|enabled|not-installed|disabled|all|snapshot|snapshot-list]" - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - valid_commands = [ - "installed", - "enabled", - "not-installed", - "disabled", - "all", - "snapshot", - "snapshot-list", - ] - if arg not in valid_commands: - typer.echo(f"[bold red]Invalid command: `show {arg}`[/bold red]", err=True) - exit(1) - - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - - if arg == 'snapshot': - show_snapshot(True) - elif arg == 'snapshot-list': - show_snapshot_list(True) - else: - show_list(arg, True) - - -@app.command('cli-only-mode', help="Set whether to use ComfyUI-Manager in CLI-only mode.") -def cli_only_mode( - mode: str = typer.Argument( - ..., help="[enable|disable]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ) -): - cmd_ctx.set_user_directory(user_directory) - cli_mode_flag = os.path.join(cmd_ctx.manager_files_directory, '.enable-cli-only-mode') - - if mode.lower() == 'enable': - with open(cli_mode_flag, 'w'): - pass - print("\nINFO: `cli-only-mode` is enabled\n") - elif mode.lower() == 'disable': - if os.path.exists(cli_mode_flag): - os.remove(cli_mode_flag) - print("\nINFO: `cli-only-mode` is disabled\n") - else: - print(f"\n[bold red]Invalid value for cli-only-mode: {mode}[/bold red]\n") - exit(1) - - -@app.command( - "deps-in-workflow", help="Generate dependencies file from workflow (.json/.png)" -) -def deps_in_workflow( - workflow: Annotated[ - str, typer.Option(show_default=False, help="Workflow file (.json/.png)") - ], - output: Annotated[ - str, typer.Option(show_default=False, help="Output file (.json)") - ], - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ) -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - - input_path = workflow - output_path = output - - if not os.path.exists(input_path): - print(f"[bold red]File not found: {input_path}[/bold red]") - exit(1) - - used_exts, unknown_nodes = asyncio.run(core.extract_nodes_from_workflow(input_path, mode=cmd_ctx.mode, channel_url=cmd_ctx.channel)) - - custom_nodes = {} - for x in used_exts: - custom_nodes[x] = {'state': core.simple_check_custom_node(x), - 'hash': '-' - } - - res = { - 'custom_nodes': custom_nodes, - 'unknown_nodes': list(unknown_nodes) - } - - with open(output_path, "w", encoding='utf-8') as output_file: - json.dump(res, output_file, indent=4) - - print(f"Workflow dependencies are being saved into {output_path}.") - - -@app.command("save-snapshot", help="Save a snapshot of the current ComfyUI environment. If output path isn't provided. Save to ComfyUI-Manager/snapshots path.") -def save_snapshot( - output: Annotated[ - str, - typer.Option( - show_default=False, help="Specify the output file path. (.json/.yaml)" - ), - ] = None, - user_directory: str = typer.Option( - None, - help="user directory" - ), - full_snapshot: Annotated[ - bool, - typer.Option( - show_default=False, help="If the snapshot should include custom node, ComfyUI version and pip versions (default), or only custom node details" - ), - ] = True, -): - cmd_ctx.set_user_directory(user_directory) - - if output is not None: - if(not output.endswith('.json') and not output.endswith('.yaml')): - print("[bold red]ERROR: output path should be either '.json' or '.yaml' file.[/bold red]") - raise typer.Exit(code=1) - - dir_path = os.path.dirname(output) - - if(dir_path != '' and not os.path.exists(dir_path)): - print(f"[bold red]ERROR: {output} path not exists.[/bold red]") - raise typer.Exit(code=1) - - path = asyncio.run(core.save_snapshot_with_postfix('snapshot', output, not full_snapshot)) - print(f"Current snapshot is saved as `{path}`") - - -@app.command("restore-snapshot", help="Restore snapshot from snapshot file") -def restore_snapshot( - snapshot_name: str, - pip_non_url: Optional[bool] = typer.Option( - default=None, - show_default=False, - is_flag=True, - help="Restore for pip packages registered on PyPI.", - ), - pip_non_local_url: Optional[bool] = typer.Option( - default=None, - show_default=False, - is_flag=True, - help="Restore for pip packages registered at web URLs.", - ), - pip_local_url: Optional[bool] = typer.Option( - default=None, - show_default=False, - is_flag=True, - help="Restore for pip packages specified by local paths.", - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), - restore_to: Optional[str] = typer.Option( - None, - help="Manually specify the installation path for the custom node. Ignore user directory." - ) -): - cmd_ctx.set_user_directory(user_directory) - - if restore_to: - cmd_ctx.update_custom_nodes_dir(restore_to) - - extras = [] - if pip_non_url: - extras.append('--pip-non-url') - - if pip_non_local_url: - extras.append('--pip-non-local-url') - - if pip_local_url: - extras.append('--pip-local-url') - - print(f"PIPs restore mode: {extras}") - - if os.path.exists(snapshot_name): - snapshot_path = os.path.abspath(snapshot_name) - else: - snapshot_path = os.path.join(cmd_ctx.get_snapshot_path(), snapshot_name) - if not os.path.exists(snapshot_path): - print(f"[bold red]ERROR: `{snapshot_path}` is not exists.[/bold red]") - exit(1) - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - try: - asyncio.run(core.restore_snapshot(snapshot_path, extras)) - except Exception: - print("[bold red]ERROR: Failed to restore snapshot.[/bold red]") - traceback.print_exc() - raise typer.Exit(code=1) - pip_fixer.fix_broken() - - -@app.command( - "restore-dependencies", help="Restore dependencies from whole installed custom nodes." -) -def restore_dependencies( - user_directory: str = typer.Option( - None, - help="user directory" - ) -): - cmd_ctx.set_user_directory(user_directory) - - node_paths = [] - - for base_path in cmd_ctx.get_custom_nodes_paths(): - for name in os.listdir(base_path): - target = os.path.join(base_path, name) - if os.path.isdir(target) and not name.endswith('.disabled'): - node_paths.append(target) - - total = len(node_paths) - i = 1 - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - for x in node_paths: - print("----------------------------------------------------------------------------------------------------") - print(f"Restoring [{i}/{total}]: {x}") - unified_manager.execute_install_script('', x, instant_execution=True) - i += 1 - pip_fixer.fix_broken() - - -@app.command( - "post-install", help="Install dependencies and execute installation script" -) -def post_install( - path: str = typer.Argument( - help="path to custom node", - ) -): - path = os.path.expanduser(path) - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - unified_manager.execute_install_script('', path, instant_execution=True) - pip_fixer.fix_broken() - - -@app.command( - "install-deps", - help="Install dependencies from dependencies file(.json) or workflow(.png/.json)", -) -def install_deps( - deps: str = typer.Argument( - help="Dependency spec file (.json)", - ), - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - asyncio.run(auto_save_snapshot()) - - if not os.path.exists(deps): - print(f"[bold red]File not found: {deps}[/bold red]") - exit(1) - else: - with open(deps, 'r', encoding="UTF-8", errors="ignore") as json_file: - try: - json_obj = json.load(json_file) - except: - print(f"[bold red]Invalid json file: {deps}[/bold red]") - exit(1) - - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) - for k in json_obj['custom_nodes'].keys(): - state = core.simple_check_custom_node(k) - if state == 'installed': - continue - elif state == 'not-installed': - asyncio.run(core.gitclone_install(k, instant_execution=True)) - else: # disabled - core.gitclone_set_active([k], False) - pip_fixer.fix_broken() - - print("Dependency installation and activation complete.") - - -@app.command(help="Clear reserved startup action in ComfyUI-Manager") -def clear(): - cancel() - - -@app.command("export-custom-node-ids", help="Export custom node ids") -def export_custom_node_ids( - path: str, - channel: Annotated[ - str, - typer.Option( - show_default=False, - help="Specify the operation mode" - ), - ] = None, - mode: str = typer.Option( - None, - help="[remote|local|cache]" - ), - user_directory: str = typer.Option( - None, - help="user directory" - ), -): - cmd_ctx.set_user_directory(user_directory) - cmd_ctx.set_channel_mode(channel, mode) - - with open(path, "w", encoding='utf-8') as output_file: - for x in unified_manager.cnr_map.keys(): - print(x, file=output_file) - - custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=cmd_ctx.channel, mode=cmd_ctx.mode)) - for x in custom_nodes.values(): - if 'cnr_latest' not in x: - if len(x['files']) == 1: - repo_url = x['files'][0] - node_id = repo_url.split('/')[-1] - print(f"{node_id}@unknown", file=output_file) - - if 'id' in x: - print(f"{x['id']}@unknown", file=output_file) - - -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(app()) - - -print("") diff --git a/cm-cli.sh b/cm-cli.sh deleted file mode 100755 index b1a21ca5..00000000 --- a/cm-cli.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -python cm-cli.py $* diff --git a/components/.gitignore b/components/.gitignore deleted file mode 100644 index fab7a5e2..00000000 --- a/components/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.json -*.pack diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 00000000..b50049d4 Binary files /dev/null and b/docs/.DS_Store differ diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index a5972752..00000000 --- a/docs/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# ComfyUI-Manager: Documentation - -This directory contains documentation for the ComfyUI-Manager, providing guides and tutorials for users in multiple languages. - -## Directory Structure - -The documentation is organized into language-specific directories: - -- **en/**: English documentation -- **ko/**: Korean documentation - -## Core Documentation Files - -### Command-Line Interface - -- **cm-cli.md**: Documentation for the ComfyUI-Manager Command Line Interface (CLI), which allows using manager functionality without the UI. - -### Advanced Features - -- **use_aria2.md**: Guide for using the aria2 download accelerator with ComfyUI-Manager for faster model downloads. - -## Documentation Standards - -The documentation follows these standards: - -1. **Markdown Format**: All documentation is written in Markdown for easy rendering on GitHub and other platforms -2. **Language-specific Directories**: Content is separated by language to facilitate localization -3. **Feature-focused Documentation**: Each major feature has its own documentation file -4. **Updated with Releases**: Documentation is kept in sync with software releases - -## Contributing to Documentation - -When contributing new documentation: - -1. Place files in the appropriate language directory -2. Use clear, concise language appropriate for the target audience -3. Include examples where helpful -4. Consider adding screenshots or diagrams for complex features -5. Maintain consistent formatting with existing documentation - -This documentation directory will continue to grow to support the expanding feature set of ComfyUI-Manager. \ No newline at end of file diff --git a/docs/en/cm-cli.md b/docs/en/cm-cli.md deleted file mode 100644 index e4b77a15..00000000 --- a/docs/en/cm-cli.md +++ /dev/null @@ -1,147 +0,0 @@ -# `cm-cli`: ComfyUI-Manager CLI - -`cm-cli` is a tool that allows you to use various functions of ComfyUI-Manager from the command line without launching ComfyUI. - - -``` --= ComfyUI-Manager CLI (V2.24) =- - - -python cm-cli.py [OPTIONS] - -OPTIONS: - [install|reinstall|uninstall|update|disable|enable|fix] node_name ... ?[--channel ] ?[--mode [remote|local|cache]] - [update|disable|enable|fix] all ?[--channel ] ?[--mode [remote|local|cache]] - [simple-show|show] [installed|enabled|not-installed|disabled|all|snapshot|snapshot-list] ?[--channel ] ?[--mode [remote|local|cache]] - save-snapshot ?[--output ] - restore-snapshot ?[--pip-non-url] ?[--pip-non-local-url] ?[--pip-local-url] - cli-only-mode [enable|disable] - restore-dependencies - clear -``` - -## How To Use? -* You can execute it via `python cm-cli.py`. -* For example, if you want to update all custom nodes: - * In the ComfyUI-Manager directory, you can execute the command `python cm-cli.py update all`. - * If running from the ComfyUI directory, you can specify the path to cm-cli.py like this: `python custom_nodes/ComfyUI-Manager/cm-cli.py update all`. - -## Prerequisite -* It must be run in the same Python environment as the one running ComfyUI. - * If using a venv, you must run it with the venv activated. - * If using a portable version, and you are in the directory with the run_nvidia_gpu.bat file, you should execute the command as follows: - `.\python_embeded\python.exe ComfyUI\custom_nodes\ComfyUI-Manager\cm-cli.py update all` -* The path for ComfyUI can be set with the COMFYUI_PATH environment variable. If omitted, a warning message will appear, and the path will be set relative to the installed location of ComfyUI-Manager: - ``` - WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path. - ``` - -## Features - -### 1. --channel, --mode -* For viewing information and managing custom nodes, you can set the information database through --channel and --mode. -* For instance, executing the command `python cm-cli.py update all --channel recent --mode remote` will operate based on the latest information from remote rather than local data embedded in the current ComfyUI-Manager repo and will only target the list in the recent channel. -* --channel, --mode are only available with the commands `simple-show, show, install, uninstall, update, disable, enable, fix`. - -### 2. Viewing Management Information - -`[simple-show|show] [installed|enabled|not-installed|disabled|all|snapshot|snapshot-list] ?[--channel ] ?[--mode [remote|local|cache]]` - -* `[show|simple-show]` - `show` provides detailed information, while `simple-show` displays information more simply. - -Executing a command like `python cm-cli.py show installed` will display detailed information about the installed custom nodes. - -``` --= ComfyUI-Manager CLI (V2.24) =- - -FETCH DATA from: https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json -[ ENABLED ] ComfyUI-Manager (author: Dr.Lt.Data) -[ ENABLED ] ComfyUI-Impact-Pack (author: Dr.Lt.Data) -[ ENABLED ] ComfyUI-Inspire-Pack (author: Dr.Lt.Data) -[ ENABLED ] ComfyUI_experiments (author: comfyanonymous) -[ ENABLED ] ComfyUI-SAI_API (author: Stability-AI) -[ ENABLED ] stability-ComfyUI-nodes (author: Stability-AI) -[ ENABLED ] comfyui_controlnet_aux (author: Fannovel16) -[ ENABLED ] ComfyUI-Frame-Interpolation (author: Fannovel16) -[ DISABLED ] ComfyUI-Loopchain (author: Fannovel16) -``` - -Using a command like `python cm-cli.py simple-show installed` will simply display information about the installed custom nodes. - -``` --= ComfyUI-Manager CLI (V2.24) =- - -FETCH DATA from: https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json -ComfyUI-Manager -ComfyUI-Impact-Pack -ComfyUI-Inspire-Pack -ComfyUI_experiments -ComfyUI-SAI_API -stability-ComfyUI-nodes -comfyui_controlnet_aux -ComfyUI-Frame-Interpolation -ComfyUI-Loopchain -``` - -`[installed|enabled|not-installed|disabled|all|snapshot|snapshot-list]` - * `enabled`, `disabled`: Shows nodes that have been enabled or disabled among the installed custom nodes. - * `installed`: Shows all nodes that have been installed, regardless of whether they are enabled or disabled. - * `not-installed`: Shows a list of custom nodes that have not been installed. - * `all`: Shows a list of all custom nodes. - * `snapshot`: Displays snapshot information of the currently installed custom nodes. When viewed with `show`, it is displayed in JSON format, and with `simple-show`, it is displayed simply, along with the commit hash. - * `snapshot-list`: Shows a list of snapshot files stored in ComfyUI-Manager/snapshots. - -### 3. Managing Custom Nodes - -`[install|reinstall|uninstall|update|disable|enable|fix] node_name ... ?[--channel ] ?[--mode [remote|local|cache]]` - -* You can apply management functions by listing the names of custom nodes, such as `python cm-cli.py install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack ComfyUI_experiments`. -* The names of the custom nodes are as shown by `show` and are the names of the git repositories. -(Plans are to update the use of nicknames in the future.) - -`[update|disable|enable|fix] all ?[--channel ] ?[--mode [remote|local|cache]]` - -* The `update, disable, enable, fix` functions can be specified for all. - -* Detailed Operations - * `install`: Installs the specified custom nodes. - * `reinstall`: Removes and then reinstalls the specified custom nodes. - * `uninstall`: Uninstalls the specified custom nodes. - * `update`: Updates the specified custom nodes. - * `disable`: Disables the specified custom nodes. - * `enable`: Enables the specified custom nodes. - * `fix`: Attempts to fix dependencies for the specified custom nodes. - - -### 4. Snapshot Management -* `python cm-cli.py save-snapshot [--output ]`: Saves the current snapshot. - * With `--output`, you can save a file in .yaml format to any specified path. -* `python cm-cli.py restore-snapshot `: Restores to the specified snapshot. - * If a file exists at the snapshot path, that snapshot is loaded. - * If no file exists at the snapshot path, it is implicitly assumed to be in ComfyUI-Manager/snapshots. - * `--pip-non-url`: Restore for pip packages registered on PyPI. - * `--pip-non-local-url`: Restore for pip packages registered at web URLs. - * `--pip-local-url`: Restore for pip packages specified by local paths. - * `--user-directory`: Set the user directory. - * `--restore-to`: The path where the restored custom nodes will be installed. (When this option is applied, only the custom nodes installed in the target path are recognized as installed.) - -### 5. CLI Only Mode - -You can set whether to use ComfyUI-Manager solely via CLI. - -`cli-only-mode [enable|disable]` - -* This mode can be used if you want to restrict the use of ComfyUI-Manager through the GUI for security or policy reasons. - * When CLI only mode is enabled, ComfyUI-Manager is loaded in a very restricted state, the internal web API is disabled, and the Manager button is not displayed in the main menu. - -### 6. Dependency Restoration - -`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 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. diff --git a/docs/en/use_aria2.md b/docs/en/use_aria2.md deleted file mode 100644 index 10a7c6dd..00000000 --- a/docs/en/use_aria2.md +++ /dev/null @@ -1,40 +0,0 @@ -# Use `aria2` as downloader - -Two environment variables are needed to use `aria2` as the downloader. - -```bash -export COMFYUI_MANAGER_ARIA2_SERVER=http://127.0.0.1:6800 -export COMFYUI_MANAGER_ARIA2_SECRET=__YOU_MUST_CHANGE_IT__ -``` - -An example `docker-compose.yml` - -```yaml -services: - - aria2: - container_name: aria2 - image: p3terx/aria2-pro - environment: - - PUID=1000 - - PGID=1000 - - UMASK_SET=022 - - RPC_SECRET=__YOU_MUST_CHANGE_IT__ - - RPC_PORT=5080 - - DISK_CACHE=64M - - IPV6_MODE=false - - UPDATE_TRACKERS=false - - CUSTOM_TRACKER_URL= - volumes: - - ./config:/config - - ./downloads:/downloads - - ~/ComfyUI/models:/models - - ~/ComfyUI/custom_nodes:/custom_nodes - ports: - - 6800:6800 - restart: unless-stopped - logging: - driver: json-file - options: - max-size: 1m -``` diff --git a/docs/en/v3.38-userdata-security-migration.md b/docs/en/v3.38-userdata-security-migration.md deleted file mode 100644 index 324321d7..00000000 --- a/docs/en/v3.38-userdata-security-migration.md +++ /dev/null @@ -1,230 +0,0 @@ -# ComfyUI-Manager V3.38: Userdata Security Migration Guide - -## Introduction - -ComfyUI-Manager V3.38 introduces a **security patch** that migrates Manager's configuration and data to a protected system path. This change leverages ComfyUI's new System User Protection API (PR #10966) to provide enhanced security isolation. - -This guide explains what happens during the migration and how to handle various situations. - ---- - -## What Changed - -### Finding Your Paths - -When ComfyUI starts, it displays the full paths in the terminal: - -``` -** User directory: /path/to/ComfyUI/user -** ComfyUI-Manager config path: /path/to/ComfyUI/user/__manager/config.ini -``` - -Look for these lines in your startup log to find the exact location on your system. In this guide, paths are shown relative to the `user` directory. - -### Path Migration - -| Data | Legacy Path | New Path | -|------|-------------|----------| -| Configuration | `user/default/ComfyUI-Manager/` | `user/__manager/` | -| Snapshots | `user/default/ComfyUI-Manager/snapshots/` | `user/__manager/snapshots/` | - -### Why This Change - -In older ComfyUI versions, the `default/` directory was **unprotected** and accessible via web APIs. If you ran ComfyUI with `--listen 0.0.0.0` or similar options to allow external connections, this data **may have been tampered with** by malicious actors. - -**Note:** If you only used ComfyUI locally (without `--listen` or with `--listen 127.0.0.1`), your data was not exposed to this vulnerability. - -The new `__manager` path uses ComfyUI's protected system directory, which: -- **Cannot be accessed** from outside (protected by ComfyUI) -- Isolates system settings from user data -- Enables stricter security for remote access - -**This is why only `config.ini` is automatically migrated** - other files (snapshots) may have been compromised and should be manually verified before copying. - ---- - -## Automatic Migration - -When you start ComfyUI with the new System User Protection API, Manager automatically handles the migration: - -### Step 1: Configuration Migration - -Only `config.ini` is migrated automatically. - -**Important**: Snapshots are **NOT** automatically migrated. You must copy them manually if needed. - -### Step 2: Security Level Check - -During migration, if your security level is below `normal` (i.e., `weak` or `normal-`), it will be automatically raised to `normal`. This is a safety measure because the security level setting itself may have been tampered with in the old version. - -``` -====================================================================== -[ComfyUI-Manager] WARNING: Security level adjusted - - Previous: 'weak' → New: 'normal' - - Raised to prevent unauthorized remote access. -====================================================================== -``` - -If you need a lower security level, you can manually edit the config after migration. - -### Step 3: Legacy Backup - -Your entire legacy directory is moved to a backup location: -``` -user/__manager/.legacy-manager-backup/ -``` - -This backup is preserved until you manually delete it. - ---- - -## Persistent Backup Notification - -As long as the backup exists, Manager will remind you on **every startup**: - -``` ----------------------------------------------------------------------- -[ComfyUI-Manager] NOTICE: Legacy backup exists - - Your old Manager data was backed up to: - /path/to/ComfyUI/user/__manager/.legacy-manager-backup - - Please verify and remove it when no longer needed. ----------------------------------------------------------------------- -``` - -**To stop this notification**: Delete the `.legacy-manager-backup` folder inside `user/__manager/` after confirming you don't need any data from it. - ---- - -## Recovering Old Data - -### Snapshots - -If you need your old snapshots, copy the contents of `.legacy-manager-backup/snapshots/` to `user/__manager/snapshots/`. - ---- - -## Outdated ComfyUI Warning - -If you're running an older version of ComfyUI without the System User Protection API, Manager will: - -1. **Force security level to `strong`** - All installations are blocked -2. **Display warning message**: - -``` -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -[ComfyUI-Manager] ERROR: ComfyUI version is outdated! - - Most operations are blocked for security. - - ComfyUI update is still allowed. - - Please update ComfyUI to use Manager normally. -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -``` - -**Solution**: Update ComfyUI to v0.3.76 or later. - ---- - -## Security Levels - -| Level | What's Allowed | -|-------|----------------| -| `strong` | ComfyUI update only. All other installations blocked. | -| `normal` | Install/update/remove registered custom nodes and models. | -| `normal-` | Above + Install via Git URL or pip (localhost only). | -| `weak` | All operations allowed, including from remote connections. | - -**Notes:** -- `strong` is forced on outdated ComfyUI versions. -- `normal` is the default and recommended for most users. -- `normal-` is for developers who need to install unregistered nodes locally. -- `weak` should only be used in isolated development environments. - -### Changing Security Level - -Edit `user/__manager/config.ini`: -```ini -[default] -security_level = normal -``` - ---- - -## Error Messages - -### "comfyui_outdated" (HTTP 403) - -This error appears when: -- Your ComfyUI doesn't have the System User Protection API -- All installations are blocked until you update ComfyUI - -**Solution**: Update ComfyUI to the latest version. - -### "security_level" (HTTP 403) - -This error appears when: -- Your security level blocks the requested operation -- For example, `strong` level blocks all installations - -**Solution**: Lower your security level in config.ini if appropriate for your use case. - ---- - -## Security Warning: Suspicious Path - -If you see this error on an **older** ComfyUI: - -``` -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -[ComfyUI-Manager] ERROR: Suspicious path detected! - - '__manager' exists with low security level: 'weak' - - Please verify manually: - /path/to/ComfyUI/user/__manager/config.ini -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -``` - -On older ComfyUI versions, the `__manager` directory is not normally created. If this directory exists, it may have been created externally. For safety, manually verify the contents of this directory before updating ComfyUI. - ---- - -## Troubleshooting - -### All my installations are blocked - -**Check 1**: Is your ComfyUI updated? -- Old ComfyUI forces `security_level = strong` -- Update ComfyUI to resolve - -**Check 2**: What's your security level? -- Check `user/__manager/config.ini` -- `security_level = strong` blocks all installations - -### My snapshots are missing - -Snapshots are not automatically migrated. You need to manually copy the `snapshots` folder from inside `.legacy-manager-backup` to the `user/__manager/` directory. - -### I keep seeing the backup notification - -Delete the `.legacy-manager-backup` folder inside `user/__manager/` after confirming you don't need any data from it. - -### Snapshot restore is blocked - -On old ComfyUI (without System User API), snapshot restore is blocked because security is forced to `strong`. Update ComfyUI to enable snapshot restore. - ---- - -## File Structure Reference - -``` -user/ -└── __manager/ - ├── config.ini # Manager configuration - ├── channels.list # Custom node channels - ├── snapshots/ # Environment snapshots - └── .legacy-manager-backup/ # Backup of old Manager data (temporary) -``` - ---- - -## Requirements - -- **ComfyUI**: v0.3.76 or later (with System User Protection API) -- **ComfyUI-Manager**: V3.38 or later diff --git a/docs/ko/cm-cli.md b/docs/ko/cm-cli.md deleted file mode 100644 index 0be8e89d..00000000 --- a/docs/ko/cm-cli.md +++ /dev/null @@ -1,150 +0,0 @@ -# `cm-cli`: ComfyUI-Manager CLI - -`cm-cli` 는 ComfyUI를 실행시키지 않고 command line에서 ComfyUI-Manager의 여러가지 기능을 사용할 수 있도록 도와주는 도구입니다. - - -``` --= ComfyUI-Manager CLI (V2.24) =- - - -python cm-cli.py [OPTIONS] - -OPTIONS: - [install|reinstall|uninstall|update|disable|enable|fix] node_name ... ?[--channel ] ?[--mode [remote|local|cache]] - [update|disable|enable|fix] all ?[--channel ] ?[--mode [remote|local|cache]] - [simple-show|show] [installed|enabled|not-installed|disabled|all|snapshot|snapshot-list] ?[--channel ] ?[--mode [remote|local|cache]] - save-snapshot ?[--output ] - restore-snapshot ?[--pip-non-url] ?[--pip-non-local-url] ?[--pip-local-url] - cli-only-mode [enable|disable] - restore-dependencies - clear -``` - -## How To Use? -* `python cm-cli.py` 를 통해서 실행 시킬 수 있습니다. -* 예를 들어 custom node를 모두 업데이트 하고 싶다면 - * 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 파일이 있는 경로인 경우, 다음과 같은 방식으로 명령을 실행해야 합니다. - `.\python_embeded\python.exe ComfyUI\custom_nodes\ComfyUI-Manager\cm-cli.py update all` -* ComfyUI 의 경로는 COMFYUI_PATH 환경 변수로 설정할 수 있습니다. 만약 생략할 경우 다음과 같은 경고 메시지가 나타나며, ComfyUI-Manager가 설치된 경로를 기준으로 상대 경로로 설정됩니다. - ``` - WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path. - ``` - -## Features - -### 1. --channel, --mode -* 정보 보기 기능과 커스텀 노드 관리 기능의 경우는 --channel과 --mode를 통해 정보 DB를 설정할 수 있습니다. -* 예를 들어 `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. 관리 정보 보기 - -`[simple-show|show] [installed|enabled|not-installed|disabled|all|snapshot|snapshot-list] ?[--channel ] ?[--mode [remote|local|cache]]` - - -* `[show|simple-show]` - `show`는 상세하게 정보를 보여주며, `simple-show`는 간단하게 정보를 보여줍니다. - - -`python cm-cli.py show installed` 와 같은 명령을 실행하면 설치된 커스텀 노드의 정보를 상세하게 보여줍니다. -``` --= ComfyUI-Manager CLI (V2.24) =- - -FETCH DATA from: https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json -[ ENABLED ] ComfyUI-Manager (author: Dr.Lt.Data) -[ ENABLED ] ComfyUI-Impact-Pack (author: Dr.Lt.Data) -[ ENABLED ] ComfyUI-Inspire-Pack (author: Dr.Lt.Data) -[ ENABLED ] ComfyUI_experiments (author: comfyanonymous) -[ ENABLED ] ComfyUI-SAI_API (author: Stability-AI) -[ ENABLED ] stability-ComfyUI-nodes (author: Stability-AI) -[ ENABLED ] comfyui_controlnet_aux (author: Fannovel16) -[ ENABLED ] ComfyUI-Frame-Interpolation (author: Fannovel16) -[ DISABLED ] ComfyUI-Loopchain (author: Fannovel16) -``` - -`python cm-cli.py simple-show installed` 와 같은 명령을 이용해서 설치된 커스텀 노드의 정보를 간단하게 보여줍니다. - -``` --= ComfyUI-Manager CLI (V2.24) =- - -FETCH DATA from: https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json -ComfyUI-Manager -ComfyUI-Impact-Pack -ComfyUI-Inspire-Pack -ComfyUI_experiments -ComfyUI-SAI_API -stability-ComfyUI-nodes -comfyui_controlnet_aux -ComfyUI-Frame-Interpolation -ComfyUI-Loopchain -``` - -* `[installed|enabled|not-installed|disabled|all|snapshot|snapshot-list]` - * `enabled`, `disabled`: 설치된 커스텀 노드들 중 enable 되었거나, disable된 노드들을 보여줍니다. - * `installed`: enable, disable 여부와 상관없이 설치된 모든 노드를 보여줍니다 - * `not-installed`: 설치되지 않은 커스텀 노드의 목록을 보여줍니다. - * `all`: 모든 커스텀 노드의 목록을 보여줍니다. - * `snapshot`: 현재 설치된 커스텀 노드의 snapshot 정보를 보여줍니다. `show`를 통해서 볼 경우는 json 출력 형태로 보여주며, `simple-show`를 통해서 볼 경우는 간단하게, 커밋 해시와 함께 보여줍니다. - * `snapshot-list`: ComfyUI-Manager/snapshots 에 저장된 snapshot 파일의 목록을 보여줍니다. - -### 3. 커스텀 노드 관리 하기 - -`[install|reinstall|uninstall|update|disable|enable|fix] node_name ... ?[--channel ] ?[--mode [remote|local|cache]]` - -* `python cm-cli.py install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack ComfyUI_experiments` 와 같이 커스텀 노드의 이름을 나열해서 관리 기능을 적용할 수 있습니다. -* 커스텀 노드의 이름은 `show`를 했을 때 보여주는 이름이며, git repository의 이름입니다. -(추후 nickname을 사용 가능하도록 업데이트할 예정입니다.) - -`[update|disable|enable|fix] all ?[--channel ] ?[--mode [remote|local|cache]]` - -* `update, disable, enable, fix` 기능은 all 로 지정 가능합니다. - -* 세부 동작 - * `install`: 지정된 커스텀 노드들을 설치합니다 - * `reinstall`: 지정된 커스텀 노드를 삭제하고 재설치 합니다. - * `uninstall`: 지정된 커스텀 노드들을 삭제합니다. - * `update`: 지정된 커스텀 노드들을 업데이트합니다. - * `disable`: 지정된 커스텀 노드들을 비활성화합니다. - * `enable`: 지정된 커스텀 노드들을 활성화합니다. - * `fix`: 지정된 커스텀 노드의 의존성을 고치기 위한 시도를 합니다. - - -### 4. 스냅샷 관리 기능 -* `python cm-cli.py save-snapshot ?[--output ]`: 현재의 snapshot을 저장합니다. - * --output 으로 임의의 경로에 .yaml 파일과 format으로 저장할 수 있습니다. -* `python cm-cli.py restore-snapshot `: 지정된 snapshot으로 복구합니다. - * snapshot 경로에 파일이 존재하는 경우 해당 snapshot을 로드합니다. - * snapshot 경로에 파일이 존재하지 않는 경우 묵시적으로, ComfyUI-Manager/snapshots 에 있다고 가정합니다. - * `--pip-non-url`: PyPI 에 등록된 pip 패키지들에 대해서 복구를 수행 - * `--pip-non-local-url`: web URL에 등록된 pip 패키지들에 대해서 복구를 수행 - * `--pip-local-url`: local 경로를 지정하고 있는 pip 패키지들에 대해서 복구를 수행 - * `--user-directory`: 사용자 디렉토리 설정 - * `--restore-to`: 복구될 커스텀 노드가 설치될 경로. (이 옵션을 적용할 경우 오직 대상 경로에 설치된 custom nodes만 설치된 것으로 인식함.) - -### 5. CLI only mode - -ComfyUI-Manager를 CLI로만 사용할 것인지를 설정할 수 있습니다. - -`cli-only-mode [enable|disable]` - -* security 혹은 policy 의 이유로 GUI 를 통한 ComfyUI-Manager 사용을 제한하고 싶은 경우 이 모드를 사용할 수 있습니다. - * CLI only mode를 적용할 경우 ComfyUI-Manager 가 매우 제한된 상태로 로드되어, 내부적으로 제공하는 web API가 비활성화되며, 메인 메뉴에서도 Manager 버튼이 표시되지 않습니다. - - -### 6. 의존성 설치 - -`restore-dependencies` - -* `ComfyUI/custom_nodes` 하위 경로에 커스텀 노드들이 설치되어 있긴 하지만, 의존성이 설치되지 않은 경우 사용할 수 있습니다. -* Colab과 같이 cloud instance를 새로 시작하는 경우 의존성 재설치 및 설치 스크립트가 재실행되어야 하는 경우 사용합니다. -* ComfyUI를 재설치할 경우, custom_nodes 경로만 백업했다가 재설치할 경우 활용 가능합니다. - - -### 7. clear - -GUI에서 install, update를 하거나 snapshot을 restore하는 경우 예약을 통해서 다음번 ComfyUI를 실행할 경우 실행되는 구조입니다. `clear` 는 이런 예약 상태를 clear해서, 아무런 사전 실행이 적용되지 않도록 합니다. diff --git a/extras.json b/extras.json deleted file mode 100644 index 22949306..00000000 --- a/extras.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "favorites": [ - "comfyui_ipadapter_plus", - "comfyui-animatediff-evolved", - "comfyui_controlnet_aux", - "comfyui-impact-pack", - "comfyui-impact-subpack", - "comfyui-custom-scripts", - "comfyui-layerdiffuse", - "comfyui-liveportraitkj", - "aigodlike-comfyui-translation", - "comfyui-reactor", - "comfyui_instantid", - "sd-dynamic-thresholding", - "pr-was-node-suite-comfyui-47064894", - "comfyui-advancedliveportrait", - "comfyui_layerstyle", - "efficiency-nodes-comfyui", - "comfyui-crystools", - "comfyui-advanced-controlnet", - "comfyui-videohelpersuite", - "comfyui-kjnodes", - "comfy-mtb", - "comfyui_essentials" - ] -} \ No newline at end of file diff --git a/git_helper.py b/git_helper.py deleted file mode 100644 index 4e184a3b..00000000 --- a/git_helper.py +++ /dev/null @@ -1,531 +0,0 @@ -import subprocess -import sys -import os -import traceback -import time - -import git -import json -import yaml -import requests -from tqdm.auto import tqdm -from git.remote import RemoteProgress - - -comfy_path = os.environ.get('COMFYUI_PATH') -git_exe_path = os.environ.get('GIT_EXE_PATH') - -if comfy_path is None: - print("\nWARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.", file=sys.stderr) - comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) - - -def download_url(url, dest_folder, filename=None): - # Ensure the destination folder exists - if not os.path.exists(dest_folder): - os.makedirs(dest_folder) - - # Extract filename from URL if not provided - if filename is None: - filename = os.path.basename(url) - - # Full path to save the file - dest_path = os.path.join(dest_folder, filename) - - # Download the file - response = requests.get(url, stream=True) - if response.status_code == 200: - with open(dest_path, 'wb') as file: - for chunk in response.iter_content(chunk_size=1024): - if chunk: - file.write(chunk) - else: - print(f"Failed to download file from {url}") - - -nodelist_path = os.path.join(os.path.dirname(__file__), "custom-node-list.json") -working_directory = os.getcwd() - -if os.path.basename(working_directory) != 'custom_nodes': - print("WARN: This script should be executed in custom_nodes dir") - print(f"DBG: INFO {working_directory}") - print(f"DBG: INFO {sys.argv}") - # exit(-1) - - -class GitProgress(RemoteProgress): - def __init__(self): - super().__init__() - self.pbar = tqdm(ascii=True) - - def update(self, op_code, cur_count, max_count=None, message=''): - self.pbar.total = max_count - self.pbar.n = cur_count - self.pbar.pos = 0 - self.pbar.refresh() - - -def gitclone(custom_nodes_path, url, target_hash=None, repo_path=None): - repo_name = os.path.splitext(os.path.basename(url))[0] - - if repo_path is None: - repo_path = os.path.join(custom_nodes_path, repo_name) - - # Clone the repository from the remote URL - repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=GitProgress()) - - if target_hash is not None: - print(f"CHECKOUT: {repo_name} [{target_hash}]") - repo.git.checkout(target_hash) - - repo.git.clear_cache() - repo.close() - - -def gitcheck(path, do_fetch=False): - try: - # Fetch the latest commits from the remote repository - repo = git.Repo(path) - - if repo.head.is_detached: - print("CUSTOM NODE CHECK: True") - return - - current_branch = repo.active_branch - branch_name = current_branch.name - - remote_name = current_branch.tracking_branch().remote_name - remote = repo.remote(name=remote_name) - - if do_fetch: - remote.fetch() - - # Get the current commit hash and the commit hash of the remote branch - commit_hash = repo.head.commit.hexsha - - if f'{remote_name}/{branch_name}' in repo.refs: - remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha - else: - print("CUSTOM NODE CHECK: True") # non default branch is treated as updatable - return - - # Compare the commit hashes to determine if the local repository is behind the remote repository - if commit_hash != remote_commit_hash: - # Get the commit dates - commit_date = repo.head.commit.committed_datetime - remote_commit_date = repo.refs[f'{remote_name}/{branch_name}'].object.committed_datetime - - # Compare the commit dates to determine if the local repository is behind the remote repository - if commit_date < remote_commit_date: - print("CUSTOM NODE CHECK: True") - else: - print("CUSTOM NODE CHECK: False") - except Exception as e: - print(e) - print("CUSTOM NODE CHECK: Error") - - -def get_remote_name(repo): - available_remotes = [remote.name for remote in repo.remotes] - if 'origin' in available_remotes: - return 'origin' - elif 'upstream' in available_remotes: - return 'upstream' - elif len(available_remotes) > 0: - return available_remotes[0] - - if not available_remotes: - print(f"[ComfyUI-Manager] No remotes are configured for this repository: {repo.working_dir}") - else: - print(f"[ComfyUI-Manager] Available remotes in '{repo.working_dir}': ") - for remote in available_remotes: - print(f"- {remote}") - - return None - - -def switch_to_default_branch(repo): - remote_name = get_remote_name(repo) - - try: - if remote_name is None: - return False - - default_branch = repo.git.symbolic_ref(f'refs/remotes/{remote_name}/HEAD').replace(f'refs/remotes/{remote_name}/', '') - repo.git.checkout(default_branch) - return True - except: - # try checkout master - # try checkout main if failed - try: - repo.git.checkout(repo.heads.master) - return True - except: - try: - if remote_name is not None: - repo.git.checkout('-b', 'master', f'{remote_name}/master') - return True - except: - try: - repo.git.checkout(repo.heads.main) - return True - except: - try: - if remote_name is not None: - repo.git.checkout('-b', 'main', f'{remote_name}/main') - return True - except: - pass - - print("[ComfyUI Manager] Failed to switch to the default branch") - return False - - -def gitpull(path): - # Check if the path is a git repository - if not os.path.exists(os.path.join(path, '.git')): - raise ValueError('Not a git repository') - - # Pull the latest changes from the remote repository - repo = git.Repo(path) - if repo.is_dirty(): - print(f"STASH: '{path}' is dirty.") - repo.git.stash() - - commit_hash = repo.head.commit.hexsha - try: - if repo.head.is_detached: - switch_to_default_branch(repo) - - current_branch = repo.active_branch - branch_name = current_branch.name - - remote_name = current_branch.tracking_branch().remote_name - remote = repo.remote(name=remote_name) - - if f'{remote_name}/{branch_name}' not in repo.refs: - switch_to_default_branch(repo) - current_branch = repo.active_branch - branch_name = current_branch.name - - remote.fetch() - if f'{remote_name}/{branch_name}' in repo.refs: - remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha - else: - print("CUSTOM NODE PULL: Fail") # update fail - return - - if commit_hash == remote_commit_hash: - print("CUSTOM NODE PULL: None") # there is no update - repo.close() - return - - try: - repo.git.pull('--ff-only') - except git.GitCommandError: - backup_name = f'backup_{time.strftime("%Y%m%d_%H%M%S")}' - repo.create_head(backup_name) - print(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}") - repo.git.reset('--hard', f'{remote_name}/{branch_name}') - print(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}") - - repo.git.submodule('update', '--init', '--recursive') - new_commit_hash = repo.head.commit.hexsha - - if commit_hash != new_commit_hash: - print("CUSTOM NODE PULL: Success") # update success - else: - print("CUSTOM NODE PULL: Fail") # update fail - except Exception as e: - print(e) - print("CUSTOM NODE PULL: Fail") # unknown git error - - repo.close() - - -def checkout_comfyui_hash(target_hash): - repo = git.Repo(comfy_path) - commit_hash = repo.head.commit.hexsha - - if commit_hash != target_hash: - try: - print(f"CHECKOUT: ComfyUI [{target_hash}]") - repo.git.checkout(target_hash) - except git.GitCommandError as e: - print(f"Error checking out the ComfyUI: {str(e)}") - - -def checkout_custom_node_hash(git_custom_node_infos): - repo_name_to_url = {} - - for url in git_custom_node_infos.keys(): - repo_name = url.split('/')[-1] - - if repo_name.endswith('.git'): - repo_name = repo_name[:-4] - - repo_name_to_url[repo_name] = url - - for path in os.listdir(working_directory): - if path.endswith("ComfyUI-Manager"): - continue - - fullpath = os.path.join(working_directory, path) - - if os.path.isdir(fullpath): - is_disabled = path.endswith(".disabled") - - try: - git_dir = os.path.join(fullpath, '.git') - if not os.path.exists(git_dir): - continue - - need_checkout = False - repo_name = os.path.basename(fullpath) - - if repo_name.endswith('.disabled'): - repo_name = repo_name[:-9] - - if repo_name not in repo_name_to_url: - if not is_disabled: - # should be disabled - print(f"DISABLE: {repo_name}") - new_path = fullpath + ".disabled" - os.rename(fullpath, new_path) - need_checkout = False - else: - item = git_custom_node_infos[repo_name_to_url[repo_name]] - if item['disabled'] and is_disabled: - pass - elif item['disabled'] and not is_disabled: - # disable - print(f"DISABLE: {repo_name}") - new_path = fullpath + ".disabled" - os.rename(fullpath, new_path) - - elif not item['disabled'] and is_disabled: - # enable - print(f"ENABLE: {repo_name}") - new_path = fullpath[:-9] - os.rename(fullpath, new_path) - fullpath = new_path - need_checkout = True - else: - need_checkout = True - - if need_checkout: - repo = git.Repo(fullpath) - commit_hash = repo.head.commit.hexsha - - if commit_hash != item['hash']: - print(f"CHECKOUT: {repo_name} [{item['hash']}]") - repo.git.checkout(item['hash']) - - except Exception: - print(f"Failed to restore snapshots for the custom node '{path}'") - - # clone missing - for k, v in git_custom_node_infos.items(): - if 'ComfyUI-Manager' in k: - continue - - if not v['disabled']: - repo_name = k.split('/')[-1] - if repo_name.endswith('.git'): - repo_name = repo_name[:-4] - - path = os.path.join(working_directory, repo_name) - if not os.path.exists(path): - print(f"CLONE: {path}") - gitclone(working_directory, k, target_hash=v['hash']) - - -def invalidate_custom_node_file(file_custom_node_infos): - global nodelist_path - - enabled_set = set() - for item in file_custom_node_infos: - if not item['disabled']: - enabled_set.add(item['filename']) - - for path in os.listdir(working_directory): - fullpath = os.path.join(working_directory, path) - - if not os.path.isdir(fullpath) and fullpath.endswith('.py'): - if path not in enabled_set: - print(f"DISABLE: {path}") - new_path = fullpath+'.disabled' - os.rename(fullpath, new_path) - - elif not os.path.isdir(fullpath) and fullpath.endswith('.py.disabled'): - path = path[:-9] - if path in enabled_set: - print(f"ENABLE: {path}") - new_path = fullpath[:-9] - os.rename(fullpath, new_path) - - # download missing: just support for 'copy' style - py_to_url = {} - - with open(nodelist_path, 'r', encoding="UTF-8") as json_file: - info = json.load(json_file) - for item in info['custom_nodes']: - if item['install_type'] == 'copy': - for url in item['files']: - if url.endswith('.py'): - py = url.split('/')[-1] - py_to_url[py] = url - - for item in file_custom_node_infos: - filename = item['filename'] - if not item['disabled']: - target_path = os.path.join(working_directory, filename) - - if not os.path.exists(target_path) and filename in py_to_url: - url = py_to_url[filename] - print(f"DOWNLOAD: {filename}") - download_url(url, working_directory) - - -def apply_snapshot(path): - try: - if os.path.exists(path): - if not path.endswith('.json') and not path.endswith('.yaml'): - print(f"Snapshot file not found: `{path}`") - print("APPLY SNAPSHOT: False") - return None - - with open(path, 'r', encoding="UTF-8") as snapshot_file: - if path.endswith('.json'): - info = json.load(snapshot_file) - elif path.endswith('.yaml'): - info = yaml.load(snapshot_file, Loader=yaml.SafeLoader) - info = info['custom_nodes'] - else: - # impossible case - print("APPLY SNAPSHOT: False") - return None - - comfyui_hash = info['comfyui'] - git_custom_node_infos = info['git_custom_nodes'] - file_custom_node_infos = info['file_custom_nodes'] - - if comfyui_hash: - checkout_comfyui_hash(comfyui_hash) - checkout_custom_node_hash(git_custom_node_infos) - invalidate_custom_node_file(file_custom_node_infos) - - print("APPLY SNAPSHOT: True") - if 'pips' in info and info['pips']: - return info['pips'] - else: - return None - - print(f"Snapshot file not found: `{path}`") - print("APPLY SNAPSHOT: False") - - return None - except Exception as e: - print(e) - traceback.print_exc() - print("APPLY SNAPSHOT: False") - - return None - - -def restore_pip_snapshot(pips, options): - non_url = [] - local_url = [] - non_local_url = [] - for k, v in pips.items(): - if v == "": - non_url.append(k) - else: - if v.startswith('file:'): - local_url.append(v) - else: - non_local_url.append(v) - - failed = [] - if '--pip-non-url' in options: - # try all at once - res = 1 - try: - res = subprocess.check_call([sys.executable, '-m', 'pip', 'install'] + non_url) - except: - pass - - # fallback - if res != 0: - for x in non_url: - res = 1 - try: - res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x]) - except: - pass - - if res != 0: - failed.append(x) - - if '--pip-non-local-url' in options: - for x in non_local_url: - res = 1 - try: - res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x]) - except: - pass - - if res != 0: - failed.append(x) - - if '--pip-local-url' in options: - for x in local_url: - res = 1 - try: - res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x]) - except: - pass - - if res != 0: - failed.append(x) - - print(f"Installation failed for pip packages: {failed}") - - -def setup_environment(): - if git_exe_path is not None: - git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=git_exe_path) - - -setup_environment() - - -try: - if sys.argv[1] == "--clone": - repo_path = None - if len(sys.argv) > 4: - repo_path = sys.argv[4] - - gitclone(sys.argv[2], sys.argv[3], repo_path=repo_path) - elif sys.argv[1] == "--check": - gitcheck(sys.argv[2], False) - elif sys.argv[1] == "--fetch": - gitcheck(sys.argv[2], True) - elif sys.argv[1] == "--pull": - gitpull(sys.argv[2]) - elif sys.argv[1] == "--apply-snapshot": - options = set() - for x in sys.argv: - if x in ['--pip-non-url', '--pip-local-url', '--pip-non-local-url']: - options.add(x) - - pips = apply_snapshot(sys.argv[2]) - - if pips and len(options) > 0: - restore_pip_snapshot(pips, options) - sys.exit(0) -except Exception as e: - print(e) - sys.exit(-1) - - diff --git a/glob/README.md b/glob/README.md deleted file mode 100644 index 375b7fa7..00000000 --- a/glob/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# ComfyUI-Manager: Core Backend (glob) - -This directory contains the Python backend modules that power ComfyUI-Manager, handling the core functionality of node management, downloading, security, and server operations. - -## Core Modules - -- **manager_core.py**: The central implementation of management functions, handling configuration, installation, updates, and node management. -- **manager_server.py**: Implements server functionality and API endpoints for the web interface to interact with the backend. -- **manager_downloader.py**: Handles downloading operations for models, extensions, and other resources. -- **manager_util.py**: Provides utility functions used throughout the system. - -## Specialized Modules - -- **cm_global.py**: Maintains global variables and state management across the system. -- **cnr_utils.py**: Helper utilities for interacting with the custom node registry (CNR). -- **git_utils.py**: Git-specific utilities for repository operations. -- **node_package.py**: Handles the packaging and installation of node extensions. -- **security_check.py**: Implements the multi-level security system for installation safety. -- **share_3rdparty.py**: Manages integration with third-party sharing platforms. - -## Architecture - -The backend follows a modular design pattern with clear separation of concerns: - -1. **Core Layer**: Manager modules provide the primary API and business logic -2. **Utility Layer**: Helper modules provide specialized functionality -3. **Integration Layer**: Modules that connect to external systems - -## Security Model - -The system implements a comprehensive security framework with multiple levels: - -- **Block**: Highest security - blocks most remote operations -- **High**: Allows only specific trusted operations -- **Middle**: Standard security for most users -- **Normal-**: More permissive for advanced users -- **Weak**: Lowest security for development environments - -## Implementation Details - -- The backend is designed to work seamlessly with ComfyUI -- Asynchronous task queuing is implemented for background operations -- The system supports multiple installation modes -- Error handling and risk assessment are integrated throughout the codebase - -## API Integration - -The backend exposes a REST API via `manager_server.py` that enables: -- Custom node management (install, update, disable, remove) -- Model downloading and organization -- System configuration -- Snapshot management -- Workflow component handling \ No newline at end of file diff --git a/glob/__pycache__/cm_global.cpython-312.pyc b/glob/__pycache__/cm_global.cpython-312.pyc new file mode 100644 index 00000000..1663517e Binary files /dev/null and b/glob/__pycache__/cm_global.cpython-312.pyc differ diff --git a/glob/__pycache__/cnr_utils.cpython-312.pyc b/glob/__pycache__/cnr_utils.cpython-312.pyc new file mode 100644 index 00000000..060aadd6 Binary files /dev/null and b/glob/__pycache__/cnr_utils.cpython-312.pyc differ diff --git a/glob/__pycache__/git_utils.cpython-312.pyc b/glob/__pycache__/git_utils.cpython-312.pyc new file mode 100644 index 00000000..377c2bac Binary files /dev/null and b/glob/__pycache__/git_utils.cpython-312.pyc differ diff --git a/glob/__pycache__/manager_core.cpython-312.pyc b/glob/__pycache__/manager_core.cpython-312.pyc new file mode 100644 index 00000000..00f03dff Binary files /dev/null and b/glob/__pycache__/manager_core.cpython-312.pyc differ diff --git a/glob/__pycache__/manager_downloader.cpython-312.pyc b/glob/__pycache__/manager_downloader.cpython-312.pyc new file mode 100644 index 00000000..460aa5cf Binary files /dev/null and b/glob/__pycache__/manager_downloader.cpython-312.pyc differ diff --git a/glob/__pycache__/manager_migration.cpython-312.pyc b/glob/__pycache__/manager_migration.cpython-312.pyc new file mode 100644 index 00000000..deb3c711 Binary files /dev/null and b/glob/__pycache__/manager_migration.cpython-312.pyc differ diff --git a/glob/__pycache__/manager_server.cpython-312.pyc b/glob/__pycache__/manager_server.cpython-312.pyc new file mode 100644 index 00000000..f568cc4f Binary files /dev/null and b/glob/__pycache__/manager_server.cpython-312.pyc differ diff --git a/glob/__pycache__/manager_util.cpython-312.pyc b/glob/__pycache__/manager_util.cpython-312.pyc new file mode 100644 index 00000000..2052b027 Binary files /dev/null and b/glob/__pycache__/manager_util.cpython-312.pyc differ diff --git a/glob/__pycache__/node_package.cpython-312.pyc b/glob/__pycache__/node_package.cpython-312.pyc new file mode 100644 index 00000000..140ace14 Binary files /dev/null and b/glob/__pycache__/node_package.cpython-312.pyc differ diff --git a/glob/__pycache__/security_check.cpython-312.pyc b/glob/__pycache__/security_check.cpython-312.pyc new file mode 100644 index 00000000..5819af1a Binary files /dev/null and b/glob/__pycache__/security_check.cpython-312.pyc differ diff --git a/glob/__pycache__/share_3rdparty.cpython-312.pyc b/glob/__pycache__/share_3rdparty.cpython-312.pyc new file mode 100644 index 00000000..91325b43 Binary files /dev/null and b/glob/__pycache__/share_3rdparty.cpython-312.pyc differ diff --git a/glob/cm_global.py b/glob/cm_global.py deleted file mode 100644 index e5d2237c..00000000 --- a/glob/cm_global.py +++ /dev/null @@ -1,117 +0,0 @@ -import traceback - -# -# Global Var -# -# Usage: -# import cm_global -# cm_global.variables['comfyui.revision'] = 1832 -# print(f"log mode: {cm_global.variables['logger.enabled']}") -# -variables = {} - - -# -# Global API -# -# Usage: -# [register API] -# import cm_global -# -# def api_hello(msg): -# print(f"hello: {msg}") -# return msg -# -# cm_global.register_api('hello', api_hello) -# -# [use API] -# import cm_global -# -# test = cm_global.try_call(api='hello', msg='an example') -# print(f"'{test}' is returned") -# - -APIs = {} - - -def register_api(k, f): - global APIs - APIs[k] = f - - -def try_call(**kwargs): - if 'api' in kwargs: - api_name = kwargs['api'] - try: - api = APIs.get(api_name) - if api is not None: - del kwargs['api'] - return api(**kwargs) - else: - print(f"WARN: The '{kwargs['api']}' API has not been registered.") - except Exception as e: - print(f"ERROR: An exception occurred while calling the '{api_name}' API.") - raise e - else: - return None - - -# -# Extension Info -# -# Usage: -# import cm_global -# -# cm_global.extension_infos['my_extension'] = {'version': [0, 1], 'name': 'me', 'description': 'example extension', } -# -extension_infos = {} - -on_extension_registered_handlers = {} - - -def register_extension(extension_name, v): - global extension_infos - global on_extension_registered_handlers - extension_infos[extension_name] = v - - if extension_name in on_extension_registered_handlers: - for k, f in on_extension_registered_handlers[extension_name]: - try: - f(extension_name, v) - except Exception: - print(f"[ERROR] '{k}' on_extension_registered_handlers") - traceback.print_exc() - - del on_extension_registered_handlers[extension_name] - - -def add_on_extension_registered(k, extension_name, f): - global on_extension_registered_handlers - if extension_name in extension_infos: - try: - v = extension_infos[extension_name] - f(extension_name, v) - except Exception: - print(f"[ERROR] '{k}' on_extension_registered_handler") - traceback.print_exc() - else: - if extension_name not in on_extension_registered_handlers: - on_extension_registered_handlers[extension_name] = [] - - on_extension_registered_handlers[extension_name].append((k, f)) - - -def add_on_revision_detected(k, f): - if 'comfyui.revision' in variables: - try: - f(variables['comfyui.revision']) - except Exception: - print(f"[ERROR] '{k}' on_revision_detected_handler") - traceback.print_exc() - else: - variables['cm.on_revision_detected_handler'].append((k, f)) - - -error_dict = {} - -disable_front = False \ No newline at end of file diff --git a/glob/cnr_utils.py b/glob/cnr_utils.py deleted file mode 100644 index 95d260db..00000000 --- a/glob/cnr_utils.py +++ /dev/null @@ -1,253 +0,0 @@ -import asyncio -import json -import os -import platform -import time -from dataclasses import dataclass -from typing import List - -import manager_core -import manager_util -import requests -import toml - -base_url = "https://api.comfy.org" - - -lock = asyncio.Lock() - -is_cache_loading = False - -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.") - return await _get_cnr_data(cache_mode=True, dont_wait=True) # timeout fallback - -async def _get_cnr_data(cache_mode=True, dont_wait=True): - global is_cache_loading - - uri = f'{base_url}/nodes' - - async def fetch_all(): - remained = True - page = 1 - - full_nodes = {} - - - # Determine form factor based on environment and platform - is_desktop = bool(os.environ.get('__COMFYUI_DESKTOP_VERSION__')) - system = platform.system().lower() - is_windows = system == 'windows' - is_mac = system == 'darwin' - is_linux = system == 'linux' - - # Get ComfyUI version tag - if is_desktop: - # extract version from pyproject.toml instead of git tag - comfyui_ver = manager_core.get_current_comfyui_ver() or 'unknown' - else: - comfyui_ver = manager_core.get_comfyui_tag() or 'unknown' - - if is_desktop: - if is_windows: - form_factor = 'desktop-win' - elif is_mac: - form_factor = 'desktop-mac' - else: - form_factor = 'other' - else: - if is_windows: - form_factor = 'git-windows' - elif is_mac: - form_factor = 'git-mac' - elif is_linux: - form_factor = 'git-linux' - else: - form_factor = 'other' - - while remained: - # Add comfyui_version and form_factor to the API request - sub_uri = f'{base_url}/nodes?page={page}&limit=30&comfyui_version={comfyui_ver}&form_factor={form_factor}' - sub_json_obj = await asyncio.wait_for(manager_util.get_data_with_cache(sub_uri, cache_mode=False, silent=True, dont_cache=True), timeout=30) - remained = page < sub_json_obj['totalPages'] - - for x in sub_json_obj['nodes']: - full_nodes[x['id']] = x - - if page % 5 == 0: - print(f"FETCH ComfyRegistry Data: {page}/{sub_json_obj['totalPages']}") - - page += 1 - time.sleep(0.5) - - print("FETCH ComfyRegistry Data [DONE]") - - for v in full_nodes.values(): - if 'latest_version' not in v: - v['latest_version'] = dict(version='nightly') - - return {'nodes': list(full_nodes.values())} - - if cache_mode: - is_cache_loading = True - cache_state = manager_util.get_cache_state(uri) - - if dont_wait: - 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.") - with open(manager_util.get_cache_path(uri), 'r', encoding="UTF-8", errors="ignore") as json_file: - return json.load(json_file)['nodes'] - - if cache_state == 'cached': - with open(manager_util.get_cache_path(uri), 'r', encoding="UTF-8", errors="ignore") as json_file: - return json.load(json_file)['nodes'] - - try: - json_obj = await fetch_all() - manager_util.save_to_cache(uri, json_obj) - return json_obj['nodes'] - except: - res = {} - print("Cannot connect to comfyregistry.") - finally: - if cache_mode: - is_cache_loading = False - - return res - - -@dataclass -class NodeVersion: - changelog: str - dependencies: List[str] - deprecated: bool - id: str - version: str - download_url: str - - -def map_node_version(api_node_version): - """ - Maps node version data from API response to NodeVersion dataclass. - - Args: - api_data (dict): The 'node_version' part of the API response. - - Returns: - NodeVersion: An instance of NodeVersion dataclass populated with data from the API. - """ - return NodeVersion( - changelog=api_node_version.get( - "changelog", "" - ), # Provide a default value if 'changelog' is missing - dependencies=api_node_version.get( - "dependencies", [] - ), # Provide a default empty list if 'dependencies' is missing - deprecated=api_node_version.get( - "deprecated", False - ), # Assume False if 'deprecated' is not specified - id=api_node_version[ - "id" - ], # 'id' should be mandatory; raise KeyError if missing - version=api_node_version[ - "version" - ], # 'version' should be mandatory; raise KeyError if missing - download_url=api_node_version.get( - "downloadUrl", "" - ), # Provide a default value if 'downloadUrl' is missing - ) - - -def install_node(node_id, version=None): - """ - Retrieves the node version for installation. - - Args: - node_id (str): The unique identifier of the node. - version (str, optional): Specific version of the node to retrieve. If omitted, the latest version is returned. - - Returns: - NodeVersion: Node version data or error message. - """ - if version is None: - url = f"{base_url}/nodes/{node_id}/install" - else: - url = f"{base_url}/nodes/{node_id}/install?version={version}" - - response = requests.get(url, verify=not manager_util.bypass_ssl) - if response.status_code == 200: - # Convert the API response to a NodeVersion object - return map_node_version(response.json()) - else: - return None - - -def all_versions_of_node(node_id): - url = f"{base_url}/nodes/{node_id}/versions?statuses=NodeVersionStatusActive&statuses=NodeVersionStatusPending" - - response = requests.get(url, verify=not manager_util.bypass_ssl) - if response.status_code == 200: - return response.json() - else: - return None - - -def read_cnr_info(fullpath): - try: - toml_path = os.path.join(fullpath, 'pyproject.toml') - tracking_path = os.path.join(fullpath, '.tracking') - - if not os.path.exists(toml_path) or not os.path.exists(tracking_path): - return None # not valid CNR node pack - - with open(toml_path, "r", encoding="utf-8") as f: - data = toml.load(f) - - project = data.get('project', {}) - name = project.get('name').strip().lower() - - # normalize version - # for example: 2.5 -> 2.5.0 - version = str(manager_util.StrictVersion(project.get('version'))) - - urls = project.get('urls', {}) - repository = urls.get('Repository') - - if name and version: # repository is optional - return { - "id": name, - "version": version, - "url": repository - } - - return None - except Exception: - return None # not valid CNR node pack - - -def generate_cnr_id(fullpath, cnr_id): - cnr_id_path = os.path.join(fullpath, '.git', '.cnr-id') - try: - if not os.path.exists(cnr_id_path): - with open(cnr_id_path, "w") as f: - return f.write(cnr_id) - except: - print(f"[ComfyUI Manager] unable to create file: {cnr_id_path}") - - -def read_cnr_id(fullpath): - cnr_id_path = os.path.join(fullpath, '.git', '.cnr-id') - try: - if os.path.exists(cnr_id_path): - with open(cnr_id_path) as f: - return f.read().strip() - except: - pass - - return None - diff --git a/glob/git_utils.py b/glob/git_utils.py deleted file mode 100644 index 1fe36cd2..00000000 --- a/glob/git_utils.py +++ /dev/null @@ -1,87 +0,0 @@ -import os -import configparser - - -GITHUB_ENDPOINT = os.getenv('GITHUB_ENDPOINT') - - -def is_git_repo(path: str) -> bool: - """ Check if the path is a git repository. """ - # NOTE: Checking it through `git.Repo` must be avoided. - # It locks the file, causing issues on Windows. - return os.path.exists(os.path.join(path, '.git')) - - -def get_commit_hash(fullpath): - git_head = os.path.join(fullpath, '.git', 'HEAD') - if os.path.exists(git_head): - with open(git_head) as f: - line = f.readline() - - if line.startswith("ref: "): - ref = os.path.join(fullpath, '.git', line[5:].strip()) - if os.path.exists(ref): - with open(ref) as f2: - return f2.readline().strip() - else: - return "unknown" - else: - return line - - return "unknown" - - -def git_url(fullpath): - """ - resolve version of unclassified custom node based on remote url in .git/config - """ - git_config_path = os.path.join(fullpath, '.git', 'config') - - if not os.path.exists(git_config_path): - return None - - # Set `strict=False` to allow duplicate `vscode-merge-base` sections, addressing - config = configparser.ConfigParser(strict=False) - config.read(git_config_path) - - for k, v in config.items(): - if k.startswith('remote ') and 'url' in v: - if 'Comfy-Org/ComfyUI-Manager' in v['url']: - return "https://github.com/ltdrdata/ComfyUI-Manager" - return v['url'] - - return None - - -def normalize_url(url) -> str: - github_id = normalize_to_github_id(url) - if github_id is not None: - url = f"https://github.com/{github_id}" - - return url - - -def normalize_to_github_id(url) -> str: - if 'github' in url or (GITHUB_ENDPOINT is not None and GITHUB_ENDPOINT in url): - author = os.path.basename(os.path.dirname(url)) - - if author.startswith('git@github.com:'): - author = author.split(':')[1] - - repo_name = os.path.basename(url) - if repo_name.endswith('.git'): - repo_name = repo_name[:-4] - - return f"{author}/{repo_name}" - - return None - - -def get_url_for_clone(url): - url = normalize_url(url) - - if GITHUB_ENDPOINT is not None and url.startswith('https://github.com/'): - url = GITHUB_ENDPOINT + url[18:] # url[18:] -> remove `https://github.com` - - return url - \ No newline at end of file diff --git a/glob/manager_core.py b/glob/manager_core.py deleted file mode 100644 index e0b3a6fe..00000000 --- a/glob/manager_core.py +++ /dev/null @@ -1,3498 +0,0 @@ -""" -description: - `manager_core` contains the core implementation of the management functions in ComfyUI-Manager. -""" - -import json -import logging -import os -import sys -import subprocess -import re -import shutil -import configparser -import platform -from datetime import datetime - -import git -from git.remote import RemoteProgress -from urllib.parse import urlparse -from tqdm.auto import tqdm -import time -import yaml -import zipfile -import traceback -from concurrent.futures import ThreadPoolExecutor, as_completed -import toml - -orig_print = print - -from rich import print -from packaging import version - -import uuid - -glob_path = os.path.join(os.path.dirname(__file__)) # ComfyUI-Manager/glob -sys.path.append(glob_path) - -import cm_global -import cnr_utils -import manager_util -import git_utils -import manager_downloader -import manager_migration -from node_package import InstalledNodePackage - - -version_code = [3, 39, 2] -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_custom_nodes_path = None - - -class InvalidChannel(Exception): - def __init__(self, channel): - self.channel = channel - super().__init__(channel) - -def get_default_custom_nodes_path(): - global default_custom_nodes_path - if default_custom_nodes_path is None: - try: - import folder_paths - default_custom_nodes_path = folder_paths.get_folder_paths("custom_nodes")[0] - except: - default_custom_nodes_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..')) - - return default_custom_nodes_path - - -def get_custom_nodes_paths(): - try: - import folder_paths - return folder_paths.get_folder_paths("custom_nodes") - except: - custom_nodes_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..')) - return [custom_nodes_path] - - -def get_comfyui_tag(): - try: - repo = git.Repo(comfy_path) - return repo.git.describe('--tags') - except: - return None - - -def get_current_comfyui_ver(): - """ - Extract version from pyproject.toml - """ - toml_path = os.path.join(comfy_path, 'pyproject.toml') - if not os.path.exists(toml_path): - return None - else: - try: - with open(toml_path, "r", encoding="utf-8") as f: - data = toml.load(f) - - project = data.get('project', {}) - return project.get('version') - except: - return None - - -def get_script_env(): - new_env = os.environ.copy() - git_exe = get_config().get('git_exe') - if git_exe is not None: - new_env['GIT_EXE_PATH'] = git_exe - - if 'COMFYUI_PATH' not in new_env: - new_env['COMFYUI_PATH'] = comfy_path - - if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env: - new_env['COMFYUI_FOLDERS_BASE_PATH'] = comfy_path - - return new_env - - -invalid_nodes = {} - - -def extract_base_custom_nodes_dir(x:str): - if os.path.dirname(x).endswith('.disabled'): - return os.path.dirname(os.path.dirname(x)) - elif x.endswith('.disabled'): - return os.path.dirname(x) - else: - return os.path.dirname(x) - - -def check_invalid_nodes(): - global invalid_nodes - - try: - import folder_paths - except: - try: - sys.path.append(comfy_path) - import folder_paths - except: - raise Exception(f"Invalid COMFYUI_FOLDERS_BASE_PATH: {comfy_path}") - - def check(root): - global invalid_nodes - - subdirs = [d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))] - for subdir in subdirs: - if subdir in ['.disabled', '__pycache__']: - continue - - package = unified_manager.installed_node_packages.get(subdir) - if not package: - continue - - if not package.isValid(): - invalid_nodes[subdir] = package.fullpath - - node_paths = folder_paths.get_folder_paths("custom_nodes") - for x in node_paths: - check(x) - - disabled_dir = os.path.join(x, '.disabled') - if os.path.exists(disabled_dir): - check(disabled_dir) - - if len(invalid_nodes): - print("\n-------------------- ComfyUI-Manager invalid nodes notice ----------------") - print("\nNodes requiring reinstallation have been detected:\n(Directly delete the corresponding path and reinstall.)\n") - - for x in invalid_nodes.values(): - print(x) - - print("\n---------------------------------------------------------------------------\n") - - -# read env vars -comfy_path: str = os.environ.get('COMFYUI_PATH') -comfy_base_path = os.environ.get('COMFYUI_FOLDERS_BASE_PATH') - -if comfy_path is None: - try: - import folder_paths - comfy_path = os.path.join(os.path.dirname(folder_paths.__file__)) - except: - comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..')) - -if comfy_base_path is None: - comfy_base_path = comfy_path - - -channel_list_template_path = os.path.join(manager_util.comfyui_manager_path, 'channels.list.template') -git_script_path = os.path.join(manager_util.comfyui_manager_path, "git_helper.py") - -manager_files_path = None -manager_config_path = None -manager_channel_list_path = None -manager_startup_script_path:str = None -manager_snapshot_path = None -manager_pip_overrides_path = None -manager_pip_blacklist_path = None -manager_components_path = None - -def update_user_directory(user_dir): - global manager_files_path - global manager_config_path - global manager_channel_list_path - global manager_startup_script_path - global manager_snapshot_path - global manager_pip_overrides_path - global manager_pip_blacklist_path - global manager_components_path - - manager_files_path = manager_migration.get_manager_path(user_dir) - if not os.path.exists(manager_files_path): - os.makedirs(manager_files_path) - manager_migration.run_migration_checks(user_dir, manager_files_path) - - manager_snapshot_path = os.path.join(manager_files_path, "snapshots") - if not os.path.exists(manager_snapshot_path): - os.makedirs(manager_snapshot_path) - - manager_startup_script_path = os.path.join(manager_files_path, "startup-scripts") - if not os.path.exists(manager_startup_script_path): - os.makedirs(manager_startup_script_path) - - manager_config_path = os.path.join(manager_files_path, 'config.ini') - manager_channel_list_path = os.path.join(manager_files_path, 'channels.list') - 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") - manager_components_path = os.path.join(manager_files_path, "components") - manager_util.cache_dir = os.path.join(manager_files_path, "cache") - - if not os.path.exists(manager_util.cache_dir): - os.makedirs(manager_util.cache_dir) - -try: - import folder_paths - update_user_directory(folder_paths.get_user_directory()) - -except Exception: - # fallback: - # This case is only possible when running with cm-cli, and in practice, this case is not actually used. - update_user_directory(os.path.abspath(manager_util.comfyui_manager_path)) - - -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'} -channel_list = None - - -def remap_pip_package(pkg): - if pkg in cm_global.pip_overrides: - res = cm_global.pip_overrides[pkg] - print(f"[ComfyUI-Manager] '{pkg}' is remapped to '{res}'") - return res - else: - return pkg - - -def is_blacklisted(name): - name = name.strip() - - pattern = r'([^<>!~=]+)([<>!~=]=?)([^ ]*)' - match = re.search(pattern, name) - - if match: - name = match.group(1) - - if name in cm_global.pip_blacklist: - return True - - if name in cm_global.pip_downgrade_blacklist: - pips = manager_util.get_installed_packages() - - if match is None: - if name in pips: - return True - elif match.group(2) in ['<=', '==', '<', '~=']: - if name in pips: - if manager_util.StrictVersion(pips[name]) >= manager_util.StrictVersion(match.group(3)): - return True - - return False - - -def is_installed(name): - name = name.strip() - - if name.startswith('#'): - return True - - pattern = r'([^<>!~=]+)([<>!~=]=?)([0-9.a-zA-Z]*)' - match = re.search(pattern, name) - - if match: - name = match.group(1) - - if name in cm_global.pip_blacklist: - return True - - if name in cm_global.pip_downgrade_blacklist: - pips = manager_util.get_installed_packages() - - if match is None: - if name in pips: - return True - elif match.group(2) in ['<=', '==', '<', '~=']: - if name in pips: - if manager_util.StrictVersion(pips[name]) >= manager_util.StrictVersion(match.group(3)): - print(f"[ComfyUI-Manager] skip black listed pip installation: '{name}'") - return True - - pkg = manager_util.get_installed_packages().get(name.lower()) - if pkg is None: - return False # update if not installed - - if match is None: - return True # don't update if version is not specified - - if match.group(2) in ['>', '>=']: - if manager_util.StrictVersion(pkg) < manager_util.StrictVersion(match.group(3)): - return False - elif manager_util.StrictVersion(pkg) > manager_util.StrictVersion(match.group(3)): - print(f"[SKIP] Downgrading pip package isn't allowed: {name.lower()} (cur={pkg})") - - if match.group(2) == '==': - if manager_util.StrictVersion(pkg) < manager_util.StrictVersion(match.group(3)): - return False - - if match.group(2) == '~=': - if manager_util.StrictVersion(pkg) == manager_util.StrictVersion(match.group(3)): - return False - - return name.lower() in manager_util.get_installed_packages() - - -def normalize_channel(channel): - if channel == 'local': - return channel - elif channel is None: - return None - elif channel.startswith('https://'): - return channel - elif channel.startswith('http://') and get_config()['http_channel_enabled'] == True: - return channel - - tmp_dict = get_channel_dict() - channel_url = tmp_dict.get(channel) - if channel_url: - return channel_url - - raise InvalidChannel(channel) - - -class ManagedResult: - def __init__(self, action): - self.action = action - self.items = [] - self.result = True - self.to_path = None - self.msg = None - self.target = None - self.postinstall = lambda: True - self.ver = None - - def append(self, item): - self.items.append(item) - - def fail(self, msg): - self.result = False - self.msg = msg - return self - - def with_target(self, target): - self.target = target - return self - - def with_msg(self, msg): - self.msg = msg - return self - - def with_postinstall(self, postinstall): - self.postinstall = postinstall - return self - - def with_ver(self, ver): - self.ver = ver - return self - - -class NormalizedKeyDict: - def __init__(self): - self._store = {} - self._key_map = {} - - def _normalize_key(self, key): - if isinstance(key, str): - return key.strip().lower() - return key - - def __setitem__(self, key, value): - norm_key = self._normalize_key(key) - self._key_map[norm_key] = key - self._store[key] = value - - def __getitem__(self, key): - norm_key = self._normalize_key(key) - original_key = self._key_map[norm_key] - return self._store[original_key] - - def __delitem__(self, key): - norm_key = self._normalize_key(key) - original_key = self._key_map.pop(norm_key) - del self._store[original_key] - - def __contains__(self, key): - return self._normalize_key(key) in self._key_map - - def get(self, key, default=None): - return self[key] if key in self else default - - def setdefault(self, key, default=None): - if key in self: - return self[key] - self[key] = default - return default - - def pop(self, key, default=None): - if key in self: - val = self[key] - del self[key] - return val - if default is not None: - return default - raise KeyError(key) - - def keys(self): - return self._store.keys() - - def values(self): - return self._store.values() - - def items(self): - return self._store.items() - - def __iter__(self): - return iter(self._store) - - def __len__(self): - return len(self._store) - - def __repr__(self): - return repr(self._store) - - def to_dict(self): - return dict(self._store) - - -class UnifiedManager: - def __init__(self): - self.installed_node_packages: dict[str, InstalledNodePackage] = {} - - self.cnr_inactive_nodes = NormalizedKeyDict() # node_id -> node_version -> fullpath - self.nightly_inactive_nodes = NormalizedKeyDict() # node_id -> fullpath - self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath - self.active_nodes = NormalizedKeyDict() # node_id -> node_version * fullpath - self.unknown_active_nodes = {} # node_id -> repo url * fullpath - self.cnr_map = NormalizedKeyDict() # node_id -> cnr info - self.repo_cnr_map = {} # repo_url -> cnr info - self.custom_node_map_cache = {} # (channel, mode) -> augmented custom node list json - self.processed_install = set() - - def get_module_name(self, x): - info = self.active_nodes.get(x) - if info is None: - for url, fullpath in self.unknown_active_nodes.values(): - if url == x: - return os.path.basename(fullpath) - else: - return os.path.basename(info[1]) - - return None - - def get_cnr_by_repo(self, url): - return self.repo_cnr_map.get(git_utils.normalize_url(url)) - - def resolve_unspecified_version(self, node_name, guess_mode=None): - if guess_mode == 'active': - # priority: - # 1. CNR/nightly active nodes - # 2. unknown - # 3. Fail - - if node_name in self.cnr_map: - version_spec = self.get_from_cnr_active_nodes(node_name) - - if version_spec is None: - if node_name in self.unknown_active_nodes: - version_spec = "unknown" - else: - return None - - elif node_name in self.unknown_active_nodes: - version_spec = "unknown" - else: - return None - - elif guess_mode == 'inactive': - # priority: - # 1. CNR latest in inactive - # 2. nightly - # 3. unknown - # 4. Fail - - if node_name in self.cnr_map: - latest = self.get_from_cnr_inactive_nodes(node_name) - - if latest is not None: - version_spec = str(latest[0]) - else: - if node_name in self.nightly_inactive_nodes: - version_spec = "nightly" - else: - version_spec = "unknown" - - elif node_name in self.unknown_inactive_nodes: - version_spec = "unknown" - else: - return None - - else: - # priority: - # 1. CNR latest in world - # 2. unknown - - if node_name in self.cnr_map: - version_spec = self.cnr_map[node_name]['latest_version']['version'] - else: - version_spec = "unknown" - - return version_spec - - def resolve_node_spec(self, node_name, guess_mode=None): - """ - resolve to 'node_name, version_spec' from version string - - version string: - node_name@latest - node_name@nightly - node_name@unknown - node_name@ - node_name - - if guess_mode is 'active' or 'inactive' - return can be 'None' based on state check - otherwise - return 'unknown' version when failed to guess - """ - - spec = node_name.split('@') - - if len(spec) == 2: - node_name = spec[0] - version_spec = spec[1] - - if version_spec == 'latest': - if node_name not in self.cnr_map: - print(f"ERROR: '{node_name}' is not a CNR node.") - return None - else: - version_spec = self.cnr_map[node_name]['latest_version']['version'] - - elif guess_mode in ['active', 'inactive']: - node_name = spec[0] - version_spec = self.resolve_unspecified_version(node_name, guess_mode=guess_mode) - if version_spec is None: - return None - else: - node_name = spec[0] - version_spec = self.resolve_unspecified_version(node_name) - if version_spec is None: - return None - - return node_name, version_spec, len(spec) > 1 - - def resolve_from_path(self, fullpath): - url = git_utils.git_url(fullpath) - if url: - url = git_utils.normalize_url(url) - - cnr = self.get_cnr_by_repo(url) - commit_hash = git_utils.get_commit_hash(fullpath) - if cnr: - cnr_utils.generate_cnr_id(fullpath, cnr['id']) - return {'id': cnr['id'], 'cnr': cnr, 'ver': 'nightly', 'hash': commit_hash} - else: - url = os.path.basename(url) - if url.endswith('.git'): - url = url[:-4] - return {'id': url, 'ver': 'unknown', 'hash': commit_hash} - else: - info = cnr_utils.read_cnr_info(fullpath) - - if info: - cnr = self.cnr_map.get(info['id']) - if cnr: - # normalize version - # for example: 2.5 -> 2.5.0 - ver = str(manager_util.StrictVersion(info['version'])) - return {'id': cnr['id'], 'cnr': cnr, 'ver': ver} - else: - return None - else: - return None - - def update_cache_at_path(self, fullpath): - node_package = InstalledNodePackage.from_fullpath(fullpath, self.resolve_from_path) - self.installed_node_packages[node_package.id] = node_package - - if node_package.is_disabled and node_package.is_unknown: - url = git_utils.git_url(node_package.fullpath) - if url is not None: - url = git_utils.normalize_url(url) - self.unknown_inactive_nodes[node_package.id] = (url, node_package.fullpath) - - if node_package.is_disabled and node_package.is_nightly: - self.nightly_inactive_nodes[node_package.id] = node_package.fullpath - - if node_package.is_enabled and not node_package.is_unknown: - self.active_nodes[node_package.id] = node_package.version, node_package.fullpath - - if node_package.is_enabled and node_package.is_unknown: - url = git_utils.git_url(node_package.fullpath) - if url is not None: - url = git_utils.normalize_url(url) - self.unknown_active_nodes[node_package.id] = (url, node_package.fullpath) - - if node_package.is_from_cnr and node_package.is_disabled: - self.add_to_cnr_inactive_nodes(node_package.id, node_package.version, node_package.fullpath) - - def is_updatable(self, node_id): - cur_ver = self.get_cnr_active_version(node_id) - latest_ver = self.cnr_map[node_id]['latest_version']['version'] - - if cur_ver and latest_ver: - return self.safe_version(latest_ver) > self.safe_version(cur_ver) - - return False - - def fetch_or_pull_git_repo(self, is_pull=False): - updated = set() - failed = set() - - def check_update(node_name, fullpath, ver_spec): - try: - if is_pull: - is_updated, success = git_repo_update_check_with(fullpath, do_update=True) - else: - is_updated, success = git_repo_update_check_with(fullpath, do_fetch=True) - - return f"{node_name}@{ver_spec}", is_updated, success - except Exception: - traceback.print_exc() - - return f"{node_name}@{ver_spec}", False, False - - with ThreadPoolExecutor() as executor: - futures = [] - - for k, v in self.unknown_active_nodes.items(): - futures.append(executor.submit(check_update, k, v[1], 'unknown')) - - for k, v in self.active_nodes.items(): - if v[0] == 'nightly': - futures.append(executor.submit(check_update, k, v[1], 'nightly')) - - for future in as_completed(futures): - item, is_updated, success = future.result() - - if is_updated: - updated.add(item) - - if not success: - failed.add(item) - - return dict(updated=list(updated), failed=list(failed)) - - def is_enabled(self, node_id, version_spec=None): - """ - 1. true if node_id@ is enabled - 2. true if node_id@ is enabled and version_spec==None - 3. false otherwise - - remark: latest version_spec is not allowed. Must be resolved before call. - """ - if version_spec == "cnr": - return self.get_cnr_active_version(node_id) not in [None, 'nightly'] - elif version_spec == 'unknown' and self.is_unknown_active(node_id): - return True - elif version_spec is not None and self.get_cnr_active_version(node_id) == version_spec: - return True - elif version_spec is None and (node_id in self.active_nodes or node_id in self.unknown_active_nodes): - return True - return False - - def is_disabled(self, node_id, version_spec=None): - """ - 1. node_id@unknown is disabled if version_spec is @unknown - 2. node_id@nightly is disabled if version_spec is @nightly - 4. node_id@ is disabled if version_spec is not None - 5. not exists (active node_id) if version_spec is None - - remark: latest version_spec is not allowed. Must be resolved before call. - """ - if version_spec == "unknown": - return node_id in self.unknown_inactive_nodes - elif version_spec == "nightly": - return node_id in self.nightly_inactive_nodes - elif version_spec == "cnr": - res = self.cnr_inactive_nodes.get(node_id, None) - if res is None: - return False - - res = [x for x in res.keys() if x != 'nightly'] - return len(res) > 0 - elif version_spec is not None: - return version_spec in self.cnr_inactive_nodes.get(node_id, []) - - if node_id in self.nightly_inactive_nodes: - return True - elif node_id in self.unknown_inactive_nodes: - return True - - target = self.cnr_inactive_nodes.get(node_id, None) - if target is not None and target == version_spec: - return True - - return False - - def is_registered_in_cnr(self, node_id): - return node_id in self.cnr_map - - def get_cnr_active_version(self, node_id): - res = self.active_nodes.get(node_id) - if res: - return res[0] - else: - return None - - def is_unknown_active(self, node_id): - return node_id in self.unknown_active_nodes - - def add_to_cnr_inactive_nodes(self, node_id, ver, fullpath): - ver_map = self.cnr_inactive_nodes.get(node_id) - if ver_map is None: - ver_map = {} - self.cnr_inactive_nodes[node_id] = ver_map - - ver_map[ver] = fullpath - - def get_from_cnr_active_nodes(self, node_id): - ver_path = self.active_nodes.get(node_id) - if ver_path is None: - return None - - return ver_path[0] - - def get_from_cnr_inactive_nodes(self, node_id, ver=None): - ver_map = self.cnr_inactive_nodes.get(node_id) - if ver_map is None: - return None - - if ver is not None: - return ver_map.get(ver) - - latest = None - for k, v in ver_map.items(): - if latest is None: - latest = self.safe_version(k), v - continue - - cur_ver = self.safe_version(k) - if cur_ver > latest[0]: - latest = cur_ver, v - - return latest - - async def reload(self, cache_mode, dont_wait=True): - self.custom_node_map_cache = {} - self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath - self.nightly_inactive_nodes = {} # node_id -> fullpath - self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath - self.unknown_active_nodes = {} # node_id -> repo url * fullpath - self.active_nodes = {} # node_id -> node_version * fullpath - - if get_config()['network_mode'] != 'public': - dont_wait = True - - # reload 'cnr_map' and 'repo_cnr_map' - cnrs = await cnr_utils.get_cnr_data(cache_mode=cache_mode=='cache', dont_wait=dont_wait) - - for x in cnrs: - self.cnr_map[x['id']] = x - if 'repository' in x: - normalized_url = git_utils.normalize_url(x['repository']) - self.repo_cnr_map[normalized_url] = x - - # reload node status info from custom_nodes/* - for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'): - for x in os.listdir(custom_nodes_path): - fullpath = os.path.join(custom_nodes_path, x) - if os.path.isdir(fullpath): - if x not in ['__pycache__', '.disabled']: - self.update_cache_at_path(fullpath) - - # reload node status info from custom_nodes/.disabled/* - for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'): - disabled_dir = os.path.join(custom_nodes_path, '.disabled') - if os.path.exists(disabled_dir): - for x in os.listdir(disabled_dir): - fullpath = os.path.join(disabled_dir, x) - if os.path.isdir(fullpath): - self.update_cache_at_path(fullpath) - - @staticmethod - async def load_nightly(channel, mode): - if channel is None: - return {} - - res = {} - - channel_url = normalize_channel(channel) - if channel_url: - if mode not in ['remote', 'local', 'cache']: - print(f"[bold red]ERROR: Invalid mode is specified `--mode {mode}`[/bold red]", file=sys.stderr) - return {} - - # validate channel - only the channel set by the user is allowed. - if channel_url not in valid_channels: - logging.error(f'[ComfyUI-Manager] An invalid channel was used: {channel_url}') - raise InvalidChannel(channel_url) - - json_obj = await get_data_by_mode(mode, 'custom-node-list.json', channel_url=channel_url) - for x in json_obj['custom_nodes']: - try: - for y in x['files']: - if 'github.com' in y and not (y.endswith('.py') or y.endswith('.js')): - repo_name = y.split('/')[-1] - res[repo_name] = (x, False) - - if 'id' in x: - if x['id'] not in res: - res[x['id']] = (x, True) - except: - logging.error(f"[ComfyUI-Manager] broken item:{x}") - - return res - - async def get_custom_nodes(self, channel, mode): - if channel is None and mode is None: - channel = 'default' - mode = 'cache' - - channel = normalize_channel(channel) - cache = self.custom_node_map_cache.get((channel, mode)) # CNR/nightly should always be based on the default channel. - - if cache is not None: - return cache - - channel = normalize_channel(channel) - nodes = await self.load_nightly(channel, mode) - - res = NormalizedKeyDict() - added_cnr = set() - for v in nodes.values(): - v = v[0] - if len(v['files']) == 1: - cnr = self.get_cnr_by_repo(v['files'][0]) - if cnr: - if 'latest_version' not in cnr: - v['cnr_latest'] = '0.0.0' - else: - v['cnr_latest'] = cnr['latest_version']['version'] - v['id'] = cnr['id'] - v['author'] = cnr['publisher']['name'] - v['title'] = cnr['name'] - v['description'] = cnr['description'] - v['health'] = '-' - if 'repository' in cnr: - v['repository'] = cnr['repository'] - added_cnr.add(cnr['id']) - node_id = v['id'] - else: - node_id = v['files'][0].split('/')[-1] - v['repository'] = v['files'][0] - res[node_id] = v - elif len(v['files']) > 1: - res[v['files'][0]] = v # A custom node composed of multiple url is treated as a single repository with one representative path - - self.custom_node_map_cache[(channel, mode)] = res - return res - - @staticmethod - def safe_version(ver_str): - try: - return version.parse(ver_str) - except: - return version.parse("0.0.0") - - def execute_install_script(self, url, repo_path, instant_execution=False, lazy_mode=False, no_deps=False): - install_script_path = os.path.join(repo_path, "install.py") - requirements_path = os.path.join(repo_path, "requirements.txt") - - res = True - if lazy_mode: - install_cmd = ["#LAZY-INSTALL-SCRIPT", sys.executable] - return try_install_script(url, repo_path, install_cmd) - else: - if os.path.exists(requirements_path) and not no_deps: - print("Install: pip packages") - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path) - lines = manager_util.robust_readlines(requirements_path) - for line in lines: - package_name = remap_pip_package(line.strip()) - if package_name and not package_name.startswith('#') and package_name not in self.processed_install: - self.processed_install.add(package_name) - clean_package_name = package_name.split('#')[0].strip() - install_cmd = manager_util.make_pip_cmd(["install", clean_package_name]) - if clean_package_name != "" and not clean_package_name.startswith('#'): - res = res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) - - pip_fixer.fix_broken() - - if os.path.exists(install_script_path) and install_script_path not in self.processed_install: - self.processed_install.add(install_script_path) - print("Install: install script") - install_cmd = [sys.executable, "install.py"] - return res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) - - return res - - def reserve_cnr_switch(self, target, zip_url, from_path, to_path, no_deps): - script_path = os.path.join(manager_startup_script_path, "install-scripts.txt") - with open(script_path, "a") as file: - obj = [target, "#LAZY-CNR-SWITCH-SCRIPT", zip_url, from_path, to_path, no_deps, get_default_custom_nodes_path(), sys.executable] - file.write(f"{obj}\n") - - print(f"Installation reserved: {target}") - - return True - - def unified_fix(self, node_id, version_spec, instant_execution=False, no_deps=False): - """ - fix dependencies - """ - - result = ManagedResult('fix') - - if version_spec == 'unknown': - info = self.unknown_active_nodes.get(node_id) - else: - info = self.active_nodes.get(node_id) - - if info is None or not os.path.exists(info[1]): - return result.fail(f'not found: {node_id}@{version_spec}') - - self.execute_install_script(node_id, info[1], instant_execution=instant_execution, no_deps=no_deps) - - return result - - def cnr_switch_version(self, node_id, version_spec=None, instant_execution=False, no_deps=False, return_postinstall=False): - if instant_execution: - return self.cnr_switch_version_instant(node_id, version_spec, instant_execution, no_deps, return_postinstall) - else: - return self.cnr_switch_version_lazy(node_id, version_spec, no_deps, return_postinstall) - - def cnr_switch_version_lazy(self, node_id, version_spec=None, no_deps=False, return_postinstall=False): - """ - switch between cnr version (lazy mode) - """ - - result = ManagedResult('switch-cnr') - - node_info = cnr_utils.install_node(node_id, version_spec) - if node_info is None or not node_info.download_url: - return result.fail(f'not available node: {node_id}@{version_spec}') - - version_spec = node_info.version - - if self.active_nodes[node_id][0] == version_spec: - return ManagedResult('skip').with_msg("Up to date") - - zip_url = node_info.download_url - from_path = self.active_nodes[node_id][1] - target = node_id - to_path = os.path.join(get_default_custom_nodes_path(), target) - - def postinstall(): - return self.reserve_cnr_switch(target, zip_url, from_path, to_path, no_deps) - - if return_postinstall: - return result.with_postinstall(postinstall) - else: - if not postinstall(): - return result.fail(f"Failed to execute install script: {node_id}@{version_spec}") - - return result - - def cnr_switch_version_instant(self, node_id, version_spec=None, instant_execution=True, no_deps=False, return_postinstall=False): - """ - switch between cnr version - """ - - # 1. download - result = ManagedResult('switch-cnr') - - node_info = cnr_utils.install_node(node_id, version_spec) - if node_info is None or not node_info.download_url: - return result.fail(f'not available node: {node_id}@{version_spec}') - - version_spec = node_info.version - - if self.active_nodes[node_id][0] == version_spec: - return ManagedResult('skip').with_msg("Up to date") - - archive_name = f"CNR_temp_{str(uuid.uuid4())}.zip" # should be unpredictable name - security precaution - download_path = os.path.join(get_default_custom_nodes_path(), archive_name) - manager_downloader.basic_download_url(node_info.download_url, get_default_custom_nodes_path(), archive_name) - - # 2. extract files into - install_path = self.active_nodes[node_id][1] - extracted = manager_util.extract_package_as_zip(download_path, install_path) - os.remove(download_path) - - if extracted is None: - if len(os.listdir(install_path)) == 0: - shutil.rmtree(install_path) - - return result.fail(f'Empty archive file: {node_id}@{version_spec}') - - # 3. calculate garbage files (.tracking - extracted) - tracking_info_file = os.path.join(install_path, '.tracking') - prev_files = set() - with open(tracking_info_file, 'r') as f: - for line in f: - prev_files.add(line.strip()) - garbage = prev_files.difference(extracted) - garbage = [os.path.join(install_path, x) for x in garbage] - - # 4-1. remove garbage files - for x in garbage: - if os.path.isfile(x): - os.remove(x) - - # 4-2. remove garbage dir if empty - for x in garbage: - if os.path.isdir(x): - if not os.listdir(x): - os.rmdir(x) - - # 5. create .tracking file - tracking_info_file = os.path.join(install_path, '.tracking') - with open(tracking_info_file, "w", encoding='utf-8') as file: - file.write('\n'.join(list(extracted))) - - # 6. post install - result.target = version_spec - - def postinstall(): - res = self.execute_install_script(f"{node_id}@{version_spec}", install_path, instant_execution=instant_execution, no_deps=no_deps) - return res - - if return_postinstall: - return result.with_postinstall(postinstall) - else: - if not postinstall(): - return result.fail(f"Failed to execute install script: {node_id}@{version_spec}") - - return result - - def unified_enable(self, node_id: str, version_spec=None): - """ - priority if version_spec == None - 1. CNR latest in disk - 2. nightly - 3. unknown - - remark: latest version_spec is not allowed. Must be resolved before call. - """ - - result = ManagedResult('enable') - - if 'comfyui-manager' in node_id.lower(): - return result.fail(f"ignored: enabling '{node_id}'") - - if version_spec is None: - version_spec = self.resolve_unspecified_version(node_id, guess_mode='inactive') - if version is None: - return result.fail(f'Specified inactive node not exists: {node_id}') - - if self.is_enabled(node_id, version_spec): - return ManagedResult('skip').with_msg('Already enabled') - - if not self.is_disabled(node_id, version_spec): - return ManagedResult('skip').with_msg('Not installed') - - from_path = None - to_path = None - - if version_spec == 'unknown': - repo_and_path = self.unknown_inactive_nodes.get(node_id) - if repo_and_path is None: - return result.fail(f'Specified inactive node not exists: {node_id}@unknown') - from_path = repo_and_path[1] - - base_path = extract_base_custom_nodes_dir(from_path) - to_path = os.path.join(base_path, node_id) - elif version_spec == 'nightly': - self.unified_disable(node_id, False) - from_path = self.nightly_inactive_nodes.get(node_id) - if from_path is None: - return result.fail(f'Specified inactive node not exists: {node_id}@nightly') - base_path = extract_base_custom_nodes_dir(from_path) - to_path = os.path.join(base_path, node_id) - elif version_spec is not None: - self.unified_disable(node_id, False) - cnr_info = self.cnr_inactive_nodes.get(node_id) - - if cnr_info is None or len(cnr_info) == 0: - return result.fail(f'Specified inactive cnr node not exists: {node_id}') - - if version_spec == "cnr": - version_spec = next(iter(cnr_info)) - - if version_spec not in cnr_info: - return result.fail(f'Specified inactive node not exists: {node_id}@{version_spec}') - - from_path = cnr_info[version_spec] - base_path = extract_base_custom_nodes_dir(from_path) - to_path = os.path.join(base_path, node_id) - - if from_path is None or not os.path.exists(from_path): - return result.fail(f'Specified inactive node path not exists: {from_path}') - - # move from disk - shutil.move(from_path, to_path) - - # update cache - if version_spec == 'unknown': - self.unknown_active_nodes[node_id] = self.unknown_inactive_nodes[node_id][0], to_path - del self.unknown_inactive_nodes[node_id] - return result.with_target(to_path) - elif version_spec == 'nightly': - del self.nightly_inactive_nodes[node_id] - else: - del self.cnr_inactive_nodes[node_id][version_spec] - - self.active_nodes[node_id] = version_spec, to_path - return result.with_target(to_path) - - def unified_disable(self, node_id: str, is_unknown): - result = ManagedResult('disable') - - if 'comfyui-manager' in node_id.lower(): - return result.fail(f"ignored: disabling '{node_id}'") - - if is_unknown: - version_spec = 'unknown' - else: - version_spec = None - - if not self.is_enabled(node_id, version_spec): - if not self.is_disabled(node_id, version_spec): - return ManagedResult('skip').with_msg('Not installed') - else: - return ManagedResult('skip').with_msg('Already disabled') - - if is_unknown: - repo_and_path = self.unknown_active_nodes.get(node_id) - - if repo_and_path is None or not os.path.exists(repo_and_path[1]): - return result.fail(f'Specified active node not exists: {node_id}') - - base_path = extract_base_custom_nodes_dir(repo_and_path[1]) - to_path = os.path.join(base_path, '.disabled', node_id) - - shutil.move(repo_and_path[1], to_path) - result.append((repo_and_path[1], to_path)) - - self.unknown_inactive_nodes[node_id] = repo_and_path[0], to_path - del self.unknown_active_nodes[node_id] - - return result - - ver_and_path = self.active_nodes.get(node_id) - - if ver_and_path is None or not os.path.exists(ver_and_path[1]): - return result.fail(f'Specified active node not exists: {node_id}') - - base_path = extract_base_custom_nodes_dir(ver_and_path[1]) - - # NOTE: A disabled node may have multiple versions, so preserve it using the `@ suffix`. - to_path = os.path.join(base_path, '.disabled', f"{node_id}@{ver_and_path[0].replace('.', '_')}") - shutil.move(ver_and_path[1], to_path) - result.append((ver_and_path[1], to_path)) - - if ver_and_path[0] == 'nightly': - self.nightly_inactive_nodes[node_id] = to_path - else: - self.add_to_cnr_inactive_nodes(node_id, ver_and_path[0], to_path) - - del self.active_nodes[node_id] - - return result - - def unified_uninstall(self, node_id: str, is_unknown: bool): - """ - Remove whole installed custom nodes including inactive nodes - """ - result = ManagedResult('uninstall') - - if 'comfyui-manager' in node_id.lower(): - return result.fail(f"ignored: uninstalling '{node_id}'") - - if is_unknown: - # remove from actives - repo_and_path = self.unknown_active_nodes.get(node_id) - - is_removed = False - - if repo_and_path is not None and os.path.exists(repo_and_path[1]): - rmtree(repo_and_path[1]) - result.append(repo_and_path[1]) - del self.unknown_active_nodes[node_id] - - is_removed = True - - # remove from inactives - repo_and_path = self.unknown_inactive_nodes.get(node_id) - - if repo_and_path is not None and os.path.exists(repo_and_path[1]): - rmtree(repo_and_path[1]) - result.append(repo_and_path[1]) - del self.unknown_inactive_nodes[node_id] - - is_removed = True - - if is_removed: - return result - else: - return ManagedResult('skip') - - # remove from actives - ver_and_path = self.active_nodes.get(node_id) - - if ver_and_path is not None and os.path.exists(ver_and_path[1]): - try_rmtree(node_id, ver_and_path[1]) - result.items.append(ver_and_path) - del self.active_nodes[node_id] - - # remove from nightly inactives - fullpath = self.nightly_inactive_nodes.get(node_id) - if fullpath is not None and os.path.exists(fullpath): - try_rmtree(node_id, fullpath) - result.items.append(('nightly', fullpath)) - del self.nightly_inactive_nodes[node_id] - - # remove from cnr inactives - ver_map = self.cnr_inactive_nodes.get(node_id) - if ver_map is not None: - for key, fullpath in ver_map.items(): - try_rmtree(node_id, fullpath) - result.items.append((key, fullpath)) - del self.cnr_inactive_nodes[node_id] - - if len(result.items) == 0: - return ManagedResult('skip').with_msg('Not installed') - - return result - - def cnr_install(self, node_id: str, version_spec=None, instant_execution=False, no_deps=False, return_postinstall=False): - result = ManagedResult('install-cnr') - - if 'comfyui-manager' in node_id.lower(): - return result.fail(f"ignored: installing '{node_id}'") - - node_info = cnr_utils.install_node(node_id, version_spec) - if node_info is None or not node_info.download_url: - return result.fail(f'not available node: {node_id}@{version_spec}') - - archive_name = f"CNR_temp_{str(uuid.uuid4())}.zip" # should be unpredictable name - security precaution - download_path = os.path.join(get_default_custom_nodes_path(), archive_name) - - # re-download. I cannot trust existing file. - if os.path.exists(download_path): - os.remove(download_path) - - # install_path - install_path = os.path.join(get_default_custom_nodes_path(), node_id) - if os.path.exists(install_path): - return result.fail(f'Install path already exists: {install_path}') - - manager_downloader.download_url(node_info.download_url, get_default_custom_nodes_path(), archive_name) - os.makedirs(install_path, exist_ok=True) - extracted = manager_util.extract_package_as_zip(download_path, install_path) - os.remove(download_path) - result.to_path = install_path - - if extracted is None: - shutil.rmtree(install_path) - return result.fail(f'Empty archive file: {node_id}@{version_spec}') - - # create .tracking file - tracking_info_file = os.path.join(install_path, '.tracking') - with open(tracking_info_file, "w", encoding='utf-8') as file: - file.write('\n'.join(extracted)) - - result.target = version_spec - - def postinstall(): - return self.execute_install_script(node_id, install_path, instant_execution=instant_execution, no_deps=no_deps) - - if return_postinstall: - return result.with_postinstall(postinstall) - else: - if not postinstall(): - return result.fail(f"Failed to execute install script: {node_id}@{version_spec}") - - return result - - def repo_install(self, url: str, repo_path: str, instant_execution=False, no_deps=False, return_postinstall=False): - result = ManagedResult('install-git') - result.append(url) - - if 'comfyui-manager' in url.lower(): - return result.fail(f"ignored: installing '{url}'") - - if not is_valid_url(url): - return result.fail(f"Invalid git url: {url}") - - if url.endswith("/"): - url = url[:-1] - try: - # Clone the repository from the remote URL - clone_url = git_utils.get_url_for_clone(url) - print(f"Download: git clone '{clone_url}'") - - if not instant_execution and platform.system() == 'Windows': - res = manager_funcs.run_script([sys.executable, git_script_path, "--clone", get_default_custom_nodes_path(), clone_url, repo_path], cwd=get_default_custom_nodes_path()) - if res != 0: - return result.fail(f"Failed to clone repo: {clone_url}") - else: - repo = git.Repo.clone_from(clone_url, repo_path, recursive=True, progress=GitProgress()) - repo.git.clear_cache() - repo.close() - - def postinstall(): - return self.execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps) - - if return_postinstall: - return result.with_postinstall(postinstall) - else: - if not postinstall(): - return result.fail(f"Failed to execute install script: {url}") - - except Exception as e: - traceback.print_exc() - return result.fail(f"Install(git-clone) error[2]: {url} / {e}") - - print("Installation was successful.") - return result - - def repo_update(self, repo_path, instant_execution=False, no_deps=False, return_postinstall=False): - result = ManagedResult('update-git') - - if not os.path.exists(os.path.join(repo_path, '.git')): - return result.fail(f'Path not found: {repo_path}') - - # version check - with git.Repo(repo_path) as repo: - if repo.head.is_detached: - if not switch_to_default_branch(repo): - return result.fail(f"Failed to switch to default branch: {repo_path}") - - current_branch = repo.active_branch - branch_name = current_branch.name - - if current_branch.tracking_branch() is None: - print(f"[ComfyUI-Manager] There is no tracking branch ({current_branch})") - remote_name = get_remote_name(repo) - else: - remote_name = current_branch.tracking_branch().remote_name - - if remote_name is None: - return result.fail(f"Failed to get remote when installing: {repo_path}") - - remote = repo.remote(name=remote_name) - - try: - remote.fetch() - except Exception as e: - if 'detected dubious' in str(e): - print(f"[ComfyUI-Manager] Try fixing 'dubious repository' error on '{repo_path}' repository") - safedir_path = repo_path.replace('\\', '/') - subprocess.run(['git', 'config', '--global', '--add', 'safe.directory', safedir_path]) - try: - remote.fetch() - except Exception: - print("\n[ComfyUI-Manager] Failed to fixing repository setup. Please execute this command on cmd: \n" - "-----------------------------------------------------------------------------------------\n" - f'git config --global --add safe.directory "{safedir_path}"\n' - "-----------------------------------------------------------------------------------------\n") - - commit_hash = repo.head.commit.hexsha - if f'{remote_name}/{branch_name}' in repo.refs: - remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha - else: - return result.fail(f"Not updatable branch: {branch_name}") - - if commit_hash != remote_commit_hash: - git_pull(repo_path) - - if len(repo.remotes) > 0: - url = repo.remotes[0].url - else: - url = "unknown repo" - - def postinstall(): - return self.execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps) - - if return_postinstall: - return result.with_postinstall(postinstall) - else: - if not postinstall(): - return result.fail(f"Failed to execute install script: {url}") - - return result - else: - return ManagedResult('skip').with_msg('Up to date') - - def unified_update(self, node_id, version_spec=None, instant_execution=False, no_deps=False, return_postinstall=False): - orig_print(f"\x1b[2K\rUpdating: {node_id}", end='') - - if version_spec is None: - version_spec = self.resolve_unspecified_version(node_id, guess_mode='active') - - if version_spec is None: - return ManagedResult('update').fail(f'Update not available: {node_id}@{version_spec}').with_ver(version_spec) - - if version_spec == 'nightly': - return self.repo_update(self.active_nodes[node_id][1], instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall).with_target('nightly').with_ver('nightly') - elif version_spec == 'unknown': - return self.repo_update(self.unknown_active_nodes[node_id][1], instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall).with_target('unknown').with_ver('unknown') - else: - return self.cnr_switch_version(node_id, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall).with_ver('cnr') - - async def install_by_id(self, node_id: str, version_spec=None, channel=None, mode=None, instant_execution=False, no_deps=False, return_postinstall=False): - """ - priority if version_spec == None - 1. CNR latest - 2. unknown - - remark: latest version_spec is not allowed. Must be resolved before call. - """ - - if 'comfyui-manager' in node_id.lower(): - return ManagedResult('skip').fail(f"ignored: installing '{node_id}'") - - repo_url = None - if version_spec is None: - if self.is_enabled(node_id): - return ManagedResult('skip') - elif self.is_disabled(node_id): - return self.unified_enable(node_id) - - else: - version_spec = self.resolve_unspecified_version(node_id) - - if version_spec == 'unknown' or version_spec == 'nightly': - try: - custom_nodes = await self.get_custom_nodes(channel, mode) - except InvalidChannel as e: - return ManagedResult('fail').fail(f'Invalid channel is used: {e.channel}') - - the_node = custom_nodes.get(node_id) - if the_node is not None: - if version_spec == 'unknown': - repo_url = the_node['files'][0] - else: # nightly - repo_url = the_node['repository'] - else: - result = ManagedResult('install') - return result.fail(f"Node '{node_id}@{version_spec}' not found in [{channel}, {mode}]") - - if self.is_enabled(node_id, version_spec): - return ManagedResult('skip').with_target(f"{node_id}@{version_spec}") - - elif self.is_disabled(node_id, version_spec): - return self.unified_enable(node_id, version_spec) - - elif version_spec == 'unknown' or version_spec == 'nightly': - if version_spec == 'nightly': - # disable cnr nodes - if self.is_enabled(node_id, 'cnr'): - self.unified_disable(node_id, False) - - to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), node_id)) - res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall) - if res.result: - if version_spec == 'unknown': - self.unknown_active_nodes[node_id] = repo_url, to_path - elif version_spec == 'nightly': - cnr_utils.generate_cnr_id(to_path, node_id) - self.active_nodes[node_id] = 'nightly', to_path - else: - return res - - return res.with_target(version_spec) - - if self.is_enabled(node_id, 'nightly'): - # disable nightly nodes - self.unified_disable(node_id, False) # NOTE: don't return from here - - if self.is_disabled(node_id, version_spec): - # enable and return if specified version is disabled - return self.unified_enable(node_id, version_spec) - - if self.is_disabled(node_id, "cnr"): - # enable and switch version if cnr is disabled (not specified version) - self.unified_enable(node_id, "cnr") - return self.cnr_switch_version(node_id, version_spec, no_deps=no_deps, return_postinstall=return_postinstall) - - if self.is_enabled(node_id, "cnr"): - return self.cnr_switch_version(node_id, version_spec, no_deps=no_deps, return_postinstall=return_postinstall) - - res = self.cnr_install(node_id, version_spec, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall) - if res.result: - self.active_nodes[node_id] = version_spec, res.to_path - - return res - - -unified_manager = UnifiedManager() - - -def identify_node_pack_from_path(fullpath): - module_name = os.path.basename(fullpath) - if module_name.endswith('.git'): - module_name = module_name[:-4] - - repo_url = git_utils.git_url(fullpath) - if repo_url is None: - # cnr - cnr = cnr_utils.read_cnr_info(fullpath) - if cnr is not None: - return module_name, cnr['version'], cnr['id'], None - - return None - else: - # nightly or unknown - cnr_id = cnr_utils.read_cnr_id(fullpath) - commit_hash = git_utils.get_commit_hash(fullpath) - - github_id = git_utils.normalize_to_github_id(repo_url) - if github_id is None: - try: - github_id = os.path.basename(repo_url) - except: - logging.warning(f"[ComfyUI-Manager] unexpected repo url: {repo_url}") - github_id = module_name - - if cnr_id is not None: - return module_name, commit_hash, cnr_id, github_id - else: - return module_name, commit_hash, '', github_id - - -def get_installed_node_packs(): - res = {} - - for x in get_custom_nodes_paths(): - for y in os.listdir(x): - if y == '__pycache__' or y == '.disabled': - continue - - fullpath = os.path.join(x, y) - info = identify_node_pack_from_path(fullpath) - if info is None: - continue - - is_disabled = not y.endswith('.disabled') - - res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': is_disabled } - - disabled_dirs = os.path.join(x, '.disabled') - if os.path.exists(disabled_dirs): - for y in os.listdir(disabled_dirs): - if y == '__pycache__': - continue - - fullpath = os.path.join(disabled_dirs, y) - info = identify_node_pack_from_path(fullpath) - if info is None: - continue - - res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': False } - - return res - - -def refresh_channel_dict(): - if channel_dict is None: - get_channel_dict() - - -def get_channel_dict(): - global channel_dict - global valid_channels - - if channel_dict is None: - channel_dict = {} - - if not os.path.exists(manager_channel_list_path): - shutil.copy(channel_list_template_path, manager_channel_list_path) - - with open(manager_channel_list_path, 'r') as file: - channels = file.read() - for x in channels.split('\n'): - channel_info = x.split("::") - if len(channel_info) == 2: - channel_dict[channel_info[0]] = channel_info[1] - valid_channels.add(channel_info[1]) - - return channel_dict - - -def get_channel_list(): - global channel_list - - if channel_list is None: - channel_list = [] - for k, v in get_channel_dict().items(): - channel_list.append(f"{k}::{v}") - - return channel_list - - -class ManagerFuncs: - def __init__(self): - pass - - def get_current_preview_method(self): - return "none" - - def run_script(self, cmd, cwd='.'): - if len(cmd) > 0 and cmd[0].startswith("#"): - print(f"[ComfyUI-Manager] Unexpected behavior: `{cmd}`") - return 0 - - subprocess.check_call(cmd, cwd=cwd, env=get_script_env()) - - return 0 - - -manager_funcs = ManagerFuncs() - - -def write_config(): - config = configparser.ConfigParser(strict=False) - - config['default'] = { - 'preview_method': manager_funcs.get_current_preview_method(), - 'git_exe': get_config()['git_exe'], - 'use_uv': get_config()['use_uv'], - 'channel_url': get_config()['channel_url'], - 'share_option': get_config()['share_option'], - 'bypass_ssl': get_config()['bypass_ssl'], - "file_logging": get_config()['file_logging'], - 'component_policy': get_config()['component_policy'], - 'update_policy': get_config()['update_policy'], - 'windows_selector_event_loop_policy': get_config()['windows_selector_event_loop_policy'], - 'model_download_by_agent': get_config()['model_download_by_agent'], - 'downgrade_blacklist': get_config()['downgrade_blacklist'], - 'security_level': get_config()['security_level'], - 'always_lazy_install': get_config()['always_lazy_install'], - 'network_mode': get_config()['network_mode'], - 'db_mode': get_config()['db_mode'], - } - - # Sanitize all string values to prevent CRLF injection attacks - for key, value in config['default'].items(): - if isinstance(value, str): - config['default'][key] = value.replace('\r', '').replace('\n', '').replace('\x00', '') - - directory = os.path.dirname(manager_config_path) - if not os.path.exists(directory): - os.makedirs(directory) - - with open(manager_config_path, 'w') as configfile: - config.write(configfile) - - -def read_config(): - try: - config = configparser.ConfigParser(strict=False) - config.read(manager_config_path) - default_conf = config['default'] - - def get_bool(key, default_value): - return default_conf[key].lower() == 'true' if key in default_conf else False - - manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False - manager_util.bypass_ssl = get_bool('bypass_ssl', False) - - result = { - 'http_channel_enabled': get_bool('http_channel_enabled', False), - 'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(), - 'git_exe': default_conf.get('git_exe', ''), - 'use_uv': get_bool('use_uv', False), - 'channel_url': default_conf.get('channel_url', DEFAULT_CHANNEL), - 'default_cache_as_channel_url': get_bool('default_cache_as_channel_url', False), - 'share_option': default_conf.get('share_option', 'all').lower(), - 'bypass_ssl': get_bool('bypass_ssl', False), - 'file_logging': get_bool('file_logging', True), - 'component_policy': default_conf.get('component_policy', 'workflow').lower(), - 'update_policy': default_conf.get('update_policy', 'stable-comfyui').lower(), - 'windows_selector_event_loop_policy': get_bool('windows_selector_event_loop_policy', False), - 'model_download_by_agent': get_bool('model_download_by_agent', False), - 'downgrade_blacklist': default_conf.get('downgrade_blacklist', '').lower(), - 'always_lazy_install': get_bool('always_lazy_install', False), - 'network_mode': default_conf.get('network_mode', 'public').lower(), - 'security_level': default_conf.get('security_level', 'normal').lower(), - 'db_mode': default_conf.get('db_mode', 'cache').lower(), - } - manager_migration.force_security_level_if_needed(result) - return result - - except Exception: - import importlib.util - # temporary disable `uv` on Windows by default (https://github.com/Comfy-Org/ComfyUI-Manager/issues/1969) - manager_util.use_uv = importlib.util.find_spec("uv") is not None and platform.system() != "Windows" - manager_util.bypass_ssl = False - - result = { - 'http_channel_enabled': False, - 'preview_method': manager_funcs.get_current_preview_method(), - 'git_exe': '', - 'use_uv': manager_util.use_uv, - 'channel_url': DEFAULT_CHANNEL, - 'default_cache_as_channel_url': False, - 'share_option': 'all', - 'bypass_ssl': manager_util.bypass_ssl, - 'file_logging': True, - 'component_policy': 'workflow', - 'update_policy': 'stable-comfyui', - 'windows_selector_event_loop_policy': False, - 'model_download_by_agent': False, - 'downgrade_blacklist': '', - 'always_lazy_install': False, - 'network_mode': 'public', # public | private | offline - 'security_level': 'normal', # strong | normal | normal- | weak - 'db_mode': 'cache', # local | cache | remote - } - manager_migration.force_security_level_if_needed(result) - return result - - -def get_config(): - global cached_config - - if cached_config is None: - cached_config = read_config() - if cached_config['http_channel_enabled']: - print("[ComfyUI-Manager] Warning: http channel enabled, make sure server in secure env") - - return cached_config - - -def get_remote_name(repo): - available_remotes = [remote.name for remote in repo.remotes] - if 'origin' in available_remotes: - return 'origin' - elif 'upstream' in available_remotes: - return 'upstream' - elif len(available_remotes) > 0: - return available_remotes[0] - - if not available_remotes: - print(f"[ComfyUI-Manager] No remotes are configured for this repository: {repo.working_dir}") - else: - print(f"[ComfyUI-Manager] Available remotes in '{repo.working_dir}': ") - for remote in available_remotes: - print(f"- {remote}") - - return None - - -def switch_to_default_branch(repo): - remote_name = get_remote_name(repo) - - try: - if remote_name is None: - return False - - default_branch = repo.git.symbolic_ref(f'refs/remotes/{remote_name}/HEAD').replace(f'refs/remotes/{remote_name}/', '') - repo.git.checkout(default_branch) - return True - except: - # try checkout master - # try checkout main if failed - try: - repo.git.checkout(repo.heads.master) - return True - except: - try: - if remote_name is not None: - repo.git.checkout('-b', 'master', f'{remote_name}/master') - return True - except: - try: - repo.git.checkout(repo.heads.main) - return True - except: - try: - if remote_name is not None: - repo.git.checkout('-b', 'main', f'{remote_name}/main') - return True - except: - pass - - print("[ComfyUI Manager] Failed to switch to the default branch") - return False - - -def reserve_script(repo_path, install_cmds): - if not os.path.exists(manager_startup_script_path): - os.makedirs(manager_startup_script_path) - - script_path = os.path.join(manager_startup_script_path, "install-scripts.txt") - with open(script_path, "a") as file: - obj = [repo_path] + install_cmds - file.write(f"{obj}\n") - - -def try_rmtree(title, fullpath): - try: - shutil.rmtree(fullpath) - except Exception as e: - logging.warning(f"[ComfyUI-Manager] An error occurred while deleting '{fullpath}', so it has been scheduled for deletion upon restart.\nEXCEPTION: {e}") - reserve_script(title, ["#LAZY-DELETE-NODEPACK", fullpath]) - - -def try_install_script(url, repo_path, install_cmd, instant_execution=False): - if not instant_execution and ( - (len(install_cmd) > 0 and install_cmd[0].startswith('#')) or platform.system() == "Windows" or get_config()['always_lazy_install'] - ): - reserve_script(repo_path, install_cmd) - return True - else: - if len(install_cmd) == 5 and install_cmd[2:4] == ['pip', 'install']: - if is_blacklisted(install_cmd[4]): - print(f"[ComfyUI-Manager] skip black listed pip installation: '{install_cmd[4]}'") - return True - elif len(install_cmd) == 6 and install_cmd[3:5] == ['pip', 'install']: # uv mode - if is_blacklisted(install_cmd[5]): - print(f"[ComfyUI-Manager] skip black listed pip installation: '{install_cmd[5]}'") - return True - - 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: - pass - - if code != 0: - if url is None: - url = os.path.dirname(repo_path) - print(f"install script failed: {url}") - return False - - return True - - -# use subprocess to avoid file system lock by git (Windows) -def __win_check_git_update(path, do_fetch=False, do_update=False): - if do_fetch: - command = [sys.executable, git_script_path, "--fetch", path] - elif do_update: - command = [sys.executable, git_script_path, "--pull", path] - else: - command = [sys.executable, git_script_path, "--check", path] - - new_env = get_script_env() - process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=get_default_custom_nodes_path(), env=new_env) - output, _ = process.communicate() - output = output.decode('utf-8').strip() - - if 'detected dubious' in output: - # fix and try again - safedir_path = path.replace('\\', '/') - try: - print(f"[ComfyUI-Manager] Try fixing 'dubious repository' error on '{safedir_path}' repo") - process = subprocess.Popen(['git', 'config', '--global', '--add', 'safe.directory', safedir_path], env=new_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output, _ = process.communicate() - - process = subprocess.Popen(command, env=new_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output, _ = process.communicate() - output = output.decode('utf-8').strip() - except Exception: - print('[ComfyUI-Manager] failed to fixing') - - if 'detected dubious' in output: - print(f'\n[ComfyUI-Manager] Failed to fixing repository setup. Please execute this command on cmd: \n' - f'-----------------------------------------------------------------------------------------\n' - f'git config --global --add safe.directory "{safedir_path}"\n' - f'-----------------------------------------------------------------------------------------\n') - - if do_update: - if "CUSTOM NODE PULL: Success" in output: - process.wait() - print(f"\x1b[2K\rUpdated: {path}") - return True, True # updated - elif "CUSTOM NODE PULL: None" in output: - process.wait() - return False, True # there is no update - else: - print(f"\x1b[2K\rUpdate error: {path}") - process.wait() - return False, False # update failed - else: - if "CUSTOM NODE CHECK: True" in output: - process.wait() - return True, True - elif "CUSTOM NODE CHECK: False" in output: - process.wait() - return False, True - else: - print(f"\x1b[2K\rFetch error: {path}") - print(f"\n{output}\n") - process.wait() - return False, True - - -def __win_check_git_pull(path): - command = [sys.executable, git_script_path, "--pull", path] - process = subprocess.Popen(command, env=get_script_env(), cwd=get_default_custom_nodes_path()) - process.wait() - - -def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=False, no_deps=False): - # import ipdb; ipdb.set_trace() - install_script_path = os.path.join(repo_path, "install.py") - requirements_path = os.path.join(repo_path, "requirements.txt") - - if lazy_mode: - install_cmd = ["#LAZY-INSTALL-SCRIPT", sys.executable] - try_install_script(url, repo_path, install_cmd) - else: - if os.path.exists(requirements_path) and not no_deps: - print("Install: pip packages") - pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path) - with open(requirements_path, "r") as requirements_file: - for line in requirements_file: - #handle comments - if '#' in line: - if line.strip()[0] == '#': - print("Line is comment...skipping") - continue - else: - line = line.split('#')[0].strip() - - package_name = remap_pip_package(line.strip()) - - if package_name and not package_name.startswith('#'): - if '--index-url' in package_name: - s = package_name.split('--index-url') - install_cmd = manager_util.make_pip_cmd(["install", s[0].strip(), '--index-url', s[1].strip()]) - else: - install_cmd = manager_util.make_pip_cmd(["install", package_name]) - - if package_name.strip() != "" and not package_name.startswith('#'): - try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) - pip_fixer.fix_broken() - - if os.path.exists(install_script_path): - print("Install: install script") - install_cmd = [sys.executable, "install.py"] - try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) - - return True - - -def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=False): - """ - - perform update check for git custom node - and fetch or update if flag is on - - :param path: path to git custom node - :param do_fetch: do fetch during check - :param do_update: do update during check - :param no_deps: don't install dependencies - :return: update state * success - """ - if do_fetch: - orig_print(f"\x1b[2K\rFetching: {path}", end='') - elif do_update: - orig_print(f"\x1b[2K\rUpdating: {path}", end='') - - # Check if the path is a git repository - if not os.path.exists(os.path.join(path, '.git')): - raise ValueError(f'[ComfyUI-Manager] Not a valid git repository: {path}') - - if platform.system() == "Windows": - updated, success = __win_check_git_update(path, do_fetch, do_update) - if updated and success: - execute_install_script(None, path, lazy_mode=True, no_deps=no_deps) - return updated, success - else: - # Fetch the latest commits from the remote repository - repo = git.Repo(path) - - remote_name = get_remote_name(repo) - - if remote_name is None: - raise ValueError(f"No remotes are configured for this repository: {path}") - - remote = repo.remote(name=remote_name) - - if not do_update and repo.head.is_detached: - if do_fetch: - remote.fetch() - - return True, True # detached branch is treated as updatable - - if repo.head.is_detached: - if not switch_to_default_branch(repo): - raise ValueError(f"Failed to switch detached branch to default branch: {path}") - - current_branch = repo.active_branch - branch_name = current_branch.name - - # Get the current commit hash - commit_hash = repo.head.commit.hexsha - - if do_fetch or do_update: - remote.fetch() - - if do_update: - if repo.is_dirty(): - print(f"\nSTASH: '{path}' is dirty.") - repo.git.stash() - - if f'{remote_name}/{branch_name}' not in repo.refs: - if not switch_to_default_branch(repo): - raise ValueError(f"Failed to switch to default branch while updating: {path}") - - current_branch = repo.active_branch - branch_name = current_branch.name - - if f'{remote_name}/{branch_name}' in repo.refs: - remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha - else: - return False, False - - if commit_hash == remote_commit_hash: - repo.close() - return False, True - - try: - remote.pull() - repo.git.submodule('update', '--init', '--recursive') - new_commit_hash = repo.head.commit.hexsha - - if commit_hash != new_commit_hash: - execute_install_script(None, path, no_deps=no_deps) - print(f"\x1b[2K\rUpdated: {path}") - return True, True - else: - return False, False - - except Exception as e: - print(f"\nUpdating failed: {path}\n{e}", file=sys.stderr) - return False, False - - if repo.head.is_detached: - repo.close() - return True, True - - # Get commit hash of the remote branch - current_branch = repo.active_branch - branch_name = current_branch.name - - if f'{remote_name}/{branch_name}' in repo.refs: - remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha - else: - return True, True # Assuming there's an update if it's not the default branch. - - # Compare the commit hashes to determine if the local repository is behind the remote repository - if commit_hash != remote_commit_hash: - # Get the commit dates - commit_date = repo.head.commit.committed_datetime - remote_commit_date = repo.refs[f'{remote_name}/{branch_name}'].object.committed_datetime - - # Compare the commit dates to determine if the local repository is behind the remote repository - if commit_date < remote_commit_date: - repo.close() - return True, True - - repo.close() - - return False, True - - -class GitProgress(RemoteProgress): - def __init__(self): - super().__init__() - self.pbar = tqdm() - - def update(self, op_code, cur_count, max_count=None, message=''): - self.pbar.total = max_count - self.pbar.n = cur_count - self.pbar.pos = 0 - self.pbar.refresh() - - -def is_valid_url(url): - try: - # Check for HTTP/HTTPS URL format - result = urlparse(url) - if all([result.scheme, result.netloc]): - return True - finally: - # Check for SSH git URL format - pattern = re.compile(r"^(.+@|ssh://).+:.+$") - if pattern.match(url): - return True - return False - - -def extract_url_and_commit_id(s): - index = s.rfind('@') - if index == -1: - return (s, '') - else: - return (s[:index], s[index+1:]) - -async def gitclone_install(url, instant_execution=False, msg_prefix='', no_deps=False): - await unified_manager.reload('cache') - await unified_manager.get_custom_nodes('default', 'cache') - - print(f"{msg_prefix}Install: {url}") - - result = ManagedResult('install-git') - - if not is_valid_url(url): - return result.fail(f"Invalid git url: '{url}'") - - if url.endswith("/"): - url = url[:-1] - try: - cnr = unified_manager.get_cnr_by_repo(url) - if cnr: - cnr_id = cnr['id'] - return await unified_manager.install_by_id(cnr_id, version_spec=None, channel='default', mode='cache') - else: - new_url, commit_id = extract_url_and_commit_id(url) - if commit_id != "": - url = new_url - repo_name = os.path.splitext(os.path.basename(url))[0] - - # NOTE: Keep original name as possible if unknown node - # node_dir = f"{repo_name}@unknown" - node_dir = repo_name - - repo_path = os.path.join(get_default_custom_nodes_path(), node_dir) - - if os.path.exists(repo_path): - return result.fail(f"Already exists: '{repo_path}'") - - for custom_nodes_dir in get_custom_nodes_paths(): - disabled_repo_path1 = os.path.join(custom_nodes_dir, '.disabled', node_dir) - disabled_repo_path2 = os.path.join(custom_nodes_dir, repo_name+'.disabled') # old style - - if os.path.exists(disabled_repo_path1): - return result.fail(f"Already exists (disabled): '{disabled_repo_path1}'") - - if os.path.exists(disabled_repo_path2): - return result.fail(f"Already exists (disabled): '{disabled_repo_path2}'") - - print(f"CLONE into '{repo_path}'") - - # Clone the repository from the remote URL - clone_url = git_utils.get_url_for_clone(url) - - if not instant_execution and platform.system() == 'Windows': - res = manager_funcs.run_script([sys.executable, git_script_path, "--clone", get_default_custom_nodes_path(), clone_url, repo_path], cwd=get_default_custom_nodes_path()) - if res != 0: - return result.fail(f"Failed to clone '{clone_url}' into '{repo_path}'") - else: - repo = git.Repo.clone_from(clone_url, repo_path, recursive=True, progress=GitProgress()) - if commit_id!= "": - repo.git.checkout(commit_id) - repo.git.submodule('update', '--init', '--recursive') - - repo.git.clear_cache() - repo.close() - - execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps) - print("Installation was successful.") - return result.with_target(repo_path) - - except Exception as e: - traceback.print_exc() - print(f"Install(git-clone) error[1]: {url} / {e}", file=sys.stderr) - return result.fail(f"Install(git-clone)[1] error: {url} / {e}") - - -def git_pull(path): - # Check if the path is a git repository - if not os.path.exists(os.path.join(path, '.git')): - raise ValueError('Not a git repository') - - # Pull the latest changes from the remote repository - if platform.system() == "Windows": - return __win_check_git_pull(path) - else: - repo = git.Repo(path) - - if repo.is_dirty(): - print(f"STASH: '{path}' is dirty.") - repo.git.stash() - - if repo.head.is_detached: - if not switch_to_default_branch(repo): - raise ValueError(f"Failed to switch to default branch while pulling: {path}") - - current_branch = repo.active_branch - remote_name = current_branch.tracking_branch().remote_name - - try: - repo.git.pull('--ff-only') - except git.GitCommandError: - branch_name = current_branch.name - backup_name = f'backup_{time.strftime("%Y%m%d_%H%M%S")}' - repo.create_head(backup_name) - logging.info(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}") - repo.git.reset('--hard', f'{remote_name}/{branch_name}') - logging.info(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}") - - repo.git.submodule('update', '--init', '--recursive') - - repo.close() - - return True - - -async def get_data_by_mode(mode, filename, channel_url=None): - if channel_url in get_channel_dict(): - channel_url = get_channel_dict()[channel_url] - - try: - local_uri = os.path.join(manager_util.comfyui_manager_path, filename) - - if mode == "local": - json_obj = await manager_util.get_data(local_uri) - else: - if channel_url is None: - uri = get_config()['channel_url'] + '/' + filename - else: - uri = channel_url + '/' + filename - - cache_uri = str(manager_util.simple_hash(uri))+'_'+filename - cache_uri = os.path.join(manager_util.cache_dir, cache_uri) - - if get_config()['network_mode'] == 'offline': - # offline network mode - if os.path.exists(cache_uri): - json_obj = await manager_util.get_data(cache_uri) - else: - local_uri = os.path.join(manager_util.comfyui_manager_path, filename) - if os.path.exists(local_uri): - json_obj = await manager_util.get_data(local_uri) - else: - json_obj = {} # fallback - else: - # public network mode - if mode == "cache" and manager_util.is_file_created_within_one_day(cache_uri): - json_obj = await manager_util.get_data(cache_uri) - else: - json_obj = await manager_util.get_data(uri) - with manager_util.cache_lock: - with open(cache_uri, "w", encoding='utf-8') as file: - json.dump(json_obj, file, indent=4, sort_keys=True) - except Exception as e: - print(f"[ComfyUI-Manager] Due to a network error, switching to local mode.\n=> {filename}\n=> {e}") - uri = os.path.join(manager_util.comfyui_manager_path, filename) - json_obj = await manager_util.get_data(uri) - - return json_obj - - -def gitclone_fix(files, instant_execution=False, no_deps=False): - print(f"Try fixing: {files}") - for url in files: - if not is_valid_url(url): - print(f"Invalid git url: '{url}'") - return False - - if url.endswith("/"): - url = url[:-1] - try: - repo_name = os.path.splitext(os.path.basename(url))[0] - repo_path = os.path.join(get_default_custom_nodes_path(), repo_name) - - if os.path.exists(repo_path+'.disabled'): - repo_path = repo_path+'.disabled' - - if not execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps): - return False - - except Exception as e: - print(f"Fix(git-clone) error: {url} / {e}", file=sys.stderr) - return False - - print(f"Attempt to fixing '{files}' is done.") - return True - - -def pip_install(packages): - install_cmd = ['#FORCE'] + manager_util.make_pip_cmd(["install", '-U']) + packages - try_install_script('pip install via manager', '..', install_cmd) - - -def rmtree(path): - retry_count = 3 - - while True: - try: - retry_count -= 1 - - if platform.system() == "Windows": - manager_funcs.run_script(['attrib', '-R', path + '\\*', '/S']) - shutil.rmtree(path) - - return True - - except Exception as ex: - print(f"ex: {ex}") - time.sleep(3) - - if retry_count < 0: - raise ex - - print(f"Uninstall retry({retry_count})") - - -def gitclone_uninstall(files): - import os - - print(f"Uninstall: {files}") - for url in files: - if url.endswith("/"): - url = url[:-1] - try: - for custom_nodes_dir in get_custom_nodes_paths(): - dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") - dir_path = os.path.join(custom_nodes_dir, dir_name) - - # safety check - if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '': - print(f"Uninstall(git-clone) error: invalid path '{dir_path}' for '{url}'") - return False - - install_script_path = os.path.join(dir_path, "uninstall.py") - disable_script_path = os.path.join(dir_path, "disable.py") - if os.path.exists(install_script_path): - uninstall_cmd = [sys.executable, "uninstall.py"] - code = manager_funcs.run_script(uninstall_cmd, cwd=dir_path) - - if code != 0: - print(f"An error occurred during the execution of the uninstall.py script. Only the '{dir_path}' will be deleted.") - elif os.path.exists(disable_script_path): - disable_script = [sys.executable, "disable.py"] - code = manager_funcs.run_script(disable_script, cwd=dir_path) - if code != 0: - print(f"An error occurred during the execution of the disable.py script. Only the '{dir_path}' will be deleted.") - - if os.path.exists(dir_path): - rmtree(dir_path) - elif os.path.exists(dir_path + ".disabled"): - rmtree(dir_path + ".disabled") - except Exception as e: - print(f"Uninstall(git-clone) error: {url} / {e}", file=sys.stderr) - return False - - print("Uninstallation was successful.") - return True - - -def gitclone_set_active(files, is_disable): - import os - - if is_disable: - action_name = "Disable" - else: - action_name = "Enable" - - print(f"{action_name}: {files}") - for url in files: - if url.endswith("/"): - url = url[:-1] - try: - for custom_nodes_dir in get_custom_nodes_paths(): - dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") - dir_path = os.path.join(custom_nodes_dir, dir_name) - - # safety check - if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '': - print(f"{action_name}(git-clone) error: invalid path '{dir_path}' for '{url}'") - return False - - if is_disable: - current_path = dir_path - base_path = extract_base_custom_nodes_dir(current_path) - new_path = os.path.join(base_path, ".disabled", dir_name) - - if not os.path.exists(current_path): - continue - else: - current_path1 = os.path.join(get_default_custom_nodes_path(), ".disabled", dir_name) - current_path2 = dir_path + ".disabled" - - if os.path.exists(current_path1): - current_path = current_path1 - elif os.path.exists(current_path2): - current_path = current_path2 - else: - continue - - base_path = extract_base_custom_nodes_dir(current_path) - new_path = os.path.join(base_path, dir_name) - - shutil.move(current_path, new_path) - - if is_disable: - if os.path.exists(os.path.join(new_path, "disable.py")): - disable_script = [sys.executable, "disable.py"] - try_install_script(url, new_path, disable_script) - else: - if os.path.exists(os.path.join(new_path, "enable.py")): - enable_script = [sys.executable, "enable.py"] - try_install_script(url, new_path, enable_script) - - break # for safety - - except Exception as e: - print(f"{action_name}(git-clone) error: {url} / {e}", file=sys.stderr) - return False - - print(f"{action_name} was successful.") - return True - - -def gitclone_update(files, instant_execution=False, skip_script=False, msg_prefix="", no_deps=False): - import os - - print(f"{msg_prefix}Update: {files}") - for url in files: - if url.endswith("/"): - url = url[:-1] - try: - for custom_nodes_dir in get_default_custom_nodes_path(): - repo_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") - repo_path = os.path.join(custom_nodes_dir, repo_name) - - if os.path.exists(repo_path+'.disabled'): - repo_path = repo_path+'.disabled' - - elif os.path.exists(os.path.join(get_default_custom_nodes_path(), "disabled", repo_name)): - repo_path = os.path.join(get_default_custom_nodes_path(), "disabled", repo_name) - - if not os.path.exists(repo_path): - continue - - git_pull(repo_path) - - if not skip_script: - if instant_execution: - if not execute_install_script(url, repo_path, lazy_mode=False, instant_execution=True, no_deps=no_deps): - return False - else: - if not execute_install_script(url, repo_path, lazy_mode=True, no_deps=no_deps): - return False - - break # for safety - - except Exception as e: - print(f"Update(git-clone) error: {url} / {e}", file=sys.stderr) - return False - - if not skip_script: - print("Update was successful.") - return True - - -def update_to_stable_comfyui(repo_path): - try: - repo = git.Repo(repo_path) - try: - repo.git.checkout(repo.heads.master) - except: - logging.error(f"[ComfyUI-Manager] Failed to checkout 'master' branch.\nrepo_path={repo_path}\nAvailable branches:") - for branch in repo.branches: - logging.error('\t'+branch.name) - return "fail", None - - versions, current_tag, latest_tag = get_comfyui_versions(repo) - - if latest_tag is None: - logging.info("[ComfyUI-Manager] Unable to update to the stable ComfyUI version.") - return "fail", None - - tag_ref = next((t for t in repo.tags if t.name == latest_tag), None) - if tag_ref is None: - logging.info(f"[ComfyUI-Manager] Unable to locate tag '{latest_tag}' in repository.") - return "fail", None - - if repo.head.commit == tag_ref.commit: - return "skip", None - else: - logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}") - repo.git.checkout(tag_ref.name) - execute_install_script("ComfyUI", repo_path, instant_execution=False, no_deps=False) - return 'updated', latest_tag - except: - traceback.print_exc() - return "fail", None - - -def update_path(repo_path, instant_execution=False, no_deps=False): - if not os.path.exists(os.path.join(repo_path, '.git')): - return "fail" - - # version check - repo = git.Repo(repo_path) - - is_switched = False - if repo.head.is_detached: - if not switch_to_default_branch(repo): - return "fail" - else: - is_switched = True - - current_branch = repo.active_branch - branch_name = current_branch.name - - if current_branch.tracking_branch() is None: - print(f"[ComfyUI-Manager] There is no tracking branch ({current_branch})") - remote_name = get_remote_name(repo) - else: - remote_name = current_branch.tracking_branch().remote_name - remote = repo.remote(name=remote_name) - - try: - remote.fetch() - except Exception as e: - if 'detected dubious' in str(e): - print(f"[ComfyUI-Manager] Try fixing 'dubious repository' error on '{repo_path}' repository") - safedir_path = repo_path.replace('\\', '/') - subprocess.run(['git', 'config', '--global', '--add', 'safe.directory', safedir_path]) - try: - remote.fetch() - except Exception: - print(f"\n[ComfyUI-Manager] Failed to fixing repository setup. Please execute this command on cmd: \n" - f"-----------------------------------------------------------------------------------------\n" - f'git config --global --add safe.directory "{safedir_path}"\n' - f"-----------------------------------------------------------------------------------------\n") - return "fail" - - commit_hash = repo.head.commit.hexsha - - if f'{remote_name}/{branch_name}' in repo.refs: - remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha - else: - return "fail" - - if commit_hash != remote_commit_hash: - git_pull(repo_path) - execute_install_script("ComfyUI", repo_path, instant_execution=instant_execution, no_deps=no_deps) - return "updated" - elif is_switched: - return "updated" - else: - return "skipped" - - -def lookup_customnode_by_url(data, target): - for x in data['custom_nodes']: - if target in x['files']: - for custom_nodes_dir in get_custom_nodes_paths(): - dir_name = os.path.splitext(os.path.basename(target))[0].replace(".git", "") - dir_path = os.path.join(custom_nodes_dir, dir_name) - if os.path.exists(dir_path): - x['installed'] = 'True' - else: - disabled_path1 = os.path.join(custom_nodes_dir, '.disabled', dir_name) - disabled_path2 = dir_path + ".disabled" - - if os.path.exists(disabled_path1) or os.path.exists(disabled_path2): - x['installed'] = 'Disabled' - else: - continue - - return x - - return None - - -def lookup_installed_custom_nodes_legacy(repo_name): - base_paths = get_custom_nodes_paths() - - for base_path in base_paths: - repo_path = os.path.join(base_path, repo_name) - if os.path.exists(repo_path): - return True, repo_path - elif os.path.exists(repo_path + '.disabled'): - return False, repo_path - - return None - - -def simple_check_custom_node(url): - dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") - dir_path = os.path.join(get_default_custom_nodes_path(), dir_name) - if os.path.exists(dir_path): - return 'installed' - elif os.path.exists(dir_path+'.disabled'): - return 'disabled' - - return 'not-installed' - - -def check_state_of_git_node_pack_single(item, do_fetch=False, do_update_check=True, do_update=False): - if item['version'] == 'unknown': - dir_path = unified_manager.unknown_active_nodes.get(item['id'])[1] - elif item['version'] == 'nightly': - dir_path = unified_manager.active_nodes.get(item['id'])[1] - else: - # skip CNR nodes - dir_path = None - - if dir_path and os.path.exists(dir_path): - if do_update_check: - try: - update_state, success = git_repo_update_check_with(dir_path, do_fetch, do_update) - if (do_update_check or do_update) and update_state: - item['update-state'] = 'true' - elif do_update and not success: - item['update-state'] = 'fail' - except Exception: - print(f"[ComfyUI-Manager] Failed to check state of the git node pack: {dir_path}") - - -def get_installed_pip_packages(): - 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: - if x.strip() == "": - continue - - if ' @ ' in x: - spec_url = x.split(' @ ') - res[spec_url[0]] = spec_url[1] - else: - res[x] = "" - - return res - - -async def get_current_snapshot(custom_nodes_only = False): - await unified_manager.reload('cache') - await unified_manager.get_custom_nodes('default', 'cache') - - # Get ComfyUI hash - repo_path = comfy_path - - comfyui_commit_hash = None - if not custom_nodes_only: - if os.path.exists(os.path.join(repo_path, '.git')): - repo = git.Repo(repo_path) - comfyui_commit_hash = repo.head.commit.hexsha - - git_custom_nodes = {} - cnr_custom_nodes = {} - file_custom_nodes = [] - - # Get custom nodes hash - for custom_nodes_dir in get_custom_nodes_paths(): - paths = os.listdir(custom_nodes_dir) - - disabled_path = os.path.join(custom_nodes_dir, '.disabled') - if os.path.exists(disabled_path): - for x in os.listdir(disabled_path): - paths.append(os.path.join(disabled_path, x)) - - for path in paths: - if path in ['.disabled', '__pycache__']: - continue - - fullpath = os.path.join(custom_nodes_dir, path) - - if os.path.isdir(fullpath): - is_disabled = path.endswith(".disabled") or os.path.basename(os.path.dirname(fullpath)) == ".disabled" - - try: - info = unified_manager.resolve_from_path(fullpath) - - if info is None: - continue - - if info['ver'] not in ['nightly', 'latest', 'unknown']: - if is_disabled: - continue # don't restore disabled state of CNR node. - - cnr_custom_nodes[info['id']] = info['ver'] - else: - commit_hash = git_utils.get_commit_hash(fullpath) - url = git_utils.git_url(fullpath) - git_custom_nodes[url] = dict(hash=commit_hash, disabled=is_disabled) - except: - print(f"Failed to extract snapshots for the custom node '{path}'.") - - elif path.endswith('.py'): - is_disabled = path.endswith(".py.disabled") - filename = os.path.basename(path) - item = { - 'filename': filename, - 'disabled': is_disabled - } - - file_custom_nodes.append(item) - - pip_packages = None if custom_nodes_only else get_installed_pip_packages() - - return { - 'comfyui': comfyui_commit_hash, - 'git_custom_nodes': git_custom_nodes, - 'cnr_custom_nodes': cnr_custom_nodes, - 'file_custom_nodes': file_custom_nodes, - 'pips': pip_packages, - } - - -async def save_snapshot_with_postfix(postfix, path=None, custom_nodes_only = False): - if path is None: - now = datetime.now() - - date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") - file_name = f"{date_time_format}_{postfix}" - - path = os.path.join(manager_snapshot_path, f"{file_name}.json") - else: - file_name = path.replace('\\', '/').split('/')[-1] - file_name = file_name.split('.')[-2] - - snapshot = await get_current_snapshot(custom_nodes_only) - if path.endswith('.json'): - with open(path, "w") as json_file: - json.dump(snapshot, json_file, indent=4) - - return file_name + '.json' - - elif path.endswith('.yaml'): - with open(path, "w") as yaml_file: - snapshot = {'custom_nodes': snapshot} - yaml.dump(snapshot, yaml_file, allow_unicode=True) - - return path - - -async def extract_nodes_from_workflow(filepath, mode='local', channel_url='default'): - # prepare json data - workflow = None - if filepath.endswith('.json'): - with open(filepath, "r", encoding="UTF-8", errors="ignore") as json_file: - try: - workflow = json.load(json_file) - except: - print(f"Invalid workflow file: {filepath}") - exit(-1) - - elif filepath.endswith('.png'): - from PIL import Image - with Image.open(filepath) as img: - if 'workflow' not in img.info: - print(f"The specified .png file doesn't have a workflow: {filepath}") - exit(-1) - else: - try: - workflow = json.loads(img.info['workflow']) - except: - print(f"This is not a valid .png file containing a ComfyUI workflow: {filepath}") - exit(-1) - - if workflow is None: - print(f"Invalid workflow file: {filepath}") - exit(-1) - - # extract nodes - used_nodes = set() - - def extract_nodes(sub_workflow): - for x in sub_workflow['nodes']: - node_name = x.get('type') - - # skip virtual nodes - if node_name in ['Reroute', 'Note']: - continue - - if node_name is not None and not (node_name.startswith('workflow/') or node_name.startswith('workflow>')): - used_nodes.add(node_name) - - if 'nodes' in workflow: - extract_nodes(workflow) - - if 'extra' in workflow: - if 'groupNodes' in workflow['extra']: - for x in workflow['extra']['groupNodes'].values(): - extract_nodes(x) - - # lookup dependent custom nodes - ext_map = await get_data_by_mode(mode, 'extension-node-map.json', channel_url) - - rext_map = {} - preemption_map = {} - patterns = [] - for k, v in ext_map.items(): - if k == 'https://github.com/comfyanonymous/ComfyUI': - for x in v[0]: - if x not in preemption_map: - preemption_map[x] = [] - - preemption_map[x] = k - continue - - for x in v[0]: - if x not in rext_map: - rext_map[x] = [] - - rext_map[x].append(k) - - if 'preemptions' in v[1]: - for x in v[1]['preemptions']: - if x not in preemption_map: - preemption_map[x] = [] - - preemption_map[x] = k - - if 'nodename_pattern' in v[1]: - patterns.append((v[1]['nodename_pattern'], k)) - - # identify used extensions - used_exts = set() - unknown_nodes = set() - - for node_name in used_nodes: - ext = preemption_map.get(node_name) - - if ext is None: - ext = rext_map.get(node_name) - if ext is not None: - ext = ext[0] - - if ext is None: - for pat_ext in patterns: - if re.search(pat_ext[0], node_name): - ext = pat_ext[1] - break - - if ext == 'https://github.com/comfyanonymous/ComfyUI': - pass - elif ext is not None: - used_exts.add(ext) - else: - unknown_nodes.add(node_name) - - return used_exts, unknown_nodes - - -def unzip(model_path): - if not os.path.exists(model_path): - print(f"[ComfyUI-Manager] unzip: File not found: {model_path}") - return False - - base_dir = os.path.dirname(model_path) - filename = os.path.basename(model_path) - target_dir = os.path.join(base_dir, filename[:-4]) - - os.makedirs(target_dir, exist_ok=True) - - with zipfile.ZipFile(model_path, 'r') as zip_ref: - zip_ref.extractall(target_dir) - - # Check if there's only one directory inside the target directory - contents = os.listdir(target_dir) - if len(contents) == 1 and os.path.isdir(os.path.join(target_dir, contents[0])): - nested_dir = os.path.join(target_dir, contents[0]) - # Move each file and sub-directory in the nested directory up to the target directory - for item in os.listdir(nested_dir): - shutil.move(os.path.join(nested_dir, item), os.path.join(target_dir, item)) - # Remove the now empty nested directory - os.rmdir(nested_dir) - - os.remove(model_path) - return True - - -def map_to_unified_keys(json_obj): - res = {} - for k, v in json_obj.items(): - cnr = unified_manager.get_cnr_by_repo(k) - if cnr: - res[cnr['id']] = v - else: - res[k] = v - - return res - - -async def get_unified_total_nodes(channel, mode, regsitry_cache_mode='cache'): - await unified_manager.reload(regsitry_cache_mode) - - res = await unified_manager.get_custom_nodes(channel, mode) - - # collect pure cnr ids (i.e. not exists in custom-node-list.json) - # populate state/updatable field to non-pure cnr nodes - cnr_ids = set(unified_manager.cnr_map.keys()) - for k, v in res.items(): - # resolve cnr_id from repo url - files_in_json = v.get('files', []) - cnr_id = None - if len(files_in_json) == 1: - cnr = unified_manager.get_cnr_by_repo(files_in_json[0]) - if cnr: - cnr_id = cnr['id'] - - if cnr_id is not None: - # cnr or nightly version - cnr_ids.discard(cnr_id) - updatable = False - cnr = unified_manager.cnr_map[cnr_id] - - if cnr_id in invalid_nodes: - v['invalid-installation'] = True - - if cnr_id in unified_manager.active_nodes: - # installed - v['state'] = 'enabled' - if unified_manager.active_nodes[cnr_id][0] != 'nightly': - updatable = unified_manager.is_updatable(cnr_id) - else: - updatable = False - v['active_version'] = unified_manager.active_nodes[cnr_id][0] - v['version'] = v['active_version'] - - if cm_global.try_call(api="cm.is_import_failed_extension", name=unified_manager.active_nodes[cnr_id][1]): - v['import-fail'] = True - - elif cnr_id in unified_manager.cnr_inactive_nodes: - # disabled - v['state'] = 'disabled' - cnr_ver = unified_manager.get_from_cnr_inactive_nodes(cnr_id) - if cnr_ver is not None: - v['version'] = str(cnr_ver[0]) - else: - v['version'] = '0' - - elif cnr_id in unified_manager.nightly_inactive_nodes: - # disabled - v['state'] = 'disabled' - v['version'] = 'nightly' - else: - # not installed - v['state'] = 'not-installed' - - if 'version' not in v: - v['version'] = cnr['latest_version']['version'] - - v['update-state'] = 'true' if updatable else 'false' - else: - # unknown version - v['version'] = 'unknown' - - if unified_manager.is_enabled(k, 'unknown'): - v['state'] = 'enabled' - v['active_version'] = 'unknown' - - if cm_global.try_call(api="cm.is_import_failed_extension", name=unified_manager.unknown_active_nodes[k][1]): - v['import-fail'] = True - - elif unified_manager.is_disabled(k, 'unknown'): - v['state'] = 'disabled' - else: - v['state'] = 'not-installed' - - # add items for pure cnr nodes - if normalize_channel(channel) == DEFAULT_CHANNEL: - # Don't show CNR nodes unless default channel - for cnr_id in cnr_ids: - cnr = unified_manager.cnr_map[cnr_id] - author = cnr['publisher']['name'] - title = cnr['name'] - reference = f"https://registry.comfy.org/nodes/{cnr['id']}" - repository = cnr.get('repository', '') - install_type = "cnr" - description = cnr.get('description', '') - - ver = None - active_version = None - updatable = False - import_fail = None - if cnr_id in unified_manager.active_nodes: - # installed - state = 'enabled' - updatable = unified_manager.is_updatable(cnr_id) - active_version = unified_manager.active_nodes[cnr['id']][0] - ver = active_version - - if cm_global.try_call(api="cm.is_import_failed_extension", name=unified_manager.active_nodes[cnr_id][1]): - import_fail = True - - elif cnr['id'] in unified_manager.cnr_inactive_nodes: - # disabled - state = 'disabled' - elif cnr['id'] in unified_manager.nightly_inactive_nodes: - # disabled - state = 'disabled' - ver = 'nightly' - else: - # not installed - state = 'not-installed' - - if ver is None: - ver = cnr['latest_version']['version'] - - item = dict(author=author, title=title, reference=reference, repository=repository, install_type=install_type, - description=description, state=state, updatable=updatable, version=ver) - - if active_version: - item['active_version'] = active_version - - if import_fail: - item['import-fail'] = True - - res[cnr_id] = item - - return res - - -def populate_github_stats(node_packs, json_obj_github): - for k, v in node_packs.items(): - try: - url = v['reference'] - if url in json_obj_github: - v['stars'] = json_obj_github[url]['stars'] - v['last_update'] = json_obj_github[url]['last_update'] - v['trust'] = json_obj_github[url]['author_account_age_days'] > 600 - else: - v['stars'] = -1 - v['last_update'] = -1 - v['trust'] = False - except: - logging.error(f"[ComfyUI-Manager] DB item is broken:\n{v}") - - -def populate_favorites(node_packs, json_obj_extras): - favorites = set(json_obj_extras['favorites']) - - for k, v in node_packs.items(): - if v.get('version') != 'unknown': - if k in favorites: - v['is_favorite'] = True - - -async def restore_snapshot(snapshot_path, git_helper_extras=None): - cloned_repos = [] - checkout_repos = [] - enabled_repos = [] - disabled_repos = [] - skip_node_packs = [] - switched_node_packs = [] - installed_node_packs = [] - failed = [] - - await unified_manager.reload('cache') - await unified_manager.get_custom_nodes('default', 'cache') - - cnr_repo_map = {} - for k, v in unified_manager.repo_cnr_map.items(): - cnr_repo_map[v['id']] = k - - print("Restore snapshot.") - - postinstalls = [] - - with open(snapshot_path, 'r', encoding="UTF-8") as snapshot_file: - if snapshot_path.endswith('.json'): - info = json.load(snapshot_file) - elif snapshot_path.endswith('.yaml'): - info = yaml.load(snapshot_file, Loader=yaml.SafeLoader) - info = info['custom_nodes'] - - if 'pips' in info and info['pips']: - pips = info['pips'] - else: - pips = {} - - # for cnr restore - cnr_info = info.get('cnr_custom_nodes') - if cnr_info is not None: - # disable not listed cnr nodes - todo_disable = [] - todo_checkout = [] - - for k, v in unified_manager.active_nodes.items(): - if 'comfyui-manager' in k: - continue - - if v[0] != 'nightly': - if k not in cnr_info: - todo_disable.append(k) - else: - cnr_ver = cnr_info[k] - if v[1] != cnr_ver: - todo_checkout.append((k, cnr_ver)) - else: - skip_node_packs.append(k) - - for x in todo_disable: - unified_manager.unified_disable(x, False) - disabled_repos.append(x) - - for x in todo_checkout: - ps = unified_manager.cnr_switch_version(x[0], x[1], instant_execution=True, no_deps=True, return_postinstall=False) - if ps.action == 'switch-cnr' and ps.result: - switched_node_packs.append(f"{x[0]}@{x[1]}") - elif ps.action == 'skip': - skip_node_packs.append(f"{x[0]}@{x[1]}") - elif not ps.result: - failed.append(f"{x[0]}@{x[1]}") - - # install listed cnr nodes - for k, v in cnr_info.items(): - if 'comfyui-manager' in k: - continue - - ps = await unified_manager.install_by_id(k, version_spec=v, instant_execution=True, return_postinstall=True) - if ps.action == 'install-cnr' and ps.result: - installed_node_packs.append(f"{k}@{v}") - - if ps is not None and ps.result: - if hasattr(ps, 'postinstall'): - postinstalls.append(ps.postinstall) - else: - print("cm-cli: unexpected [0001]") - - # for nightly restore - _git_info = info.get('git_custom_nodes') - git_info = {} - - # normalize github repo - for k, v in _git_info.items(): - # robust filter out comfyui-manager while restoring snapshot - if 'comfyui-manager' in k.lower(): - continue - - norm_k = git_utils.normalize_url(k) - git_info[norm_k] = v - - if git_info is not None: - todo_disable = [] - todo_enable = [] - todo_checkout = [] - processed_urls = [] - - for k, v in unified_manager.active_nodes.items(): - if 'comfyui-manager' in k: - continue - - if v[0] == 'nightly' and cnr_repo_map.get(k): - repo_url = cnr_repo_map.get(k) - normalized_url = git_utils.normalize_url(repo_url) - - if normalized_url not in git_info: - todo_disable.append(k) - else: - commit_hash = git_info[normalized_url]['hash'] - todo_checkout.append((v[1], commit_hash)) - - for k, v in unified_manager.nightly_inactive_nodes.items(): - if 'comfyui-manager' in k: - continue - - if cnr_repo_map.get(k): - repo_url = cnr_repo_map.get(k) - normalized_url = git_utils.normalize_url(repo_url) - - if normalized_url in git_info: - commit_hash = git_info[normalized_url]['hash'] - todo_enable.append((k, commit_hash)) - processed_urls.append(normalized_url) - - for x in todo_disable: - unified_manager.unified_disable(x, False) - disabled_repos.append(x) - - for x in todo_enable: - res = unified_manager.unified_enable(x[0], 'nightly') - - is_switched = False - if res and res.target: - is_switched = repo_switch_commit(res.target, x[1]) - - if is_switched: - checkout_repos.append(f"{x[0]}@{x[1]}") - else: - enabled_repos.append(x[0]) - - for x in todo_checkout: - is_switched = repo_switch_commit(x[0], x[1]) - - if is_switched: - checkout_repos.append(f"{x[0]}@{x[1]}") - - for x in git_info.keys(): - normalized_url = git_utils.normalize_url(x) - cnr = unified_manager.repo_cnr_map.get(normalized_url) - if cnr is not None: - pack_id = cnr['id'] - res = await unified_manager.install_by_id(pack_id, 'nightly', instant_execution=True, no_deps=False, return_postinstall=False) - if res.action == 'install-git' and res.result: - cloned_repos.append(pack_id) - elif res.action == 'skip': - skip_node_packs.append(pack_id) - elif not res.result: - failed.append(pack_id) - processed_urls.append(x) - - for x in processed_urls: - if x in git_info: - del git_info[x] - - # for unknown restore - todo_disable = [] - todo_enable = [] - todo_checkout = [] - processed_urls = [] - - for k2, v2 in unified_manager.unknown_active_nodes.items(): - repo_url = resolve_giturl_from_path(v2[1]) - - if repo_url is None: - continue - - normalized_url = git_utils.normalize_url(repo_url) - - if normalized_url not in git_info: - todo_disable.append(k2) - else: - commit_hash = git_info[normalized_url]['hash'] - todo_checkout.append((k2, commit_hash)) - processed_urls.append(normalized_url) - - for k2, v2 in unified_manager.unknown_inactive_nodes.items(): - repo_url = resolve_giturl_from_path(v2[1]) - - if repo_url is None: - continue - - normalized_url = git_utils.normalize_url(repo_url) - - if normalized_url in git_info: - commit_hash = git_info[normalized_url]['hash'] - todo_enable.append((k2, commit_hash)) - processed_urls.append(normalized_url) - - for x in todo_disable: - unified_manager.unified_disable(x, True) - disabled_repos.append(x) - - for x in todo_enable: - res = unified_manager.unified_enable(x[0], 'unknown') - - is_switched = False - if res and res.target: - is_switched = repo_switch_commit(res.target, x[1]) - - if is_switched: - checkout_repos.append(f"{x[0]}@{x[1]}") - else: - enabled_repos.append(x[0]) - - for x in todo_checkout: - is_switched = repo_switch_commit(x[0], x[1]) - - if is_switched: - checkout_repos.append(f"{x[0]}@{x[1]}") - else: - skip_node_packs.append(x[0]) - - for x in processed_urls: - if x in git_info: - del git_info[x] - - for repo_url in git_info.keys(): - repo_name = os.path.basename(repo_url) - if repo_name.endswith('.git'): - repo_name = repo_name[:-4] - - to_path = os.path.join(get_default_custom_nodes_path(), repo_name) - unified_manager.repo_install(repo_url, to_path, instant_execution=True, no_deps=False, return_postinstall=False) - cloned_repos.append(repo_name) - - manager_util.restore_pip_snapshot(pips, git_helper_extras) - - # print summary - for x in cloned_repos: - print(f"[ INSTALLED ] {x}") - for x in installed_node_packs: - print(f"[ INSTALLED ] {x}") - for x in checkout_repos: - print(f"[ CHECKOUT ] {x}") - for x in switched_node_packs: - print(f"[ SWITCHED ] {x}") - for x in enabled_repos: - print(f"[ ENABLED ] {x}") - for x in disabled_repos: - print(f"[ DISABLED ] {x}") - for x in skip_node_packs: - print(f"[ SKIPPED ] {x}") - for x in failed: - print(f"[ FAILED ] {x}") - - # if is_failed: - # print("[bold red]ERROR: Failed to restore snapshot.[/bold red]") - - -def get_comfyui_versions(repo=None): - repo = repo or git.Repo(comfy_path) - - remote_name = None - try: - remote_name = get_remote_name(repo) - repo.remotes[remote_name].fetch() - except: - logging.error("[ComfyUI-Manager] Failed to fetch ComfyUI") - - def parse_semver(tag_name): - match = re.match(r'^v(\d+)\.(\d+)\.(\d+)$', tag_name) - return tuple(int(x) for x in match.groups()) if match else None - - def normalize_describe(tag_name): - if not tag_name: - return None - base = tag_name.split('-', 1)[0] - return base if parse_semver(base) else None - - # Collect semver tags and sort descending (highest first) - semver_tags = [] - for tag in repo.tags: - semver = parse_semver(tag.name) - if semver: - semver_tags.append((semver, tag.name)) - semver_tags.sort(key=lambda x: x[0], reverse=True) - semver_tags = [name for _, name in semver_tags] - - latest_tag = semver_tags[0] if semver_tags else None - - try: - described = repo.git.describe('--tags') - except Exception: - described = '' - - try: - exact_tag = repo.git.describe('--tags', '--exact-match') - except Exception: - exact_tag = '' - - head_is_default = False - if remote_name: - try: - default_head_ref = repo.refs[f'{remote_name}/HEAD'] - default_commit = default_head_ref.reference.commit - head_is_default = repo.head.commit == default_commit - except Exception: - head_is_default = False - - nearest_semver = normalize_describe(described) - exact_semver = exact_tag if parse_semver(exact_tag) else None - - if head_is_default and not exact_tag: - current_tag = 'nightly' - else: - current_tag = exact_tag or described or 'nightly' - - # Prepare semver list for display: top 4 plus the current/nearest semver if missing - display_semver_tags = semver_tags[:4] - if exact_semver and exact_semver not in display_semver_tags: - display_semver_tags.append(exact_semver) - elif nearest_semver and nearest_semver not in display_semver_tags: - display_semver_tags.append(nearest_semver) - - versions = ['nightly'] - - if current_tag and not exact_semver and current_tag not in versions and current_tag not in display_semver_tags: - versions.append(current_tag) - - for tag in display_semver_tags: - if tag not in versions: - versions.append(tag) - - versions = versions[:6] - - return versions, current_tag, latest_tag - - -def switch_comfyui(tag): - repo = git.Repo(comfy_path) - - if tag == 'nightly': - repo.git.checkout('master') - tracking_branch = repo.active_branch.tracking_branch() - remote_name = tracking_branch.remote_name - repo.remotes[remote_name].pull() - print("[ComfyUI-Manager] ComfyUI version is switched to the latest 'master' version") - else: - repo.git.checkout(tag) - print(f"[ComfyUI-Manager] ComfyUI version is switched to '{tag}'") - - -def resolve_giturl_from_path(fullpath): - """ - resolve giturl path of unclassified custom node based on remote url in .git/config - """ - git_config_path = os.path.join(fullpath, '.git', 'config') - - if not os.path.exists(git_config_path): - return "unknown" - - config = configparser.ConfigParser(strict=False) - config.read(git_config_path) - - for k, v in config.items(): - if k.startswith('remote ') and 'url' in v: - return v['url'].replace("git@github.com:", "https://github.com/") - - return None - - -def repo_switch_commit(repo_path, commit_hash): - try: - repo = git.Repo(repo_path) - if repo.head.commit.hexsha == commit_hash: - return False - - repo.git.checkout(commit_hash) - return True - except: - return None diff --git a/glob/manager_downloader.py b/glob/manager_downloader.py deleted file mode 100644 index 2718de50..00000000 --- a/glob/manager_downloader.py +++ /dev/null @@ -1,163 +0,0 @@ -import os -from urllib.parse import urlparse -import urllib -import sys -import logging -import requests -from huggingface_hub import HfApi -from tqdm.auto import tqdm - - -aria2 = os.getenv('COMFYUI_MANAGER_ARIA2_SERVER') -HF_ENDPOINT = os.getenv('HF_ENDPOINT') - - -if aria2 is not None: - secret = os.getenv('COMFYUI_MANAGER_ARIA2_SECRET') - url = urlparse(aria2) - port = url.port - host = url.scheme + '://' + url.hostname - import aria2p - - aria2 = aria2p.API(aria2p.Client(host=host, port=port, secret=secret)) - - -def basic_download_url(url, dest_folder: str, filename: str): - ''' - Download file from url to dest_folder with filename - using requests library. - ''' - import requests - - # Ensure the destination folder exists - if not os.path.exists(dest_folder): - os.makedirs(dest_folder) - - # Full path to save the file - dest_path = os.path.join(dest_folder, filename) - - # Download the file - response = requests.get(url, stream=True) - if response.status_code == 200: - with open(dest_path, 'wb') as file: - for chunk in response.iter_content(chunk_size=1024): - if chunk: - file.write(chunk) - else: - raise Exception(f"Failed to download file from {url}") - - -def download_url(model_url: str, model_dir: str, filename: str): - if HF_ENDPOINT: - model_url = model_url.replace('https://huggingface.co', HF_ENDPOINT) - logging.info(f"model_url replaced by HF_ENDPOINT, new = {model_url}") - if aria2: - return aria2_download_url(model_url, model_dir, filename) - else: - from torchvision.datasets.utils import download_url as torchvision_download_url - try: - return torchvision_download_url(model_url, model_dir, filename) - except Exception as e: - logging.error(f"[ComfyUI-Manager] Failed to download: {model_url} / {repr(e)}") - raise - - -def aria2_find_task(dir: str, filename: str): - target = os.path.join(dir, filename) - - downloads = aria2.get_downloads() - - for download in downloads: - for file in download.files: - if file.is_metadata: - continue - if str(file.path) == target: - return download - - -def aria2_download_url(model_url: str, model_dir: str, filename: str): - import manager_core as core - import tqdm - import time - - if model_dir.startswith(core.comfy_path): - model_dir = model_dir[len(core.comfy_path) :] - - download_dir = model_dir if model_dir.startswith('/') else os.path.join('/models', model_dir) - - download = aria2_find_task(download_dir, filename) - if download is None: - options = {'dir': download_dir, 'out': filename} - download = aria2.add(model_url, options)[0] - - if download.is_active: - with tqdm.tqdm( - total=download.total_length, - bar_format='{l_bar}{bar}{r_bar}', - desc=filename, - unit='B', - unit_scale=True, - ) as progress_bar: - while download.is_active: - if progress_bar.total == 0 and download.total_length != 0: - progress_bar.reset(download.total_length) - progress_bar.update(download.completed_length - progress_bar.n) - time.sleep(1) - download.update() - - -def download_url_with_agent(url, save_path): - try: - headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} - - req = urllib.request.Request(url, headers=headers) - response = urllib.request.urlopen(req) - data = response.read() - - if not os.path.exists(os.path.dirname(save_path)): - os.makedirs(os.path.dirname(save_path)) - - with open(save_path, 'wb') as f: - f.write(data) - - except Exception as e: - print(f"Download error: {url} / {e}", file=sys.stderr) - return False - - print("Installation was successful.") - return True - -# NOTE: snapshot_download doesn't provide file size tqdm. -def download_repo_in_bytes(repo_id, local_dir): - api = HfApi() - repo_info = api.repo_info(repo_id=repo_id, files_metadata=True) - - os.makedirs(local_dir, exist_ok=True) - - total_size = 0 - for file_info in repo_info.siblings: - if file_info.size is not None: - total_size += file_info.size - - pbar = tqdm(total=total_size, unit="B", unit_scale=True, desc="Downloading") - - for file_info in repo_info.siblings: - out_path = os.path.join(local_dir, file_info.rfilename) - os.makedirs(os.path.dirname(out_path), exist_ok=True) - - if file_info.size is None: - continue - - download_url = f"https://huggingface.co/{repo_id}/resolve/main/{file_info.rfilename}" - - with requests.get(download_url, stream=True) as r, open(out_path, "wb") as f: - r.raise_for_status() - for chunk in r.iter_content(chunk_size=65536): - if chunk: - f.write(chunk) - pbar.update(len(chunk)) - - pbar.close() - - diff --git a/glob/manager_migration.py b/glob/manager_migration.py deleted file mode 100644 index 00db2775..00000000 --- a/glob/manager_migration.py +++ /dev/null @@ -1,356 +0,0 @@ -""" -ComfyUI-Manager migration module. -Handles migration from legacy paths to new __manager path structure. -""" - -import os -import sys -import subprocess -import configparser - -# Startup notices for notice board -startup_notices = [] # List of (message, level) tuples - - -def add_startup_notice(message, level='warning'): - """Add a notice to be displayed on Manager notice board. - - Args: - message: HTML-formatted message string - level: 'warning', 'error', 'info' - """ - global startup_notices - startup_notices.append((message, level)) - - -# Cache for API check (computed once per session) -_cached_has_system_user_api = None - - -def has_system_user_api(): - """Check if ComfyUI has the System User Protection API (PR #10966). - - Result is cached for performance. - """ - global _cached_has_system_user_api - if _cached_has_system_user_api is None: - try: - import folder_paths - _cached_has_system_user_api = hasattr(folder_paths, 'get_system_user_directory') - except Exception: - _cached_has_system_user_api = False - return _cached_has_system_user_api - - -def get_manager_path(user_dir): - """Get the appropriate manager files path based on ComfyUI version. - - Returns: - str: manager_files_path - """ - if has_system_user_api(): - return os.path.abspath(os.path.join(user_dir, '__manager')) - else: - return os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager')) - - -def run_migration_checks(user_dir, manager_files_path): - """Run all migration and security checks. - - Call this after get_manager_path() to handle: - - Legacy config migration (new ComfyUI) - - Legacy backup notification (every startup) - - Suspicious directory detection (old ComfyUI) - - Outdated ComfyUI warning (old ComfyUI) - """ - if has_system_user_api(): - migrated = migrate_legacy_config(user_dir, manager_files_path) - # Only check for legacy backup if migration didn't just happen - # (migration already shows backup location in its message) - if not migrated: - check_legacy_backup(manager_files_path) - else: - check_suspicious_manager(user_dir) - warn_outdated_comfyui() - - -def check_legacy_backup(manager_files_path): - """Check for legacy backup and notify user to verify and remove it. - - This runs on every startup to remind users about pending legacy backup. - """ - backup_dir = os.path.join(manager_files_path, '.legacy-manager-backup') - if not os.path.exists(backup_dir): - return - - # Terminal output - print("\n" + "-"*70) - print("[ComfyUI-Manager] NOTICE: Legacy backup exists") - print(" - Your old Manager data was backed up to:") - print(f" {backup_dir}") - print(" - Please verify and remove it when no longer needed.") - print("-"*70 + "\n") - - # Notice board output - add_startup_notice( - "Legacy ComfyUI-Manager data backup exists. Please verify and remove when no longer needed. See terminal for details.", - level='info' - ) - - -def check_suspicious_manager(user_dir): - """Check for suspicious __manager directory on old ComfyUI. - - On old ComfyUI without System User API, if __manager exists with low security, - warn the user to verify manually. - - Returns: - bool: True if suspicious setup detected - """ - if has_system_user_api(): - return False # Not suspicious on new ComfyUI - - suspicious_path = os.path.abspath(os.path.join(user_dir, '__manager')) - if not os.path.exists(suspicious_path): - return False - - config_path = os.path.join(suspicious_path, 'config.ini') - if not os.path.exists(config_path): - return False - - config = configparser.ConfigParser() - config.read(config_path) - sec_level = config.get('default', 'security_level', fallback='normal').lower() - - if sec_level in ['weak', 'normal-']: - # Terminal output - print("\n" + "!"*70) - print("[ComfyUI-Manager] ERROR: Suspicious path detected!") - print(f" - '__manager' exists with low security level: '{sec_level}'") - print(" - Please verify manually:") - print(f" {config_path}") - print("!"*70 + "\n") - - # Notice board output - add_startup_notice( - "[Security Alert] Suspicious path detected. See terminal log for details.", - level='error' - ) - return True - - return False - - -def warn_outdated_comfyui(): - """Warn user about outdated ComfyUI without System User API.""" - if has_system_user_api(): - return - - # Terminal output - print("\n" + "!"*70) - print("[ComfyUI-Manager] ERROR: ComfyUI version is outdated!") - print(" - Most operations are blocked for security.") - print(" - ComfyUI update is still allowed.") - print(" - Please update ComfyUI to use Manager normally.") - print("!"*70 + "\n") - - # Notice board output - add_startup_notice( - "[Security Alert] ComfyUI outdated. Installations blocked (update allowed).
" - "Update ComfyUI for normal operation.", - level='error' - ) - - -def migrate_legacy_config(user_dir, manager_files_path): - """Migrate ONLY config.ini to new __manager path if needed. - - IMPORTANT: Only config.ini is migrated. Other files (snapshots, cache, etc.) - are NOT migrated - users must recreate them. - - Scenarios: - 1. Legacy exists, New doesn't exist → Migrate config.ini - 2. Legacy exists, New exists → First update after upgrade - - Run ComfyUI dependency installation - - Rename legacy to .backup - 3. Legacy doesn't exist → No migration needed - - Returns: - bool: True if migration was performed - """ - if not has_system_user_api(): - return False - - legacy_dir = os.path.join(user_dir, 'default', 'ComfyUI-Manager') - legacy_config = os.path.join(legacy_dir, 'config.ini') - new_config = os.path.join(manager_files_path, 'config.ini') - - if not os.path.exists(legacy_dir): - return False # No legacy directory, nothing to migrate - - # IMPORTANT: Check for config.ini existence, not just directory - # (because makedirs() creates __manager before this function is called) - - # Case: Both configs exist (first update after ComfyUI upgrade) - # This means user ran new ComfyUI at least once, creating __manager/config.ini - if os.path.exists(legacy_config) and os.path.exists(new_config): - _handle_first_update_migration(user_dir, legacy_dir, manager_files_path) - return True - - # Case: Legacy config exists but new config doesn't (normal migration) - # This is the first run after ComfyUI upgrade - if os.path.exists(legacy_config) and not os.path.exists(new_config): - pass # Continue with normal migration below - else: - return False - - # Terminal output - print("\n" + "-"*70) - print("[ComfyUI-Manager] NOTICE: Legacy config.ini detected") - print(f" - Old: {legacy_config}") - print(f" - New: {new_config}") - print(" - Migrating config.ini only (other files are NOT migrated).") - print(" - Security level below 'normal' will be raised.") - print("-"*70 + "\n") - - _migrate_config_with_security_check(legacy_config, new_config) - - # Move legacy directory to backup - _move_legacy_to_backup(legacy_dir, manager_files_path) - - return True - - -def _handle_first_update_migration(user_dir, legacy_dir, manager_files_path): - """Handle first ComfyUI update when both legacy and new directories exist. - - This scenario happens when: - - User was on old ComfyUI (using default/ComfyUI-Manager) - - ComfyUI was updated (now has System User API) - - Manager already created __manager on first new run - - But legacy directory still exists - - Actions: - 1. Run ComfyUI dependency installation - 2. Move legacy to __manager/.legacy-manager-backup - """ - # Terminal output - print("\n" + "-"*70) - print("[ComfyUI-Manager] NOTICE: First update after ComfyUI upgrade detected") - print(" - Both legacy and new directories exist.") - print(" - Running ComfyUI dependency installation...") - print("-"*70 + "\n") - - # Run ComfyUI dependency installation - # Path: glob/manager_migration.py → glob → comfyui-manager → custom_nodes → ComfyUI - try: - comfyui_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - requirements_path = os.path.join(comfyui_path, 'requirements.txt') - if os.path.exists(requirements_path): - subprocess.run([sys.executable, '-m', 'pip', 'install', '-r', requirements_path], - capture_output=True, check=False) - print("[ComfyUI-Manager] ComfyUI dependencies installation completed.") - except Exception as e: - print(f"[ComfyUI-Manager] WARNING: Failed to install ComfyUI dependencies: {e}") - - # Move legacy to backup inside __manager - _move_legacy_to_backup(legacy_dir, manager_files_path) - - -def _move_legacy_to_backup(legacy_dir, manager_files_path): - """Move legacy directory to backup inside __manager. - - Returns: - str: Path to backup directory if successful, None if failed - """ - import shutil - - backup_dir = os.path.join(manager_files_path, '.legacy-manager-backup') - - try: - if os.path.exists(backup_dir): - shutil.rmtree(backup_dir) # Remove old backup if exists - shutil.move(legacy_dir, backup_dir) - - # Terminal output (full paths shown here only) - print("\n" + "-"*70) - print("[ComfyUI-Manager] NOTICE: Legacy settings migrated") - print(f" - Old location: {legacy_dir}") - print(f" - Backed up to: {backup_dir}") - print(" - Please verify and remove the backup when no longer needed.") - print("-"*70 + "\n") - - # Notice board output (no full paths for security) - add_startup_notice( - "Legacy ComfyUI-Manager data migrated. See terminal for details.", - level='info' - ) - return backup_dir - except Exception as e: - print(f"[ComfyUI-Manager] WARNING: Failed to backup legacy directory: {e}") - add_startup_notice( - f"[MIGRATION] Failed to backup legacy directory: {e}", - level='warning' - ) - return None - - -def _migrate_config_with_security_check(legacy_path, new_path): - """Migrate legacy config, raising security level only if below default.""" - config = configparser.ConfigParser() - try: - config.read(legacy_path) - except Exception as e: - print(f"[ComfyUI-Manager] WARNING: Failed to parse config.ini: {e}") - print(" - Creating fresh config with default settings.") - add_startup_notice( - "[MIGRATION] Failed to parse legacy config. Using defaults.", - level='warning' - ) - return # Skip migration, let Manager create fresh config - - # Security level hierarchy: strong > normal > normal- > weak - # Default is 'normal', only raise if below default - if 'default' in config: - current_level = config['default'].get('security_level', 'normal').lower() - below_default_levels = ['weak', 'normal-'] - - if current_level in below_default_levels: - config['default']['security_level'] = 'normal' - - # Terminal output - print("\n" + "="*70) - print("[ComfyUI-Manager] WARNING: Security level adjusted") - print(f" - Previous: '{current_level}' → New: 'normal'") - print(" - Raised to prevent unauthorized remote access.") - print("="*70 + "\n") - - # Notice board output - add_startup_notice( - f"[MIGRATION] Security level raised: '{current_level}' → 'normal'.
" - "To prevent unauthorized remote access.", - level='warning' - ) - else: - print(f" - Security level: '{current_level}' (no change needed)") - - # Ensure directory exists - os.makedirs(os.path.dirname(new_path), exist_ok=True) - - with open(new_path, 'w') as f: - config.write(f) - - -def force_security_level_if_needed(config_dict): - """Force security level to 'strong' if on old ComfyUI. - - Args: - config_dict: Configuration dictionary to modify in-place - - Returns: - bool: True if security level was forced - """ - if not has_system_user_api(): - config_dict['security_level'] = 'strong' - return True - return False diff --git a/glob/manager_server.py b/glob/manager_server.py deleted file mode 100644 index eff7c032..00000000 --- a/glob/manager_server.py +++ /dev/null @@ -1,1876 +0,0 @@ -import traceback - -import folder_paths -import locale -import subprocess # don't remove this -import concurrent -import nodes -import os -import sys -import threading -import re -import shutil -import git -from datetime import datetime - -from server import PromptServer -import manager_core as core -import manager_util -import cm_global -import logging -import asyncio -import queue - -import manager_downloader -import manager_migration - - -logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})") -logging.info("[ComfyUI-Manager] network_mode: " + core.get_config()['network_mode']) - -comfy_ui_hash = "-" -comfyui_tag = None - -SECURITY_MESSAGE_MIDDLE_OR_BELOW = "ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy" -SECURITY_MESSAGE_NORMAL_MINUS = "ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy" -SECURITY_MESSAGE_GENERAL = "ERROR: This installation is not allowed in this security_level. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy" -SECURITY_MESSAGE_NORMAL_MINUS_MODEL = "ERROR: Downloading models that are not in '.safetensors' format is only allowed for models registered in the 'default' channel at this security level. If you want to download this model, set the security level to 'normal-' or lower." - -routes = PromptServer.instance.routes - - -def has_per_queue_preview(): - """ - Check if ComfyUI PR #11261 (per-queue live preview override) is merged - - Returns: - bool: True if ComfyUI has per-queue preview feature - """ - try: - import latent_preview - return hasattr(latent_preview, 'set_preview_method') - except ImportError: - return False - - -# Detect ComfyUI per-queue preview override feature (PR #11261) -COMFYUI_HAS_PER_QUEUE_PREVIEW = has_per_queue_preview() - - -def handle_stream(stream, prefix): - stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace') - for msg in stream: - if prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg): - if msg.startswith('100%'): - print('\r' + msg, end="", file=sys.stderr), - else: - print('\r' + msg[:-1], end="", file=sys.stderr), - else: - if prefix == '[!]': - print(prefix, msg, end="", file=sys.stderr) - else: - print(prefix, msg, end="") - - -from comfy.cli_args import args -import latent_preview - -def is_loopback(address): - import ipaddress - try: - return ipaddress.ip_address(address).is_loopback - except ValueError: - return False - -is_local_mode = is_loopback(args.listen) - - -model_dir_name_map = { - "checkpoints": "checkpoints", - "checkpoint": "checkpoints", - "unclip": "checkpoints", - "text_encoders": "text_encoders", - "clip": "text_encoders", - "vae": "vae", - "lora": "loras", - "t2i-adapter": "controlnet", - "t2i-style": "controlnet", - "controlnet": "controlnet", - "clip_vision": "clip_vision", - "gligen": "gligen", - "upscale": "upscale_models", - "embedding": "embeddings", - "embeddings": "embeddings", - "unet": "diffusion_models", - "diffusion_model": "diffusion_models", -} - - -def is_allowed_security_level(level): - if level == 'block': - return False - elif level == 'high': - if is_local_mode: - return core.get_config()['security_level'] in ['weak', 'normal-'] - else: - return core.get_config()['security_level'] == 'weak' - elif level == 'middle': - return core.get_config()['security_level'] in ['weak', 'normal', 'normal-'] - else: - return True - - -async def get_risky_level(files, pip_packages): - json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json') - json_data2 = await core.get_data_by_mode('cache', 'custom-node-list.json', channel_url='https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main') - - all_urls = set() - for x in json_data1['custom_nodes'] + json_data2['custom_nodes']: - all_urls.update(x.get('files', [])) - - for x in files: - if x not in all_urls: - return "high" - - all_pip_packages = set() - for x in json_data1['custom_nodes'] + json_data2['custom_nodes']: - all_pip_packages.update(x.get('pip', [])) - - for p in pip_packages: - if p not in all_pip_packages: - return "block" - - return "middle" - - -class ManagerFuncsInComfyUI(core.ManagerFuncs): - def get_current_preview_method(self): - if args.preview_method == latent_preview.LatentPreviewMethod.Auto: - return "auto" - elif args.preview_method == latent_preview.LatentPreviewMethod.Latent2RGB: - return "latent2rgb" - elif args.preview_method == latent_preview.LatentPreviewMethod.TAESD: - return "taesd" - else: - return "none" - - def run_script(self, cmd, cwd='.'): - if len(cmd) > 0 and cmd[0].startswith("#"): - logging.error(f"[ComfyUI-Manager] Unexpected behavior: `{cmd}`") - return 0 - - process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, env=core.get_script_env()) - - stdout_thread = threading.Thread(target=handle_stream, args=(process.stdout, "")) - stderr_thread = threading.Thread(target=handle_stream, args=(process.stderr, "[!]")) - - stdout_thread.start() - stderr_thread.start() - - stdout_thread.join() - stderr_thread.join() - - return process.wait() - - -core.manager_funcs = ManagerFuncsInComfyUI() - -sys.path.append('../..') - -from manager_downloader import download_url, download_url_with_agent - -core.comfy_path = os.path.dirname(folder_paths.__file__) -core.js_path = os.path.join(core.comfy_path, "web", "extensions") - -local_db_model = os.path.join(manager_util.comfyui_manager_path, "model-list.json") -local_db_alter = os.path.join(manager_util.comfyui_manager_path, "alter-list.json") -local_db_custom_node_list = os.path.join(manager_util.comfyui_manager_path, "custom-node-list.json") -local_db_extension_node_mappings = os.path.join(manager_util.comfyui_manager_path, "extension-node-map.json") - - -def set_preview_method(method): - if method == 'auto': - args.preview_method = latent_preview.LatentPreviewMethod.Auto - elif method == 'latent2rgb': - args.preview_method = latent_preview.LatentPreviewMethod.Latent2RGB - elif method == 'taesd': - args.preview_method = latent_preview.LatentPreviewMethod.TAESD - else: - args.preview_method = latent_preview.LatentPreviewMethod.NoPreviews - - core.get_config()['preview_method'] = method - - -if COMFYUI_HAS_PER_QUEUE_PREVIEW: - logging.info( - "[ComfyUI-Manager] ComfyUI per-queue preview override detected (PR #11261). " - "Manager's preview method feature is disabled. " - "Use ComfyUI's --preview-method CLI option or 'Settings > Execution > Live preview method'." - ) -elif args.preview_method == latent_preview.LatentPreviewMethod.NoPreviews: - set_preview_method(core.get_config()['preview_method']) -else: - logging.warning( - "[ComfyUI-Manager] Since --preview-method is set, " - "ComfyUI-Manager's preview method feature will be ignored." - ) - - -def set_component_policy(mode): - core.get_config()['component_policy'] = mode - -def set_update_policy(mode): - core.get_config()['update_policy'] = mode - -def set_db_mode(mode): - core.get_config()['db_mode'] = mode - -def print_comfyui_version(): - global comfy_ui_hash - global comfyui_tag - - is_detached = False - try: - repo = git.Repo(os.path.dirname(folder_paths.__file__)) - core.comfy_ui_revision = len(list(repo.iter_commits('HEAD'))) - - comfy_ui_hash = repo.head.commit.hexsha - cm_global.variables['comfyui.revision'] = core.comfy_ui_revision - - core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime - cm_global.variables['comfyui.commit_datetime'] = core.comfy_ui_commit_datetime - - is_detached = repo.head.is_detached - current_branch = repo.active_branch.name - - comfyui_tag = core.get_comfyui_tag() - - try: - if not os.environ.get('__COMFYUI_DESKTOP_VERSION__') and core.comfy_ui_commit_datetime.date() < core.comfy_ui_required_commit_datetime.date(): - logging.warning(f"\n\n## [WARN] ComfyUI-Manager: Your ComfyUI version ({core.comfy_ui_revision})[{core.comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version. ##\n\n") - except: - pass - - # process on_revision_detected --> - if 'cm.on_revision_detected_handler' in cm_global.variables: - for k, f in cm_global.variables['cm.on_revision_detected_handler']: - try: - f(core.comfy_ui_revision) - except Exception: - logging.error(f"[ERROR] '{k}' on_revision_detected_handler") - traceback.print_exc() - - del cm_global.variables['cm.on_revision_detected_handler'] - else: - logging.warning("[ComfyUI-Manager] Some features are restricted due to your ComfyUI being outdated.") - # <-- - - if current_branch == "master": - if comfyui_tag: - logging.info(f"### ComfyUI Version: {comfyui_tag} | Released on '{core.comfy_ui_commit_datetime.date()}'") - else: - logging.info(f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'") - else: - if comfyui_tag: - logging.info(f"### ComfyUI Version: {comfyui_tag} on '{current_branch}' | Released on '{core.comfy_ui_commit_datetime.date()}'") - else: - logging.info(f"### ComfyUI Revision: {core.comfy_ui_revision} on '{current_branch}' [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'") - except: - if is_detached: - logging.info(f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] *DETACHED | Released on '{core.comfy_ui_commit_datetime.date()}'") - else: - logging.info("### ComfyUI Revision: UNKNOWN (The currently installed ComfyUI is not a Git repository)") - - -print_comfyui_version() -core.check_invalid_nodes() - - - -def setup_environment(): - git_exe = core.get_config()['git_exe'] - - if git_exe != '': - git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=git_exe) - - -setup_environment() - -# Expand Server api - -from aiohttp import web -import aiohttp -import json -import zipfile -import urllib.request - - -def security_403_response(): - """Return appropriate 403 response based on ComfyUI version.""" - if not manager_migration.has_system_user_api(): - return web.json_response({"error": "comfyui_outdated"}, status=403) - return web.json_response({"error": "security_level"}, status=403) - - -def get_model_dir(data, show_log=False): - if 'download_model_base' in folder_paths.folder_names_and_paths: - models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0] - else: - models_base = folder_paths.models_dir - - # NOTE: Validate to prevent path traversal. - if any(char in data['filename'] for char in {'/', '\\', ':'}): - return None - - def resolve_custom_node(save_path): - save_path = save_path[13:] # remove 'custom_nodes/' - - # NOTE: Validate to prevent path traversal. - if save_path.startswith(os.path.sep) or ':' in save_path: - return None - - repo_name = save_path.replace('\\','/').split('/')[0] # get custom node repo name - - # NOTE: The creation of files within the custom node path should be removed in the future. - repo_path = core.lookup_installed_custom_nodes_legacy(repo_name) - if repo_path is not None and repo_path[0]: - # Returns the retargeted path based on the actually installed repository - return os.path.join(os.path.dirname(repo_path[1]), save_path) - else: - return None - - if data['save_path'] != 'default': - if '..' in data['save_path'] or data['save_path'].startswith('/'): - if show_log: - logging.info(f"[WARN] '{data['save_path']}' is not allowed path. So it will be saved into 'models/etc'.") - base_model = os.path.join(models_base, "etc") - else: - if data['save_path'].startswith("custom_nodes"): - base_model = resolve_custom_node(data['save_path']) - if base_model is None: - if show_log: - logging.info(f"[ComfyUI-Manager] The target custom node for model download is not installed: {data['save_path']}") - return None - else: - base_model = os.path.join(models_base, data['save_path']) - else: - model_dir_name = model_dir_name_map.get(data['type'].lower()) - if model_dir_name is not None: - base_model = folder_paths.folder_names_and_paths[model_dir_name][0][0] - else: - base_model = os.path.join(models_base, "etc") - - return base_model - - -def get_model_path(data, show_log=False): - base_model = get_model_dir(data, show_log) - if base_model is None: - return None - else: - if data['filename'] == '': - return os.path.join(base_model, os.path.basename(data['url'])) - else: - return os.path.join(base_model, data['filename']) - - -def check_state_of_git_node_pack(node_packs, do_fetch=False, do_update_check=True, do_update=False): - if do_fetch: - print("Start fetching...", end="") - elif do_update: - print("Start updating...", end="") - elif do_update_check: - print("Start update check...", end="") - - def process_custom_node(item): - core.check_state_of_git_node_pack_single(item, do_fetch, do_update_check, do_update) - - with concurrent.futures.ThreadPoolExecutor(4) as executor: - for k, v in node_packs.items(): - if v.get('active_version') in ['unknown', 'nightly']: - executor.submit(process_custom_node, v) - - if do_fetch: - print("\x1b[2K\rFetching done.") - elif do_update: - update_exists = any(item.get('updatable', False) for item in node_packs.values()) - if update_exists: - print("\x1b[2K\rUpdate done.") - else: - print("\x1b[2K\rAll extensions are already up-to-date.") - elif do_update_check: - print("\x1b[2K\rUpdate check done.") - - -def nickname_filter(json_obj): - preemptions_map = {} - - for k, x in json_obj.items(): - if 'preemptions' in x[1]: - for y in x[1]['preemptions']: - preemptions_map[y] = k - elif k.endswith("/ComfyUI"): - for y in x[0]: - preemptions_map[y] = k - - updates = {} - for k, x in json_obj.items(): - removes = set() - for y in x[0]: - k2 = preemptions_map.get(y) - if k2 is not None and k != k2: - removes.add(y) - - if len(removes) > 0: - updates[k] = [y for y in x[0] if y not in removes] - - for k, v in updates.items(): - json_obj[k][0] = v - - return json_obj - - -task_queue = queue.Queue() -nodepack_result = {} -model_result = {} -tasks_in_progress = set() -task_worker_lock = threading.Lock() - -async def task_worker(): - global task_queue - global nodepack_result - global model_result - global tasks_in_progress - - async def do_install(item) -> str: - ui_id, node_spec_str, channel, mode, skip_post_install = item - - try: - node_spec = core.unified_manager.resolve_node_spec(node_spec_str) - if node_spec is None: - logging.error(f"Cannot resolve install target: '{node_spec_str}'") - return f"Cannot resolve install target: '{node_spec_str}'" - - node_name, version_spec, is_specified = node_spec - res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install) - # discard post install if skip_post_install mode - - if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']: - logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}") - return res.msg - - elif not res.result: - logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}") - return res.msg - - return 'success' - except Exception: - traceback.print_exc() - return f"Installation failed:\n{node_spec_str}" - - async def do_update(item): - ui_id, node_name, node_ver = item - - try: - res = core.unified_manager.unified_update(node_name, node_ver) - - if res.ver == 'unknown': - url = core.unified_manager.unknown_active_nodes[node_name][0] - try: - title = os.path.basename(url) - except Exception: - title = node_name - else: - url = core.unified_manager.cnr_map[node_name].get('repository') - title = core.unified_manager.cnr_map[node_name]['name'] - - manager_util.clear_pip_cache() - - if url is not None: - base_res = {'url': url, 'title': title} - else: - base_res = {'title': title} - - if res.result: - if res.action == 'skip': - base_res['msg'] = 'skip' - return base_res - else: - base_res['msg'] = 'success' - return base_res - - base_res['msg'] = f"An error occurred while updating '{node_name}'." - logging.error(f"\nERROR: An error occurred while updating '{node_name}'. (res.result={res.result}, res.action={res.action})") - return base_res - except Exception: - traceback.print_exc() - - return {'msg':f"An error occurred while updating '{node_name}'."} - - async def do_update_comfyui(is_stable) -> str: - try: - repo_path = os.path.dirname(folder_paths.__file__) - latest_tag = None - if is_stable: - res, latest_tag = core.update_to_stable_comfyui(repo_path) - else: - res = core.update_path(repo_path) - - if res == "fail": - logging.error("ComfyUI update failed") - return "fail" - elif res == "updated": - if is_stable: - logging.info("ComfyUI is updated to latest stable version.") - return "success-stable-"+latest_tag - else: - logging.info("ComfyUI is updated to latest nightly version.") - return "success-nightly" - else: # skipped - logging.info("ComfyUI is up-to-date.") - return "skip" - - except Exception: - traceback.print_exc() - - return "An error occurred while updating 'comfyui'." - - async def do_fix(item) -> str: - ui_id, node_name, node_ver = item - - try: - res = core.unified_manager.unified_fix(node_name, node_ver) - - if res.result: - return 'success' - else: - logging.error(res.msg) - - logging.error(f"\nERROR: An error occurred while fixing '{node_name}@{node_ver}'.") - except Exception: - traceback.print_exc() - - return f"An error occurred while fixing '{node_name}@{node_ver}'." - - async def do_uninstall(item) -> str: - ui_id, node_name, is_unknown = item - - try: - res = core.unified_manager.unified_uninstall(node_name, is_unknown) - - if res.result: - return 'success' - - logging.error(f"\nERROR: An error occurred while uninstalling '{node_name}'.") - except Exception: - traceback.print_exc() - - return f"An error occurred while uninstalling '{node_name}'." - - async def do_disable(item) -> str: - ui_id, node_name, is_unknown = item - - try: - res = core.unified_manager.unified_disable(node_name, is_unknown) - - if res: - return 'success' - - except Exception: - traceback.print_exc() - - return f"Failed to disable: '{node_name}'" - - async def do_install_model(item) -> str: - ui_id, json_data = item - - model_path = get_model_path(json_data) - model_url = json_data['url'] - - res = False - - try: - if model_path is not None: - logging.info(f"Install model '{json_data['name']}' from '{model_url}' into '{model_path}'") - - if json_data['filename'] == '': - if os.path.exists(os.path.join(model_path, os.path.dirname(json_data['url']))): - logging.error(f"[ComfyUI-Manager] the model path already exists: {model_path}") - return f"The model path already exists: {model_path}" - - logging.info(f"[ComfyUI-Manager] Downloading '{model_url}' into '{model_path}'") - manager_downloader.download_repo_in_bytes(repo_id=model_url, local_dir=model_path) - - return 'success' - - elif not core.get_config()['model_download_by_agent'] and ( - model_url.startswith('https://github.com') or model_url.startswith('https://huggingface.co') or model_url.startswith('https://heibox.uni-heidelberg.de')): - model_dir = get_model_dir(json_data, True) - download_url(model_url, model_dir, filename=json_data['filename']) - if model_path.endswith('.zip'): - res = core.unzip(model_path) - else: - res = True - - if res: - return 'success' - else: - res = download_url_with_agent(model_url, model_path) - if res and model_path.endswith('.zip'): - res = core.unzip(model_path) - else: - logging.error(f"[ComfyUI-Manager] Model installation error: invalid model type - {json_data['type']}") - - if res: - return 'success' - - except Exception as e: - logging.error(f"[ComfyUI-Manager] ERROR: {e}") - - return f"Model installation error: {model_url}" - - stats = {} - - while True: - done_count = len(nodepack_result) + len(model_result) - total_count = done_count + task_queue.qsize() - - if task_queue.empty(): - logging.info(f"\n[ComfyUI-Manager] Queued works are completed.\n{stats}") - - logging.info("\nAfter restarting ComfyUI, please refresh the browser.") - PromptServer.instance.send_sync("cm-queue-status", - {'status': 'done', - 'nodepack_result': nodepack_result, 'model_result': model_result, - 'total_count': total_count, 'done_count': done_count}) - nodepack_result = {} - task_queue = queue.Queue() - return # terminate worker thread - - with task_worker_lock: - kind, item = task_queue.get() - tasks_in_progress.add((kind, item[0])) - - try: - if kind == 'install': - msg = await do_install(item) - elif kind == 'install-model': - msg = await do_install_model(item) - elif kind == 'update': - msg = await do_update(item) - elif kind == 'update-main': - msg = await do_update(item) - elif kind == 'update-comfyui': - msg = await do_update_comfyui(item[1]) - elif kind == 'fix': - msg = await do_fix(item) - elif kind == 'uninstall': - msg = await do_uninstall(item) - elif kind == 'disable': - msg = await do_disable(item) - else: - msg = "Unexpected kind: " + kind - except Exception: - traceback.print_exc() - msg = f"Exception: {(kind, item)}" - - with task_worker_lock: - tasks_in_progress.remove((kind, item[0])) - - ui_id = item[0] - if kind == 'install-model': - model_result[ui_id] = msg - ui_target = "model_manager" - elif kind == 'update-main': - nodepack_result[ui_id] = msg - ui_target = "main" - elif kind == 'update-comfyui': - nodepack_result['comfyui'] = msg - ui_target = "main" - elif kind == 'update': - nodepack_result[ui_id] = msg['msg'] - ui_target = "nodepack_manager" - else: - nodepack_result[ui_id] = msg - ui_target = "nodepack_manager" - - stats[kind] = stats.get(kind, 0) + 1 - - PromptServer.instance.send_sync("cm-queue-status", - {'status': 'in_progress', 'target': item[0], 'ui_target': ui_target, - 'total_count': total_count, 'done_count': done_count}) - - -@routes.get("/customnode/getmappings") -async def fetch_customnode_mappings(request): - """ - provide unified (node -> node pack) mapping list - """ - mode = request.rel_url.query["mode"] - - nickname_mode = False - if mode == "nickname": - mode = "local" - nickname_mode = True - - json_obj = await core.get_data_by_mode(mode, 'extension-node-map.json') - json_obj = core.map_to_unified_keys(json_obj) - - if nickname_mode: - json_obj = nickname_filter(json_obj) - - all_nodes = set() - patterns = [] - for k, x in json_obj.items(): - all_nodes.update(set(x[0])) - - if 'nodename_pattern' in x[1]: - patterns.append((x[1]['nodename_pattern'], x[0])) - - missing_nodes = set(nodes.NODE_CLASS_MAPPINGS.keys()) - all_nodes - - for x in missing_nodes: - for pat, item in patterns: - if re.match(pat, x): - item.append(x) - - return web.json_response(json_obj, content_type='application/json') - - -@routes.get("/customnode/fetch_updates") -async def fetch_updates(request): - try: - if request.rel_url.query["mode"] == "local": - channel = 'local' - else: - channel = core.get_config()['channel_url'] - - await core.unified_manager.reload(request.rel_url.query["mode"]) - await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"]) - - res = core.unified_manager.fetch_or_pull_git_repo(is_pull=False) - - for x in res['failed']: - logging.error(f"FETCH FAILED: {x}") - - logging.info("\nDone.") - - if len(res['updated']) > 0: - return web.Response(status=201) - - return web.Response(status=200) - except: - traceback.print_exc() - return web.Response(status=400) - - -@routes.get("/manager/queue/update_all") -async def update_all(request): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return security_403_response() - - with task_worker_lock: - is_processing = task_worker_thread is not None and task_worker_thread.is_alive() - if is_processing: - return web.Response(status=401) - - await core.save_snapshot_with_postfix('autosave') - - if request.rel_url.query["mode"] == "local": - channel = 'local' - else: - channel = core.get_config()['channel_url'] - - await core.unified_manager.reload(request.rel_url.query["mode"]) - await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"]) - - for k, v in core.unified_manager.active_nodes.items(): - if k == 'comfyui-manager': - # skip updating comfyui-manager if desktop version - if os.environ.get('__COMFYUI_DESKTOP_VERSION__'): - continue - - update_item = k, k, v[0] - task_queue.put(("update-main", update_item)) - - for k, v in core.unified_manager.unknown_active_nodes.items(): - if k == 'comfyui-manager': - # skip updating comfyui-manager if desktop version - if os.environ.get('__COMFYUI_DESKTOP_VERSION__'): - continue - - update_item = k, k, 'unknown' - task_queue.put(("update-main", update_item)) - - return web.Response(status=200) - - -def convert_markdown_to_html(input_text): - pattern_a = re.compile(r'\[a/([^]]+)]\(([^)]+)\)') - pattern_w = re.compile(r'\[w/([^]]+)]') - pattern_i = re.compile(r'\[i/([^]]+)]') - pattern_bold = re.compile(r'\*\*([^*]+)\*\*') - pattern_white = re.compile(r'%%([^*]+)%%') - - def replace_a(match): - return f"{match.group(1)}" - - def replace_w(match): - return f"

{match.group(1)}

" - - def replace_i(match): - return f"

{match.group(1)}

" - - def replace_bold(match): - return f"{match.group(1)}" - - def replace_white(match): - return f"{match.group(1)}" - - input_text = input_text.replace('\\[', '[').replace('\\]', ']').replace('<', '<').replace('>', '>') - - result_text = re.sub(pattern_a, replace_a, input_text) - result_text = re.sub(pattern_w, replace_w, result_text) - result_text = re.sub(pattern_i, replace_i, result_text) - result_text = re.sub(pattern_bold, replace_bold, result_text) - result_text = re.sub(pattern_white, replace_white, result_text) - - return result_text.replace("\n", "
") - - -def populate_markdown(x): - if 'description' in x: - x['description'] = convert_markdown_to_html(manager_util.sanitize_tag(x['description'])) - - if 'name' in x: - x['name'] = manager_util.sanitize_tag(x['name']) - - if 'title' in x: - x['title'] = manager_util.sanitize_tag(x['title']) - - -# freeze imported version -startup_time_installed_node_packs = core.get_installed_node_packs() -@routes.get("/customnode/installed") -async def installed_list(request): - mode = request.query.get('mode', 'default') - - if mode == 'imported': - res = startup_time_installed_node_packs - else: - res = core.get_installed_node_packs() - - return web.json_response(res, content_type='application/json') - - -@routes.get("/customnode/getlist") -async def fetch_customnode_list(request): - """ - provide unified custom node list - """ - if request.rel_url.query.get("skip_update", '').lower() == "true": - skip_update = True - else: - skip_update = False - - if request.rel_url.query["mode"] == "local": - channel = 'local' - else: - channel = core.get_config()['channel_url'] - - node_packs = await core.get_unified_total_nodes(channel, request.rel_url.query["mode"], 'cache') - json_obj_github = core.get_data_by_mode(request.rel_url.query["mode"], 'github-stats.json', 'default') - json_obj_extras = core.get_data_by_mode(request.rel_url.query["mode"], 'extras.json', 'default') - - core.populate_github_stats(node_packs, await json_obj_github) - core.populate_favorites(node_packs, await json_obj_extras) - - check_state_of_git_node_pack(node_packs, not skip_update, do_update_check=not skip_update) - - for v in node_packs.values(): - populate_markdown(v) - - if channel != 'local': - found = 'custom' - - for name, url in core.get_channel_dict().items(): - if url == channel: - found = name - break - - channel = found - - result = dict(channel=channel, node_packs=node_packs.to_dict()) - - return web.json_response(result, content_type='application/json') - - -@routes.get("/customnode/alternatives") -async def fetch_customnode_alternatives(request): - alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json') - - res = {} - - for item in alter_json['items']: - populate_markdown(item) - res[item['id']] = item - - res = core.map_to_unified_keys(res) - - return web.json_response(res, content_type='application/json') - - -def check_model_installed(json_obj): - def is_exists(model_dir_name, filename, url): - if filename == '': - filename = os.path.basename(url) - - dirs = folder_paths.get_folder_paths(model_dir_name) - - for x in dirs: - if os.path.exists(os.path.join(x, filename)): - return True - - return False - - model_dir_names = ['checkpoints', 'loras', 'vae', 'text_encoders', 'diffusion_models', 'clip_vision', 'embeddings', - 'diffusers', 'vae_approx', 'controlnet', 'gligen', 'upscale_models', 'hypernetworks', - 'photomaker', 'classifiers'] - - total_models_files = set() - for x in model_dir_names: - for y in folder_paths.get_filename_list(x): - total_models_files.add(y) - - def process_model_phase(item): - if 'diffusion' not in item['filename'] and 'pytorch' not in item['filename'] and 'model' not in item['filename']: - # non-general name case - if item['filename'] in total_models_files: - item['installed'] = 'True' - return - - if item['save_path'] == 'default': - model_dir_name = model_dir_name_map.get(item['type'].lower()) - if model_dir_name is not None: - item['installed'] = str(is_exists(model_dir_name, item['filename'], item['url'])) - else: - item['installed'] = 'False' - else: - model_dir_name = item['save_path'].split('/')[0] - if model_dir_name in folder_paths.folder_names_and_paths: - if is_exists(model_dir_name, item['filename'], item['url']): - item['installed'] = 'True' - - if 'installed' not in item: - if item['filename'] == '': - filename = os.path.basename(item['url']) - else: - filename = item['filename'] - - fullpath = os.path.join(folder_paths.models_dir, item['save_path'], filename) - - item['installed'] = 'True' if os.path.exists(fullpath) else 'False' - - with concurrent.futures.ThreadPoolExecutor(8) as executor: - for item in json_obj['models']: - executor.submit(process_model_phase, item) - - -@routes.get("/externalmodel/getlist") -async def fetch_externalmodel_list(request): - # The model list is only allowed in the default channel, yet. - json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json') - - check_model_installed(json_obj) - - for x in json_obj['models']: - populate_markdown(x) - - return web.json_response(json_obj, content_type='application/json') - - -@PromptServer.instance.routes.get("/snapshot/getlist") -async def get_snapshot_list(request): - items = [f[:-5] for f in os.listdir(core.manager_snapshot_path) if f.endswith('.json')] - items.sort(reverse=True) - return web.json_response({'items': items}, content_type='application/json') - - -def get_safe_snapshot_path(target): - """ - Safely construct a snapshot file path, preventing path traversal attacks. - """ - if '/' in target or '\\' in target or '..' in target or '\x00' in target: - return None - return os.path.join(core.manager_snapshot_path, f"{target}.json") - - -@routes.get("/snapshot/remove") -async def remove_snapshot(request): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return security_403_response() - - try: - target = request.rel_url.query["target"] - path = get_safe_snapshot_path(target) - - if path is None: - logging.error(f"[ComfyUI-Manager] Invalid snapshot target: {target}") - return web.Response(text="Invalid snapshot target", status=400) - - if os.path.exists(path): - os.remove(path) - - return web.Response(status=200) - except: - return web.Response(status=400) - - -@routes.get("/snapshot/restore") -async def restore_snapshot(request): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return security_403_response() - - try: - target = request.rel_url.query["target"] - path = get_safe_snapshot_path(target) - - if path is None: - logging.error(f"[ComfyUI-Manager] Invalid snapshot target: {target}") - return web.Response(text="Invalid snapshot target", status=400) - - if os.path.exists(path): - if not os.path.exists(core.manager_startup_script_path): - os.makedirs(core.manager_startup_script_path) - - target_path = os.path.join(core.manager_startup_script_path, "restore-snapshot.json") - shutil.copy(path, target_path) - - logging.info(f"Snapshot restore scheduled: `{target}`") - return web.Response(status=200) - - logging.error(f"Snapshot file not found: `{path}`") - return web.Response(status=400) - except: - return web.Response(status=400) - - -@routes.get("/snapshot/get_current") -async def get_current_snapshot_api(request): - try: - return web.json_response(await core.get_current_snapshot(), content_type='application/json') - except: - return web.Response(status=400) - - -@routes.get("/snapshot/save") -async def save_snapshot(request): - try: - await core.save_snapshot_with_postfix('snapshot') - return web.Response(status=200) - except: - return web.Response(status=400) - - -def unzip_install(files): - temp_filename = 'manager-temp.zip' - for url in files: - if url.endswith("/"): - url = url[:-1] - try: - headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} - - req = urllib.request.Request(url, headers=headers) - response = urllib.request.urlopen(req) - data = response.read() - - with open(temp_filename, 'wb') as f: - f.write(data) - - with zipfile.ZipFile(temp_filename, 'r') as zip_ref: - zip_ref.extractall(core.get_default_custom_nodes_path()) - - os.remove(temp_filename) - except Exception as e: - logging.error(f"Install(unzip) error: {url} / {e}", file=sys.stderr) - return False - - logging.info("Installation was successful.") - return True - - -def copy_install(files, js_path_name=None): - for url in files: - if url.endswith("/"): - url = url[:-1] - try: - filename = os.path.basename(url) - if url.endswith(".py"): - download_url(url, core.get_default_custom_nodes_path(), filename) - else: - path = os.path.join(core.js_path, js_path_name) if js_path_name is not None else core.js_path - if not os.path.exists(path): - os.makedirs(path) - download_url(url, path, filename) - - except Exception as e: - logging.error(f"Install(copy) error: {url} / {e}", file=sys.stderr) - return False - - logging.info("Installation was successful.") - return True - - -def copy_uninstall(files, js_path_name='.'): - for url in files: - if url.endswith("/"): - url = url[:-1] - dir_name = os.path.basename(url) - base_path = core.get_default_custom_nodes_path() if url.endswith('.py') else os.path.join(core.js_path, js_path_name) - file_path = os.path.join(base_path, dir_name) - - try: - if os.path.exists(file_path): - os.remove(file_path) - elif os.path.exists(file_path + ".disabled"): - os.remove(file_path + ".disabled") - except Exception as e: - logging.error(f"Uninstall(copy) error: {url} / {e}", file=sys.stderr) - return False - - logging.info("Uninstallation was successful.") - return True - - -def copy_set_active(files, is_disable, js_path_name='.'): - if is_disable: - action_name = "Disable" - else: - action_name = "Enable" - - for url in files: - if url.endswith("/"): - url = url[:-1] - dir_name = os.path.basename(url) - base_path = core.get_default_custom_nodes_path() if url.endswith('.py') else os.path.join(core.js_path, js_path_name) - file_path = os.path.join(base_path, dir_name) - - try: - if is_disable: - current_name = file_path - new_name = file_path + ".disabled" - else: - current_name = file_path + ".disabled" - new_name = file_path - - os.rename(current_name, new_name) - - except Exception as e: - logging.error(f"{action_name}(copy) error: {url} / {e}", file=sys.stderr) - - return False - - logging.info(f"{action_name} was successful.") - return True - - -@routes.get("/customnode/versions/{node_name}") -async def get_cnr_versions(request): - node_name = request.match_info.get("node_name", None) - versions = core.cnr_utils.all_versions_of_node(node_name) - - if versions is not None: - return web.json_response(versions, content_type='application/json') - - return web.Response(status=400) - - -@routes.get("/customnode/disabled_versions/{node_name}") -async def get_disabled_versions(request): - node_name = request.match_info.get("node_name", None) - versions = [] - if node_name in core.unified_manager.nightly_inactive_nodes: - versions.append(dict(version='nightly')) - - for v in core.unified_manager.cnr_inactive_nodes.get(node_name, {}).keys(): - versions.append(dict(version=v)) - - if versions: - return web.json_response(versions, content_type='application/json') - - return web.Response(status=400) - - -@routes.post("/customnode/import_fail_info") -async def import_fail_info(request): - json_data = await request.json() - - if 'cnr_id' in json_data: - module_name = core.unified_manager.get_module_name(json_data['cnr_id']) - else: - module_name = core.unified_manager.get_module_name(json_data['url']) - - if module_name is not None: - info = cm_global.error_dict.get(module_name) - if info is not None: - return web.json_response(info) - - return web.Response(status=400) - - -@routes.post("/manager/queue/reinstall") -async def reinstall_custom_node(request): - await uninstall_custom_node(request) - await install_custom_node(request) - - -@routes.get("/manager/queue/reset") -async def reset_queue(request): - global task_queue - task_queue = queue.Queue() - return web.Response(status=200) - - -@routes.get("/manager/queue/status") -async def queue_count(request): - global task_queue - - with task_worker_lock: - done_count = len(nodepack_result) + len(model_result) - in_progress_count = len(tasks_in_progress) - total_count = done_count + in_progress_count + task_queue.qsize() - is_processing = task_worker_thread is not None and task_worker_thread.is_alive() - - return web.json_response({ - 'total_count': total_count, 'done_count': done_count, 'in_progress_count': in_progress_count, - 'is_processing': is_processing}) - - -@routes.post("/manager/queue/install") -async def install_custom_node(request): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") - - json_data = await request.json() - - # non-nightly cnr is safe - risky_level = None - cnr_id = json_data.get('id') - skip_post_install = json_data.get('skip_post_install') - - git_url = None - - selected_version = json_data.get('selected_version') - if json_data['version'] != 'unknown' and selected_version != 'unknown': - if skip_post_install: - if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes: - core.unified_manager.unified_enable(cnr_id) - return web.Response(status=200) - elif selected_version is None: - selected_version = 'latest' - - if selected_version != 'nightly': - risky_level = 'low' - node_spec_str = f"{cnr_id}@{selected_version}" - else: - node_spec_str = f"{cnr_id}@nightly" - git_url = [json_data.get('repository')] - if git_url is None: - logging.error(f"[ComfyUI-Manager] Following node pack doesn't provide `nightly` version: ${git_url}") - return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}") - elif json_data['version'] != 'unknown' and selected_version == 'unknown': - logging.error(f"[ComfyUI-Manager] Invalid installation request: {json_data}") - return web.Response(status=400, text="Invalid installation request") - else: - # unknown - unknown_name = os.path.basename(json_data['files'][0]) - node_spec_str = f"{unknown_name}@unknown" - git_url = json_data.get('files') - - # apply security policy if not cnr node (nightly isn't regarded as cnr node) - if risky_level is None: - if git_url is not None: - risky_level = await get_risky_level(git_url, json_data.get('pip', [])) - else: - return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}") - - if not is_allowed_security_level(risky_level): - logging.error(SECURITY_MESSAGE_GENERAL) - return web.Response(status=404, text="A security error has occurred. Please check the terminal logs") - - install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install - task_queue.put(("install", install_item)) - - return web.Response(status=200) - - -task_worker_thread:threading.Thread = None - -@routes.get("/manager/queue/start") -async def queue_start(request): - global nodepack_result - global model_result - global task_worker_thread - - if task_worker_thread is not None and task_worker_thread.is_alive(): - return web.Response(status=201) # already in-progress - - nodepack_result = {} - model_result = {} - - task_worker_thread = threading.Thread(target=lambda: asyncio.run(task_worker())) - task_worker_thread.start() - - return web.Response(status=200) - - -@routes.post("/manager/queue/fix") -async def fix_custom_node(request): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_GENERAL) - return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") - - json_data = await request.json() - - node_id = json_data.get('id') - node_ver = json_data['version'] - if node_ver != 'unknown': - node_name = node_id - else: - # unknown - node_name = os.path.basename(json_data['files'][0]) - - update_item = json_data.get('ui_id'), node_name, json_data['version'] - task_queue.put(("fix", update_item)) - - return web.Response(status=200) - - -@routes.post("/customnode/install/git_url") -async def install_custom_node_git_url(request): - if not is_allowed_security_level('high'): - logging.error(SECURITY_MESSAGE_NORMAL_MINUS) - return security_403_response() - - url = await request.text() - res = await core.gitclone_install(url) - - if res.action == 'skip': - logging.info(f"\nAlready installed: '{res.target}'") - return web.Response(status=200) - elif res.result: - logging.info("\nAfter restarting ComfyUI, please refresh the browser.") - return web.Response(status=200) - - logging.error(res.msg) - return web.Response(status=400) - - -@routes.post("/customnode/install/pip") -async def install_custom_node_pip(request): - if not is_allowed_security_level('high'): - logging.error(SECURITY_MESSAGE_NORMAL_MINUS) - return security_403_response() - - packages = await request.text() - core.pip_install(packages.split(' ')) - - return web.Response(status=200) - - -@routes.post("/manager/queue/uninstall") -async def uninstall_custom_node(request): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") - - json_data = await request.json() - - node_id = json_data.get('id') - if json_data['version'] != 'unknown': - is_unknown = False - node_name = node_id - else: - # unknown - is_unknown = True - node_name = os.path.basename(json_data['files'][0]) - - uninstall_item = json_data.get('ui_id'), node_name, is_unknown - task_queue.put(("uninstall", uninstall_item)) - - return web.Response(status=200) - - -@routes.post("/manager/queue/update") -async def update_custom_node(request): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") - - json_data = await request.json() - - node_id = json_data.get('id') - if json_data['version'] != 'unknown': - node_name = node_id - else: - # unknown - node_name = os.path.basename(json_data['files'][0]) - - update_item = json_data.get('ui_id'), node_name, json_data['version'] - task_queue.put(("update", update_item)) - - return web.Response(status=200) - - -@routes.get("/manager/queue/update_comfyui") -async def update_comfyui(request): - is_stable = core.get_config()['update_policy'] != 'nightly-comfyui' - task_queue.put(("update-comfyui", ('comfyui', is_stable))) - return web.Response(status=200) - - -@routes.get("/comfyui_manager/comfyui_versions") -async def comfyui_versions(request): - try: - res, current, latest = core.get_comfyui_versions() - return web.json_response({'versions': res, 'current': current}, status=200, content_type='application/json') - except Exception as e: - logging.error(f"ComfyUI update fail: {e}", file=sys.stderr) - - return web.Response(status=400) - - -@routes.get("/comfyui_manager/comfyui_switch_version") -async def comfyui_switch_version(request): - try: - if "ver" in request.rel_url.query: - core.switch_comfyui(request.rel_url.query['ver']) - - return web.Response(status=200) - except Exception as e: - logging.error(f"ComfyUI update fail: {e}", file=sys.stderr) - - return web.Response(status=400) - - -@routes.post("/manager/queue/disable") -async def disable_node(request): - json_data = await request.json() - - node_id = json_data.get('id') - if json_data['version'] != 'unknown': - is_unknown = False - node_name = node_id - else: - # unknown - is_unknown = True - node_name = os.path.basename(json_data['files'][0]) - - update_item = json_data.get('ui_id'), node_name, is_unknown - task_queue.put(("disable", update_item)) - - return web.Response(status=200) - - -async def check_whitelist_for_model(item): - json_obj = await core.get_data_by_mode('cache', 'model-list.json') - - for x in json_obj.get('models', []): - if x['save_path'] == item['save_path'] and x['base'] == item['base'] and x['filename'] == item['filename']: - return True - - json_obj = await core.get_data_by_mode('local', 'model-list.json') - - for x in json_obj.get('models', []): - if x['save_path'] == item['save_path'] and x['base'] == item['base'] and x['filename'] == item['filename']: - return True - - return False - - -@routes.post("/manager/queue/install_model") -async def install_model(request): - json_data = await request.json() - - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") - - # validate request - if not await check_whitelist_for_model(json_data): - logging.error(f"[ComfyUI-Manager] Invalid model install request is detected: {json_data}") - return web.Response(status=400, text="Invalid model install request is detected") - - if not json_data['filename'].endswith('.safetensors') and not is_allowed_security_level('high'): - models_json = await core.get_data_by_mode('cache', 'model-list.json', 'default') - - is_belongs_to_whitelist = False - for x in models_json['models']: - if x.get('url') == json_data['url']: - is_belongs_to_whitelist = True - break - - if not is_belongs_to_whitelist: - logging.error(SECURITY_MESSAGE_NORMAL_MINUS_MODEL) - return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") - - install_item = json_data.get('ui_id'), json_data - task_queue.put(("install-model", install_item)) - - return web.Response(status=200) - - -@routes.get("/manager/preview_method") -async def preview_method(request): - # Setting change request - if "value" in request.rel_url.query: - # Reject setting change if per-queue preview feature is available - if COMFYUI_HAS_PER_QUEUE_PREVIEW: - return web.Response(text="DISABLED", status=403) - - # Process normally if not available - set_preview_method(request.rel_url.query['value']) - core.write_config() - return web.Response(status=200) - - # Status query request - else: - # Return DISABLED if per-queue preview feature is available - if COMFYUI_HAS_PER_QUEUE_PREVIEW: - return web.Response(text="DISABLED", status=200) - - # Return current value if not available - return web.Response(text=core.manager_funcs.get_current_preview_method(), status=200) - - -@routes.get("/manager/db_mode") -async def db_mode(request): - if "value" in request.rel_url.query: - set_db_mode(request.rel_url.query['value']) - core.write_config() - else: - return web.Response(text=core.get_config()['db_mode'], status=200) - - return web.Response(status=200) - - - -@routes.get("/manager/policy/component") -async def component_policy(request): - if "value" in request.rel_url.query: - set_component_policy(request.rel_url.query['value']) - core.write_config() - else: - return web.Response(text=core.get_config()['component_policy'], status=200) - - return web.Response(status=200) - - -@routes.get("/manager/policy/update") -async def update_policy(request): - if "value" in request.rel_url.query: - set_update_policy(request.rel_url.query['value']) - core.write_config() - else: - return web.Response(text=core.get_config()['update_policy'], status=200) - - return web.Response(status=200) - - -@routes.get("/manager/channel_url_list") -async def channel_url_list(request): - channels = core.get_channel_dict() - if "value" in request.rel_url.query: - channel_url = channels.get(request.rel_url.query['value']) - if channel_url is not None: - core.get_config()['channel_url'] = channel_url - core.write_config() - else: - selected = 'custom' - selected_url = core.get_config()['channel_url'] - - for name, url in channels.items(): - if url == selected_url: - selected = name - break - - res = {'selected': selected, - 'list': core.get_channel_list()} - return web.json_response(res, status=200) - - return web.Response(status=200) - - -def add_target_blank(html_text): - pattern = r'(]*)(>)' - - def add_target(match): - if 'target=' not in match.group(1): - return match.group(1) + ' target="_blank"' + match.group(2) - return match.group(0) - - modified_html = re.sub(pattern, add_target, html_text) - - return modified_html - - -@routes.get("/manager/notice") -async def get_notice(request): - url = "github.com" - path = "/ltdrdata/ltdrdata.github.io/wiki/News" - - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - async with session.get(f"https://{url}{path}") as response: - if response.status == 200: - # html_content = response.read().decode('utf-8') - html_content = await response.text() - - pattern = re.compile(r'
([\s\S]*?)
') - match = pattern.search(html_content) - - if match: - markdown_content = match.group(1) - version_tag = os.environ.get('__COMFYUI_DESKTOP_VERSION__') - if version_tag is not None: - markdown_content += f"
ComfyUI: {version_tag} [Desktop]" - else: - version_tag = core.get_comfyui_tag() - if version_tag is None: - markdown_content += f"
ComfyUI: {core.comfy_ui_revision}[{comfy_ui_hash[:6]}]({core.comfy_ui_commit_datetime.date()})" - else: - markdown_content += (f"
ComfyUI: {version_tag}
" - f"         ({core.comfy_ui_commit_datetime.date()})") - # markdown_content += f"
         ()" - markdown_content += f"
Manager: {core.version_str}" - - markdown_content = add_target_blank(markdown_content) - - try: - if '__COMFYUI_DESKTOP_VERSION__' not in os.environ: - if core.comfy_ui_commit_datetime == datetime(1900, 1, 1, 0, 0, 0): - markdown_content = '

Your ComfyUI isn\'t git repo.

' + markdown_content - elif core.comfy_ui_required_commit_datetime.date() > core.comfy_ui_commit_datetime.date(): - markdown_content = '

Your ComfyUI is too OUTDATED!!!

' + markdown_content - except: - pass - - # Prepend startup notices from manager_migration - for message, level in reversed(manager_migration.startup_notices): - if level == 'error': - style = 'color:red; background-color:white; font-weight:bold' - elif level == 'warning': - style = 'color:orange; background-color:white; font-weight:bold' - else: - style = 'color:blue; background-color:white' - markdown_content = f'

{message}

' + markdown_content - - return web.Response(text=markdown_content, status=200) - else: - return web.Response(text="Unable to retrieve Notice", status=200) - else: - return web.Response(text="Unable to retrieve Notice", status=200) - - -@routes.get("/manager/startup_alerts") -async def get_startup_alerts(request): - """Return startup alerts for customAlert display on page load. - - Returns JSON array of alerts that should be shown to user immediately. - All startup notices (error, warning, info) are returned. - """ - alerts = [] - - # Return all startup notices for alert display - for message, level in manager_migration.startup_notices: - # Convert HTML BR to newlines for customAlert - text = message.replace('
', '\n').replace('
', '\n') - # Add [ComfyUI-Manager] prefix for customAlert (notice board shows in Manager UI anyway) - text = text.replace('[Security Alert]', '[ComfyUI-Manager] Security Alert:') - text = text.replace('[MIGRATION]', '[ComfyUI-Manager] Migration:') - alerts.append({ - 'message': text, - 'level': level - }) - - return web.json_response(alerts) - - -@routes.get("/manager/reboot") -def restart(self): - if not is_allowed_security_level('middle'): - logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) - return security_403_response() - - try: - sys.stdout.close_log() - except Exception: - pass - - if '__COMFY_CLI_SESSION__' in os.environ: - with open(os.path.join(os.environ['__COMFY_CLI_SESSION__'] + '.reboot'), 'w'): - pass - - print("\nRestarting...\n\n") # This printing should not be logging - that will be ugly - exit(0) - - print("\nRestarting... [Legacy Mode]\n\n") # This printing should not be logging - that will be ugly - - sys_argv = sys.argv.copy() - if '--windows-standalone-build' in sys_argv: - sys_argv.remove('--windows-standalone-build') - - if sys_argv[0].endswith("__main__.py"): # this is a python module - module_name = os.path.basename(os.path.dirname(sys_argv[0])) - cmds = [sys.executable, '-m', module_name] + sys_argv[1:] - elif sys.platform.startswith('win32'): - cmds = ['"' + sys.executable + '"', '"' + sys_argv[0] + '"'] + sys_argv[1:] - else: - cmds = [sys.executable] + sys_argv - - print(f"Command: {cmds}", flush=True) - - return os.execv(sys.executable, cmds) - - -@routes.post("/manager/component/save") -async def save_component(request): - try: - data = await request.json() - name = data['name'] - workflow = data['workflow'] - - if not os.path.exists(core.manager_components_path): - os.mkdir(core.manager_components_path) - - if 'packname' in workflow and workflow['packname'] != '': - sanitized_name = manager_util.sanitize_filename(workflow['packname']) + '.pack' - else: - sanitized_name = manager_util.sanitize_filename(name) + '.json' - - filepath = os.path.join(core.manager_components_path, sanitized_name) - components = {} - if os.path.exists(filepath): - with open(filepath) as f: - components = json.load(f) - - components[name] = workflow - - with open(filepath, 'w') as f: - json.dump(components, f, indent=4, sort_keys=True) - return web.Response(text=filepath, status=200) - except: - return web.Response(status=400) - - -@routes.post("/manager/component/loads") -async def load_components(request): - if os.path.exists(core.manager_components_path): - try: - json_files = [f for f in os.listdir(core.manager_components_path) if f.endswith('.json')] - pack_files = [f for f in os.listdir(core.manager_components_path) if f.endswith('.pack')] - - components = {} - for json_file in json_files + pack_files: - file_path = os.path.join(core.manager_components_path, json_file) - with open(file_path, 'r') as file: - try: - # When there is a conflict between the .pack and the .json, the pack takes precedence and overrides. - components.update(json.load(file)) - except json.JSONDecodeError as e: - logging.error(f"[ComfyUI-Manager] Error decoding component file in file {json_file}: {e}") - - return web.json_response(components) - except Exception as e: - logging.error(f"[ComfyUI-Manager] failed to load components\n{e}") - return web.Response(status=400) - else: - return web.json_response({}) - - -@routes.get("/manager/version") -async def get_version(request): - return web.Response(text=core.version_str, status=200) - - -async def _confirm_try_install(sender, custom_node_url, msg): - json_obj = await core.get_data_by_mode('default', 'custom-node-list.json') - - sender = manager_util.sanitize_tag(sender) - msg = manager_util.sanitize_tag(msg) - target = core.lookup_customnode_by_url(json_obj, custom_node_url) - - if target is not None: - PromptServer.instance.send_sync("cm-api-try-install-customnode", - {"sender": sender, "target": target, "msg": msg}) - else: - logging.error(f"[ComfyUI Manager API] Failed to try install - Unknown custom node url '{custom_node_url}'") - - -def confirm_try_install(sender, custom_node_url, msg): - asyncio.run(_confirm_try_install(sender, custom_node_url, msg)) - - -cm_global.register_api('cm.try-install-custom-node', confirm_try_install) - - -async def default_cache_update(): - core.refresh_channel_dict() - channel_url = core.get_config()['channel_url'] - async def get_cache(filename): - try: - if core.get_config()['default_cache_as_channel_url']: - uri = f"{channel_url}/{filename}" - else: - uri = f"{core.DEFAULT_CHANNEL}/{filename}" - - cache_uri = str(manager_util.simple_hash(uri)) + '_' + filename - cache_uri = os.path.join(manager_util.cache_dir, cache_uri) - - json_obj = await manager_util.get_data(uri, True) - - with manager_util.cache_lock: - with open(cache_uri, "w", encoding='utf-8') as file: - json.dump(json_obj, file, indent=4, sort_keys=True) - logging.info(f"[ComfyUI-Manager] default cache updated: {uri}") - except Exception as e: - logging.error(f"[ComfyUI-Manager] Failed to perform initial fetching '{filename}': {e}") - traceback.print_exc() - - 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") - d = get_cache("alter-list.json") - e = get_cache("github-stats.json") - - await asyncio.gather(a, b, c, d, e) - - if core.get_config()['network_mode'] == 'private': - logging.info("[ComfyUI-Manager] The private comfyregistry is not yet supported in `network_mode=private`.") - else: - # load at least once - await core.unified_manager.reload('remote', dont_wait=False) - await core.unified_manager.get_custom_nodes(channel_url, 'remote') - - logging.info("[ComfyUI-Manager] All startup tasks have been completed.") - - -threading.Thread(target=lambda: asyncio.run(default_cache_update())).start() - -if not os.path.exists(core.manager_config_path): - core.get_config() - core.write_config() - - -cm_global.register_extension('ComfyUI-Manager', - {'version': core.version, - 'name': 'ComfyUI Manager', - 'nodes': {}, - 'description': 'This extension provides the ability to manage custom nodes in ComfyUI.', }) - - diff --git a/glob/manager_util.py b/glob/manager_util.py deleted file mode 100644 index fd110b7f..00000000 --- a/glob/manager_util.py +++ /dev/null @@ -1,633 +0,0 @@ -""" -description: - `manager_util` is the lightest module shared across the prestartup_script, main code, and cm-cli of ComfyUI-Manager. -""" -import traceback - -import aiohttp -import json -import threading -import os -from datetime import datetime -import subprocess -import sys -import re -import logging -import platform -import shlex -from functools import lru_cache - - -cache_lock = threading.Lock() - -comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also updated together in **manager_core.update_user_directory**. - -use_uv = False -bypass_ssl = False - -def add_python_path_to_env(): - if platform.system() != "Windows": - sep = ':' - else: - sep = ';' - - 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): - """ - 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: -# from distutils.version import StrictVersion -# except: -# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.") -class StrictVersion: - def __init__(self, version_string): - self.version_string = version_string - self.major = 0 - self.minor = 0 - self.patch = 0 - self.pre_release = None - self.parse_version_string() - - def parse_version_string(self): - parts = self.version_string.split('.') - if not parts: - raise ValueError("Version string must not be empty") - - self.major = int(parts[0]) - self.minor = int(parts[1]) if len(parts) > 1 else 0 - self.patch = int(parts[2]) if len(parts) > 2 else 0 - - # Handling pre-release versions if present - if len(parts) > 3: - self.pre_release = parts[3] - - def __str__(self): - version = f"{self.major}.{self.minor}.{self.patch}" - if self.pre_release: - version += f"-{self.pre_release}" - return version - - def __eq__(self, other): - return (self.major, self.minor, self.patch, self.pre_release) == \ - (other.major, other.minor, other.patch, other.pre_release) - - def __lt__(self, other): - if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch): - return self.pre_release_compare(self.pre_release, other.pre_release) < 0 - return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch) - - @staticmethod - def pre_release_compare(pre1, pre2): - if pre1 == pre2: - return 0 - if pre1 is None: - return 1 - if pre2 is None: - return -1 - return -1 if pre1 < pre2 else 1 - - def __le__(self, other): - return self == other or self < other - - def __gt__(self, other): - return not self <= other - - def __ge__(self, other): - return not self < other - - def __ne__(self, other): - return not self == other - - -def simple_hash(input_string): - hash_value = 0 - for char in input_string: - hash_value = (hash_value * 31 + ord(char)) % (2**32) - - return hash_value - - -def is_file_created_within_one_day(file_path): - if not os.path.exists(file_path): - return False - - file_creation_time = os.path.getctime(file_path) - current_time = datetime.now().timestamp() - time_difference = current_time - file_creation_time - - return time_difference <= 86400 - - -async def get_data(uri, silent=False): - if not silent: - print(f"FETCH DATA from: {uri}", end="") - - if uri.startswith("http"): - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=not bypass_ssl)) as session: - headers = { - 'Cache-Control': 'no-cache', - 'Pragma': 'no-cache', - 'Expires': '0' - } - async with session.get(uri, headers=headers) as resp: - json_text = await resp.text() - else: - with cache_lock: - with open(uri, "r", encoding="utf-8") as f: - json_text = f.read() - - try: - json_obj = json.loads(json_text) - except Exception as e: - logging.error(f"[ComfyUI-Manager] An error occurred while fetching '{uri}': {e}") - - return {} - - if not silent: - print(" [DONE]") - - return json_obj - - -def get_cache_path(uri): - cache_uri = str(simple_hash(uri)) + '_' + os.path.basename(uri).replace('&', "_").replace('?', "_").replace('=', "_") - return os.path.join(cache_dir, cache_uri+'.json') - - -def get_cache_state(uri): - cache_uri = get_cache_path(uri) - - if not os.path.exists(cache_uri): - return "not-cached" - elif is_file_created_within_one_day(cache_uri): - return "cached" - - return "expired" - - -def save_to_cache(uri, json_obj, silent=False): - cache_uri = get_cache_path(uri) - - with cache_lock: - with open(cache_uri, "w", encoding='utf-8') as file: - json.dump(json_obj, file, indent=4, sort_keys=True) - if not silent: - logging.info(f"[ComfyUI-Manager] default cache updated: {uri}") - - -async def get_data_with_cache(uri, silent=False, cache_mode=True, dont_wait=False, dont_cache=False): - cache_uri = get_cache_path(uri) - - if cache_mode and dont_wait: - # NOTE: return the cache if possible, even if it is expired, so do not cache - if not os.path.exists(cache_uri): - logging.error(f"[ComfyUI-Manager] The network connection is unstable, so it is operating in fallback mode: {uri}") - - return {} - else: - if not is_file_created_within_one_day(cache_uri): - logging.error(f"[ComfyUI-Manager] The network connection is unstable, so it is operating in outdated cache mode: {uri}") - - return await get_data(cache_uri, silent=silent) - - if cache_mode and is_file_created_within_one_day(cache_uri): - json_obj = await get_data(cache_uri, silent=silent) - else: - json_obj = await get_data(uri, silent=silent) - if not dont_cache: - with cache_lock: - with open(cache_uri, "w", encoding='utf-8') as file: - json.dump(json_obj, file, indent=4, sort_keys=True) - if not silent: - logging.info(f"[ComfyUI-Manager] default cache updated: {uri}") - - return json_obj - - -def sanitize_tag(x): - return x.replace('<', '<').replace('>', '>') - - -def extract_package_as_zip(file_path, extract_path): - import zipfile - try: - with zipfile.ZipFile(file_path, "r") as zip_ref: - zip_ref.extractall(extract_path) - extracted_files = zip_ref.namelist() - logging.info(f"Extracted zip file to {extract_path}") - return extracted_files - except zipfile.BadZipFile: - logging.error(f"File '{file_path}' is not a zip or is corrupted.") - return None - - -pip_map = None - - -def get_installed_packages(renew=False): - global pip_map - - if renew or pip_map is None: - try: - result = subprocess.check_output(make_pip_cmd(['list']), universal_newlines=True) - - pip_map = {} - for line in result.split('\n'): - x = line.strip() - if x: - y = line.split() - if y[0] == 'Package' or y[0].startswith('-'): - continue - - normalized_name = y[0].lower().replace('-', '_') - pip_map[normalized_name] = y[1] - except subprocess.CalledProcessError: - logging.error("[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.") - return {} - - return pip_map - - -def clear_pip_cache(): - global pip_map - pip_map = None - - -def parse_requirement_line(line): - tokens = shlex.split(line) - if not tokens: - return None - - package_spec = tokens[0] - - pattern = re.compile( - r'^(?P[A-Za-z0-9_.+-]+)' - r'(?P==|>=|<=|!=|~=|>|<)?' - r'(?P[A-Za-z0-9_.+-]*)$' - ) - m = pattern.match(package_spec) - if not m: - return None - - package = m.group('package') - operator = m.group('operator') or None - version = m.group('version') or None - - index_url = None - if '--index-url' in tokens: - idx = tokens.index('--index-url') - if idx + 1 < len(tokens): - index_url = tokens[idx + 1] - - res = {'package': package} - - if operator is not None: - res['operator'] = operator - - if version is not None: - res['version'] = StrictVersion(version) - - if index_url is not None: - res['index_url'] = index_url - - return res - - -torch_torchvision_torchaudio_version_map = { - '2.7.0': ('0.22.0', '2.7.0'), - '2.6.0': ('0.21.0', '2.6.0'), - '2.5.1': ('0.20.0', '2.5.0'), - '2.5.0': ('0.20.0', '2.5.0'), - '2.4.1': ('0.19.1', '2.4.1'), - '2.4.0': ('0.19.0', '2.4.0'), - '2.3.1': ('0.18.1', '2.3.1'), - '2.3.0': ('0.18.0', '2.3.0'), - '2.2.2': ('0.17.2', '2.2.2'), - '2.2.1': ('0.17.1', '2.2.1'), - '2.2.0': ('0.17.0', '2.2.0'), - '2.1.2': ('0.16.2', '2.1.2'), - '2.1.1': ('0.16.1', '2.1.1'), - '2.1.0': ('0.16.0', '2.1.0'), - '2.0.1': ('0.15.2', '2.0.1'), - '2.0.0': ('0.15.1', '2.0.0'), -} - - -def torch_rollback(prev): - spec = prev.split('+') - if len(spec) > 1: - platform = spec[1] - else: - cmd = make_pip_cmd(['install', '--force', 'torch', 'torchvision', 'torchaudio']) - subprocess.check_output(cmd, universal_newlines=True) - logging.error(cmd) - return - - torch_ver = StrictVersion(spec[0]) - torch_ver = f"{torch_ver.major}.{torch_ver.minor}.{torch_ver.patch}" - torch_torchvision_torchaudio_ver = torch_torchvision_torchaudio_version_map.get(torch_ver) - - if torch_torchvision_torchaudio_ver is None: - cmd = make_pip_cmd(['install', '--pre', 'torch', 'torchvision', 'torchaudio', - '--index-url', f"https://download.pytorch.org/whl/nightly/{platform}"]) - logging.info("[ComfyUI-Manager] restore PyTorch to nightly version") - else: - torchvision_ver, torchaudio_ver = torch_torchvision_torchaudio_ver - cmd = make_pip_cmd(['install', f'torch=={torch_ver}', f'torchvision=={torchvision_ver}', f"torchaudio=={torchaudio_ver}", - '--index-url', f"https://download.pytorch.org/whl/{platform}"]) - logging.info(f"[ComfyUI-Manager] restore PyTorch to {torch_ver}+{platform}") - - subprocess.check_output(cmd, universal_newlines=True) - - -class PIPFixer: - def __init__(self, prev_pip_versions, comfyui_path, manager_files_path): - self.prev_pip_versions = { **prev_pip_versions } - self.comfyui_path = comfyui_path - self.manager_files_path = manager_files_path - - def fix_broken(self): - new_pip_versions = get_installed_packages(True) - - # remove `comfy` python package - try: - if 'comfy' in new_pip_versions: - cmd = make_pip_cmd(['uninstall', 'comfy']) - subprocess.check_output(cmd, universal_newlines=True) - - logging.warning("[ComfyUI-Manager] 'comfy' python package is uninstalled.\nWARN: The 'comfy' package is completely unrelated to ComfyUI and should never be installed as it causes conflicts with ComfyUI.") - except Exception as e: - logging.error("[ComfyUI-Manager] Failed to uninstall `comfy` python package") - logging.error(e) - - # fix torch - reinstall torch packages if version is changed - try: - if 'torch' not in self.prev_pip_versions or 'torchvision' not in self.prev_pip_versions or 'torchaudio' not in self.prev_pip_versions: - logging.error("[ComfyUI-Manager] PyTorch is not installed") - elif self.prev_pip_versions['torch'] != new_pip_versions['torch'] \ - or self.prev_pip_versions['torchvision'] != new_pip_versions['torchvision'] \ - or self.prev_pip_versions['torchaudio'] != new_pip_versions['torchaudio']: - torch_rollback(self.prev_pip_versions['torch']) - except Exception as e: - logging.error("[ComfyUI-Manager] Failed to restore PyTorch") - logging.error(e) - - # fix opencv - try: - ocp = new_pip_versions.get('opencv-contrib-python') - ocph = new_pip_versions.get('opencv-contrib-python-headless') - op = new_pip_versions.get('opencv-python') - oph = new_pip_versions.get('opencv-python-headless') - - versions = [ocp, ocph, op, oph] - versions = [StrictVersion(x) for x in versions if x is not None] - versions.sort(reverse=True) - - if len(versions) > 0: - # upgrade to maximum version - targets = [] - cur = versions[0] - if ocp is not None and StrictVersion(ocp) != cur: - targets.append('opencv-contrib-python') - if ocph is not None and StrictVersion(ocph) != cur: - targets.append('opencv-contrib-python-headless') - if op is not None and StrictVersion(op) != cur: - targets.append('opencv-python') - if oph is not None and StrictVersion(oph) != cur: - targets.append('opencv-python-headless') - - if len(targets) > 0: - for x in targets: - cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}"]) - subprocess.check_output(cmd, universal_newlines=True) - - logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}") - except Exception as e: - logging.error("[ComfyUI-Manager] Failed to restore opencv") - logging.error(e) - - # fix missing frontend - try: - # NOTE: package name in requirements is 'comfyui-frontend-package' - # but, package name from `pip freeze` is 'comfyui_frontend_package' - # but, package name from `uv pip freeze` is 'comfyui-frontend-package' - # - # get_installed_packages returns normalized name (i.e. comfyui_frontend_package) - if 'comfyui_frontend_package' not in new_pip_versions: - requirements_path = os.path.join(self.comfyui_path, 'requirements.txt') - - with open(requirements_path, 'r') as file: - lines = file.readlines() - - front_line = next((line.strip() for line in lines if line.startswith('comfyui-frontend-package')), None) - if front_line is None: - logging.info("[ComfyUI-Manager] Skipped fixing the 'comfyui-frontend-package' dependency because the ComfyUI is outdated.") - else: - cmd = make_pip_cmd(['install', front_line]) - subprocess.check_output(cmd , universal_newlines=True) - logging.info("[ComfyUI-Manager] 'comfyui-frontend-package' dependency were fixed") - except Exception as e: - logging.error("[ComfyUI-Manager] Failed to restore comfyui-frontend-package") - logging.error(e) - - # restore based on custom list - pip_auto_fix_path = os.path.join(self.manager_files_path, "pip_auto_fix.list") - if os.path.exists(pip_auto_fix_path): - with open(pip_auto_fix_path, 'r', encoding="UTF-8", errors="ignore") as f: - fixed_list = [] - - for x in f.readlines(): - try: - parsed = parse_requirement_line(x) - need_to_reinstall = True - - normalized_name = parsed['package'].lower().replace('-', '_') - if normalized_name in new_pip_versions: - if 'version' in parsed and 'operator' in parsed: - cur = StrictVersion(new_pip_versions[normalized_name]) - dest = parsed['version'] - op = parsed['operator'] - if cur == dest: - if op in ['==', '>=', '<=']: - need_to_reinstall = False - elif cur < dest: - if op in ['<=', '<', '~=', '!=']: - need_to_reinstall = False - elif cur > dest: - if op in ['>=', '>', '~=', '!=']: - need_to_reinstall = False - - if need_to_reinstall: - cmd_args = ['install'] - if 'version' in parsed and 'operator' in parsed: - cmd_args.append(parsed['package']+parsed['operator']+parsed['version'].version_string) - - if 'index_url' in parsed: - cmd_args.append('--index-url') - cmd_args.append(parsed['index_url']) - - cmd = make_pip_cmd(cmd_args) - subprocess.check_output(cmd, universal_newlines=True) - - fixed_list.append(parsed['package']) - except Exception as e: - traceback.print_exc() - logging.error(f"[ComfyUI-Manager] Failed to restore '{x}'") - logging.error(e) - - if len(fixed_list) > 0: - logging.info(f"[ComfyUI-Manager] dependencies in pip_auto_fix.json were fixed: {fixed_list}") - -def sanitize(data): - return data.replace("<", "<").replace(">", ">") - - -def sanitize_filename(input_string): - result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string) - return result_string - - -def robust_readlines(fullpath): - import chardet - try: - with open(fullpath, "r") as f: - return f.readlines() - except: - encoding = None - with open(fullpath, "rb") as f: - raw_data = f.read() - result = chardet.detect(raw_data) - encoding = result['encoding'] - - if encoding is not None: - with open(fullpath, "r", encoding=encoding) as f: - return f.readlines() - - print(f"[ComfyUI-Manager] Failed to recognize encoding for: {fullpath}") - return [] - - -def restore_pip_snapshot(pips, options): - non_url = [] - local_url = [] - non_local_url = [] - - for k, v in pips.items(): - # NOTE: skip torch related packages - if k.startswith("torch==") or k.startswith("torchvision==") or k.startswith("torchaudio==") or k.startswith("nvidia-"): - continue - - if v == "": - non_url.append(k) - else: - if v.startswith('file:'): - local_url.append(v) - else: - non_local_url.append(v) - - - # restore other pips - failed = [] - if '--pip-non-url' in options: - # try all at once - res = 1 - try: - res = subprocess.check_output(make_pip_cmd(['install'] + non_url)) - except Exception: - pass - - # fallback - if res != 0: - for x in non_url: - res = 1 - try: - res = subprocess.check_output(make_pip_cmd(['install', '--no-deps', x])) - except Exception: - pass - - if res != 0: - failed.append(x) - - if '--pip-non-local-url' in options: - for x in non_local_url: - res = 1 - try: - res = subprocess.check_output(make_pip_cmd(['install', '--no-deps', x])) - except Exception: - pass - - if res != 0: - failed.append(x) - - if '--pip-local-url' in options: - for x in local_url: - res = 1 - try: - res = subprocess.check_output(make_pip_cmd(['install', '--no-deps', x])) - except Exception: - pass - - if res != 0: - failed.append(x) - - print(f"Installation failed for pip packages: {failed}") \ No newline at end of file diff --git a/glob/node_package.py b/glob/node_package.py deleted file mode 100644 index d199fa30..00000000 --- a/glob/node_package.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -import os - -from git_utils import get_commit_hash - - -@dataclass -class InstalledNodePackage: - """Information about an installed node package.""" - - id: str - fullpath: str - disabled: bool - version: str - - @property - def is_unknown(self) -> bool: - return self.version == "unknown" - - @property - def is_nightly(self) -> bool: - return self.version == "nightly" - - @property - def is_from_cnr(self) -> bool: - return not self.is_unknown and not self.is_nightly - - @property - def is_enabled(self) -> bool: - return not self.disabled - - @property - def is_disabled(self) -> bool: - return self.disabled - - def get_commit_hash(self) -> str: - return get_commit_hash(self.fullpath) - - def isValid(self) -> bool: - if self.is_from_cnr: - return os.path.exists(os.path.join(self.fullpath, '.tracking')) - - return True - - @staticmethod - def from_fullpath(fullpath: str, resolve_from_path) -> InstalledNodePackage: - parent_folder_name = os.path.basename(os.path.dirname(fullpath)) - module_name = os.path.basename(fullpath) - - if module_name.endswith(".disabled"): - node_id = module_name[:-9] - disabled = True - elif parent_folder_name == ".disabled": - # Nodes under custom_nodes/.disabled/* are disabled - node_id = module_name - disabled = True - else: - node_id = module_name - disabled = False - - info = resolve_from_path(fullpath) - if info is None: - version = 'unknown' - else: - node_id = info['id'] # robust module guessing - version = info['ver'] - - return InstalledNodePackage( - id=node_id, fullpath=fullpath, disabled=disabled, version=version - ) diff --git a/glob/security_check.py b/glob/security_check.py deleted file mode 100644 index f5cdbcb0..00000000 --- a/glob/security_check.py +++ /dev/null @@ -1,170 +0,0 @@ -import sys -import subprocess -import os - -import manager_util - - -def security_check(): - print("[START] Security scan") - - custom_nodes_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) - comfyui_path = os.path.abspath(os.path.join(custom_nodes_path, '..')) - - guide = { - "ComfyUI_LLMVISION": """ -0.Remove ComfyUI\\custom_nodes\\ComfyUI_LLMVISION. -1.Remove pip packages: openai-1.16.3.dist-info, anthropic-0.21.4.dist-info, openai-1.30.2.dist-info, anthropic-0.21.5.dist-info, anthropic-0.26.1.dist-info, %LocalAppData%\\rundll64.exe - (For portable versions, it is recommended to reinstall. If you are using a venv, it is advised to recreate the venv.) -2.Remove these files in your system: lib/browser/admin.py, Cadmino.py, Fadmino.py, VISION-D.exe, BeamNG.UI.exe -3.Check your Windows registry for the key listed above and remove it. - (HKEY_CURRENT_USER\\Software\\OpenAICLI) -4.Run a malware scanner. -5.Change all of your passwords, everywhere. - -(Reinstall OS is recommended.) -\n -Detailed information: https://old.reddit.com/r/comfyui/comments/1dbls5n/psa_if_youve_used_the_comfyui_llmvision_node_from/ - """, - "lolMiner": """ -1. Remove pip packages: lolMiner* -2. Remove files: lolMiner*, 4G_Ethash_Linux_Readme.txt, mine* in ComfyUI dir. - -(Reinstall ComfyUI is recommended.) - """, - "ultralytics==8.3.41": f""" -Execute following commands: - {sys.executable} -m pip uninstall ultralytics - {sys.executable} -m pip install ultralytics==8.3.40 - -And kill and remove /tmp/ultralytics_runner - - -The version 8.3.41 to 8.3.42 of the Ultralytics package you installed is compromised. Please uninstall that version and reinstall the latest version. -https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situation/ - """, - "ultralytics==8.3.42": f""" -Execute following commands: - {sys.executable} -m pip uninstall ultralytics - {sys.executable} -m pip install ultralytics==8.3.40 - -And kill and remove /tmp/ultralytics_runner - - -The version 8.3.41 to 8.3.42 of the Ultralytics package you installed is compromised. Please uninstall that version and reinstall the latest version. -https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situation/ - """, - "litellm==1.82.7": f""" -Execute following commands: - {sys.executable} -m pip uninstall litellm - -The litellm PyPI package versions 1.82.7 and 1.82.8 were compromised via a supply chain attack. -Malicious code harvests SSH keys, environment variables, API keys, cloud credentials, and exfiltrates them to an attacker-controlled server. -Version 1.82.8 also installs a .pth file that executes malware on ANY Python startup, even without importing litellm. - -1. Uninstall litellm immediately. -2. Assume all credentials accessible to the litellm environment are compromised. -3. Rotate all API keys, cloud credentials, SSH keys, and database passwords. -4. Check site-packages for unexpected .pth files (e.g. litellm_init.pth) and remove them. -5. Run a full malware scan. - -Details: https://github.com/BerriAI/litellm/issues/24518 -Advisory: PYSEC-2026-2 - """, - "litellm==1.82.8": f""" -Execute following commands: - {sys.executable} -m pip uninstall litellm - -The litellm PyPI package versions 1.82.7 and 1.82.8 were compromised via a supply chain attack. -Malicious code harvests SSH keys, environment variables, API keys, cloud credentials, and exfiltrates them to an attacker-controlled server. -Version 1.82.8 also installs a .pth file that executes malware on ANY Python startup, even without importing litellm. - -1. Uninstall litellm immediately. -2. Assume all credentials accessible to the litellm environment are compromised. -3. Rotate all API keys, cloud credentials, SSH keys, and database passwords. -4. Check site-packages for unexpected .pth files (e.g. litellm_init.pth) and remove them. -5. Run a full malware scan. - -Details: https://github.com/BerriAI/litellm/issues/24518 -Advisory: PYSEC-2026-2 - """ - } - - node_blacklist = {"ComfyUI_LLMVISION": "ComfyUI_LLMVISION"} - - pip_blacklist = { - "AppleBotzz": "ComfyUI_LLMVISION", - "ultralytics==8.3.41": "ultralytics==8.3.41", - "ultralytics==8.3.42": "ultralytics==8.3.42", - "litellm==1.82.7": "litellm==1.82.7", - "litellm==1.82.8": "litellm==1.82.8", - } - - file_blacklist = { - "ComfyUI_LLMVISION": ["%LocalAppData%\\rundll64.exe"], - "lolMiner": [os.path.join(comfyui_path, 'lolMiner')] - } - - installed_pips = subprocess.check_output(manager_util.make_pip_cmd(["freeze"]), text=True) - - detected = set() - try: - anthropic_info = subprocess.check_output(manager_util.make_pip_cmd(["show", "anthropic"]), text=True, stderr=subprocess.DEVNULL) - requires_lines = [x for x in anthropic_info.split('\n') if x.startswith("Requires")] - if requires_lines: - anthropic_reqs = requires_lines[0].split(": ", 1)[1] - if "pycrypto" in anthropic_reqs: - location_lines = [x for x in anthropic_info.split('\n') if x.startswith("Location")] - if location_lines: - location = location_lines[0].split(": ", 1)[1] - for fi in os.listdir(location): - if fi.startswith("anthropic"): - guide["ComfyUI_LLMVISION"] = (f"\n0.Remove {os.path.join(location, fi)}" + guide["ComfyUI_LLMVISION"]) - detected.add("ComfyUI_LLMVISION") - - except subprocess.CalledProcessError: - pass - - for k, v in node_blacklist.items(): - if os.path.exists(os.path.join(custom_nodes_path, k)): - print(f"[SECURITY ALERT] custom node '{k}' is dangerous.") - detected.add(v) - - installed_pip_set = set(installed_pips.strip().split('\n')) - - for k, v in pip_blacklist.items(): - if '==' in k: - if k in installed_pip_set: - detected.add(v) - else: - if any(line.split('==')[0] == k for line in installed_pip_set): - detected.add(v) - - for k, v in file_blacklist.items(): - for x in v: - if os.path.exists(os.path.expandvars(x)): - detected.add(k) - break - - if len(detected) > 0: - for line in installed_pip_set: - for k, v in pip_blacklist.items(): - if '==' in k: - if line == k: - print(f"[SECURITY ALERT] '{line}' is dangerous.") - else: - if line.split('==')[0] == k: - print(f"[SECURITY ALERT] '{line}' is dangerous.") - - print("\n########################################################################") - print(" Malware has been detected, forcibly terminating ComfyUI execution.") - print("########################################################################\n") - - for x in detected: - print(f"\n======== TARGET: {x} =========") - print("\nTODO:") - print(guide.get(x)) - - exit(-1) - - print("[DONE] Security scan") diff --git a/glob/share_3rdparty.py b/glob/share_3rdparty.py deleted file mode 100644 index 837176fb..00000000 --- a/glob/share_3rdparty.py +++ /dev/null @@ -1,433 +0,0 @@ -import mimetypes -import manager_core as core -import os -from aiohttp import web -import aiohttp -import json -import hashlib - -import folder_paths -from server import PromptServer - - -def extract_model_file_names(json_data): - """Extract unique file names from the input JSON data.""" - file_names = set() - model_filename_extensions = {'.safetensors', '.ckpt', '.pt', '.pth', '.bin'} - - # Recursively search for file names in the JSON data - def recursive_search(data): - if isinstance(data, dict): - for value in data.values(): - recursive_search(value) - elif isinstance(data, list): - for item in data: - recursive_search(item) - elif isinstance(data, str) and '.' in data: - file_names.add(os.path.basename(data)) # file_names.add(data) - - recursive_search(json_data) - return [f for f in list(file_names) if os.path.splitext(f)[1] in model_filename_extensions] - - -def find_file_paths(base_dir, file_names): - """Find the paths of the files in the base directory.""" - file_paths = {} - - for root, dirs, files in os.walk(base_dir): - # Exclude certain directories - dirs[:] = [d for d in dirs if d not in ['.git']] - - for file in files: - if file in file_names: - file_paths[file] = os.path.join(root, file) - return file_paths - - -def compute_sha256_checksum(filepath): - """Compute the SHA256 checksum of a file, in chunks""" - sha256 = hashlib.sha256() - with open(filepath, 'rb') as f: - for chunk in iter(lambda: f.read(4096), b''): - sha256.update(chunk) - return sha256.hexdigest() - - -@PromptServer.instance.routes.get("/manager/share_option") -async def share_option(request): - if "value" in request.rel_url.query: - core.get_config()['share_option'] = request.rel_url.query['value'] - core.write_config() - else: - return web.Response(text=core.get_config()['share_option'], status=200) - - return web.Response(status=200) - - -def get_openart_auth(): - if not os.path.exists(os.path.join(core.manager_files_path, ".openart_key")): - return None - try: - with open(os.path.join(core.manager_files_path, ".openart_key"), "r") as f: - openart_key = f.read().strip() - return openart_key if openart_key else None - except: - return None - - -def get_matrix_auth(): - if not os.path.exists(os.path.join(core.manager_files_path, "matrix_auth")): - return None - try: - with open(os.path.join(core.manager_files_path, "matrix_auth"), "r") as f: - matrix_auth = f.read() - homeserver, username, password = matrix_auth.strip().split("\n") - if not homeserver or not username or not password: - return None - return { - "homeserver": homeserver, - "username": username, - "password": password, - } - except: - return None - - -def get_comfyworkflows_auth(): - if not os.path.exists(os.path.join(core.manager_files_path, "comfyworkflows_sharekey")): - return None - try: - with open(os.path.join(core.manager_files_path, "comfyworkflows_sharekey"), "r") as f: - share_key = f.read() - if not share_key.strip(): - return None - return share_key - except: - return None - - -def get_youml_settings(): - if not os.path.exists(os.path.join(core.manager_files_path, ".youml")): - return None - try: - with open(os.path.join(core.manager_files_path, ".youml"), "r") as f: - youml_settings = f.read().strip() - return youml_settings if youml_settings else None - except: - return None - - -def set_youml_settings(settings): - with open(os.path.join(core.manager_files_path, ".youml"), "w") as f: - f.write(settings) - - -@PromptServer.instance.routes.get("/manager/get_openart_auth") -async def api_get_openart_auth(request): - # print("Getting stored Matrix credentials...") - openart_key = get_openart_auth() - if not openart_key: - return web.Response(status=404) - return web.json_response({"openart_key": openart_key}) - - -@PromptServer.instance.routes.post("/manager/set_openart_auth") -async def api_set_openart_auth(request): - json_data = await request.json() - openart_key = json_data['openart_key'] - with open(os.path.join(core.manager_files_path, ".openart_key"), "w") as f: - f.write(openart_key) - return web.Response(status=200) - - -@PromptServer.instance.routes.get("/manager/get_matrix_auth") -async def api_get_matrix_auth(request): - # print("Getting stored Matrix credentials...") - matrix_auth = get_matrix_auth() - if not matrix_auth: - return web.Response(status=404) - return web.json_response(matrix_auth) - - -@PromptServer.instance.routes.get("/manager/youml/settings") -async def api_get_youml_settings(request): - youml_settings = get_youml_settings() - if not youml_settings: - return web.Response(status=404) - return web.json_response(json.loads(youml_settings)) - - -@PromptServer.instance.routes.post("/manager/youml/settings") -async def api_set_youml_settings(request): - json_data = await request.json() - set_youml_settings(json.dumps(json_data)) - return web.Response(status=200) - - -@PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth") -async def api_get_comfyworkflows_auth(request): - # Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken' - # in the same directory as the ComfyUI base folder - # print("Getting stored Comfyworkflows.com auth...") - comfyworkflows_auth = get_comfyworkflows_auth() - if not comfyworkflows_auth: - return web.Response(status=404) - return web.json_response({"comfyworkflows_sharekey": comfyworkflows_auth}) - - -@PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images") -async def set_esheep_workflow_and_images(request): - json_data = await request.json() - with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), "w", encoding='utf-8') as file: - json.dump(json_data, file, indent=4) - return web.Response(status=200) - - -@PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images") -async def get_esheep_workflow_and_images(request): - with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file: - data = json.load(file) - return web.Response(status=200, text=json.dumps(data)) - - -def set_matrix_auth(json_data): - homeserver = json_data['homeserver'] - username = json_data['username'] - password = json_data['password'] - with open(os.path.join(core.manager_files_path, "matrix_auth"), "w") as f: - f.write("\n".join([homeserver, username, password])) - - -def set_comfyworkflows_auth(comfyworkflows_sharekey): - with open(os.path.join(core.manager_files_path, "comfyworkflows_sharekey"), "w") as f: - f.write(comfyworkflows_sharekey) - - -def has_provided_matrix_auth(matrix_auth): - return matrix_auth['homeserver'].strip() and matrix_auth['username'].strip() and matrix_auth['password'].strip() - - -def has_provided_comfyworkflows_auth(comfyworkflows_sharekey): - return comfyworkflows_sharekey.strip() - - -@PromptServer.instance.routes.post("/manager/share") -async def share_art(request): - # get json data - json_data = await request.json() - - matrix_auth = json_data['matrix_auth'] - comfyworkflows_sharekey = json_data['cw_auth']['cw_sharekey'] - - set_matrix_auth(matrix_auth) - set_comfyworkflows_auth(comfyworkflows_sharekey) - - share_destinations = json_data['share_destinations'] - credits = json_data['credits'] - title = json_data['title'] - description = json_data['description'] - is_nsfw = json_data['is_nsfw'] - prompt = json_data['prompt'] - potential_outputs = json_data['potential_outputs'] - selected_output_index = json_data['selected_output_index'] - - try: - output_to_share = potential_outputs[int(selected_output_index)] - except: - # for now, pick the first output - output_to_share = potential_outputs[0] - - assert output_to_share['type'] in ('image', 'output') - output_dir = folder_paths.get_output_directory() - - if output_to_share['type'] == 'image': - asset_filename = output_to_share['image']['filename'] - asset_subfolder = output_to_share['image']['subfolder'] - - if output_to_share['image']['type'] == 'temp': - output_dir = folder_paths.get_temp_directory() - else: - asset_filename = output_to_share['output']['filename'] - asset_subfolder = output_to_share['output']['subfolder'] - - if asset_subfolder: - asset_filepath = os.path.join(output_dir, asset_subfolder, asset_filename) - else: - asset_filepath = os.path.join(output_dir, asset_filename) - - # get the mime type of the asset - assetFileType = mimetypes.guess_type(asset_filepath)[0] - - share_website_host = "UNKNOWN" - if "comfyworkflows" in share_destinations: - share_website_host = "https://comfyworkflows.com" - share_endpoint = f"{share_website_host}/api" - - # get presigned urls - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - async with session.post( - f"{share_endpoint}/get_presigned_urls", - json={ - "assetFileName": asset_filename, - "assetFileType": assetFileType, - "workflowJsonFileName": 'workflow.json', - "workflowJsonFileType": 'application/json', - }, - ) as resp: - assert resp.status == 200 - presigned_urls_json = await resp.json() - assetFilePresignedUrl = presigned_urls_json["assetFilePresignedUrl"] - assetFileKey = presigned_urls_json["assetFileKey"] - workflowJsonFilePresignedUrl = presigned_urls_json["workflowJsonFilePresignedUrl"] - workflowJsonFileKey = presigned_urls_json["workflowJsonFileKey"] - - # upload asset - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp: - assert resp.status == 200 - - # upload workflow json - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - async with session.put(workflowJsonFilePresignedUrl, data=json.dumps(prompt['workflow']).encode('utf-8')) as resp: - assert resp.status == 200 - - model_filenames = extract_model_file_names(prompt['workflow']) - model_file_paths = find_file_paths(folder_paths.base_path, model_filenames) - - models_info = {} - for filename, filepath in model_file_paths.items(): - models_info[filename] = { - "filename": filename, - "sha256_checksum": compute_sha256_checksum(filepath), - "relative_path": os.path.relpath(filepath, folder_paths.base_path), - } - - # make a POST request to /api/upload_workflow with form data key values - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - form = aiohttp.FormData() - if comfyworkflows_sharekey: - form.add_field("shareKey", comfyworkflows_sharekey) - form.add_field("source", "comfyui_manager") - form.add_field("assetFileKey", assetFileKey) - form.add_field("assetFileType", assetFileType) - form.add_field("workflowJsonFileKey", workflowJsonFileKey) - form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow'])) - form.add_field("sharedWorkflowPromptJsonString", json.dumps(prompt['output'])) - form.add_field("shareWorkflowCredits", credits) - form.add_field("shareWorkflowTitle", title) - form.add_field("shareWorkflowDescription", description) - form.add_field("shareWorkflowIsNSFW", str(is_nsfw).lower()) - form.add_field("currentSnapshot", json.dumps(await core.get_current_snapshot())) - form.add_field("modelsInfo", json.dumps(models_info)) - - async with session.post( - f"{share_endpoint}/upload_workflow", - data=form, - ) as resp: - assert resp.status == 200 - upload_workflow_json = await resp.json() - workflowId = upload_workflow_json["workflowId"] - - # check if the user has provided Matrix credentials - if "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') - homeserver = homeserver.replace("http://", "https://") - if not homeserver.startswith("https://"): - homeserver = "https://" + homeserver - - 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) - - # Upload asset - with open(asset_filepath, 'rb') as f: - 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 - - # 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" - if description: - text_content += f"{description}\n" - if credits: - text_content += f"\ncredits: {credits}\n" - 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({ - "comfyworkflows": { - "url": None if "comfyworkflows" not in share_destinations else f"{share_website_host}/workflows/{workflowId}", - }, - "matrix": { - "success": None if "matrix" not in share_destinations else True - } - }, content_type='application/json', status=200) diff --git a/js/README.md b/js/README.md deleted file mode 100644 index 5ceb21ba..00000000 --- a/js/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# ComfyUI-Manager: Frontend (js) - -This directory contains the JavaScript frontend implementation for ComfyUI-Manager, providing the user interface components that interact with the backend API. - -## Core Components - -- **comfyui-manager.js**: Main entry point that initializes the manager UI and integrates with ComfyUI. -- **custom-nodes-manager.js**: Implements the UI for browsing, installing, and managing custom nodes. -- **model-manager.js**: Handles the model management interface for downloading and organizing AI models. -- **components-manager.js**: Manages reusable workflow components system. -- **snapshot.js**: Implements the snapshot system for backing up and restoring installations. - -## Sharing Components - -- **comfyui-share-common.js**: Base functionality for workflow sharing features. -- **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. - -## Utility Components - -- **cm-api.js**: Client-side API wrapper for communication with the backend. -- **common.js**: Shared utilities and helper functions used across the frontend. -- **node_fixer.js**: Utilities for fixing disconnected links and repairing malformed nodes by recreating them while preserving connections. -- **popover-helper.js**: UI component for popup tooltips and contextual information. -- **turbogrid.esm.js**: Grid component library - https://github.com/cenfun/turbogrid -- **workflow-metadata.js**: Handles workflow metadata parsing, validation and cross-repository compatibility including versioning, dependencies tracking, and resource management. - -## Architecture - -The frontend follows a modular component-based architecture: - -1. **Integration Layer**: Connects with ComfyUI's existing UI system -2. **Manager Components**: Individual functional UI components (node manager, model manager, etc.) -3. **Sharing Components**: Platform-specific sharing implementations -4. **Utility Layer**: Reusable UI components and helpers - -## Implementation Details - -- The frontend integrates directly with ComfyUI's UI system through `app.js` -- Dialog-based UI for most manager functions to avoid cluttering the main interface -- Asynchronous API calls to handle backend operations without blocking the UI - -## Styling - -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. diff --git a/js/cm-api.js b/js/cm-api.js deleted file mode 100644 index c4d6da03..00000000 --- a/js/cm-api.js +++ /dev/null @@ -1,67 +0,0 @@ -import { api } from "../../scripts/api.js"; -import { app } from "../../scripts/app.js"; -import { sleep, customConfirm, customAlert, handle403Response, show_message } from "./common.js"; - -async function tryInstallCustomNode(event) { - let msg = '-= [ComfyUI Manager] extension installation request =-\n\n'; - msg += `The '${event.detail.sender}' extension requires the installation of the '${event.detail.target.title}' extension. `; - - if(event.detail.target.installed == 'Disabled') { - msg += 'However, the extension is currently disabled. Would you like to enable it and reboot?' - } - else if(event.detail.target.installed == 'True') { - msg += 'However, it seems that the extension is in an import-fail state or is not compatible with the current version. Please address this issue.'; - } - else { - msg += `Would you like to install it and reboot?`; - } - - msg += `\n\nRequest message:\n${event.detail.msg}`; - - if(event.detail.target.installed == 'True') { - customAlert(msg); - return; - } - const res = await customConfirm(msg); - if(res) { - if(event.detail.target.installed == 'Disabled') { - const response = await api.fetchApi(`/customnode/toggle_active`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(event.detail.target) - }); - } - else { - await sleep(300); - app.ui.dialog.show(`Installing... '${event.detail.target.title}'`); - - const response = await api.fetchApi(`/customnode/install`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(event.detail.target) - }); - - if(response.status == 403) { - await handle403Response(response); - return false; - } - else if(response.status == 400) { - let msg = await res.text(); - show_message(msg); - return false; - } - } - - let response = await api.fetchApi("/manager/reboot"); - if(response.status == 403) { - await handle403Response(response); - return false; - } - - await sleep(300); - - app.ui.dialog.show(`Rebooting...`); - } -} - -api.addEventListener("cm-api-try-install-customnode", tryInstallCustomNode); diff --git a/js/comfyui-gui-builder.js b/js/comfyui-gui-builder.js deleted file mode 100644 index e6de81c6..00000000 --- a/js/comfyui-gui-builder.js +++ /dev/null @@ -1,227 +0,0 @@ -import { $el } from "../../scripts/ui.js"; - -function normalizeContent(content) { - const tmp = document.createElement('div'); - if (typeof content === 'string') { - tmp.innerHTML = content; - return Array.from(tmp.childNodes); - } - if (content instanceof Node) { - return content; - } - return content; -} - -export function createSettingsCombo(label, content) { - const settingItem = $el("div.setting-item", {}, [ - $el("div.flex.flex-row.items-center.gap-2",[ - $el("div.form-label.flex.grow.items-center", [ - $el("span.text-muted", { textContent: label },) - ]), - $el("div.form-input.flex.justify-end", - [content] - ) - ] - ) - ]); - return settingItem; -} - -export function buildGuiFrame(dialogId, title, iconClass, content, owner) { - const dialog_mask = $el("div.p-dialog-mask.p-overlay-mask.p-overlay-mask-enter", { - parent: document.body, - style: { - position: "fixed", - height: "100%", - width: "100%", - left: "0px", - top: "0px", - display: "flex", - justifyContent: "center", - alignItems: "center", - pointerEvents: "auto", - zIndex: "1000" - }, - onclick: (e) => { - if (e.target === dialog_mask) { - owner.close(); - } - } - // data-pc-section="mask" - }); - - const header_actions = $el("div.p-dialog-header-actions", { - // [TODO] - // data-pc-section="headeractions" - } - ); - - const close_button = $el("button.p-button.p-component.p-button-icon-only.p-button-secondary.p-button-rounded.p-button-text.p-dialog-close-button", { - parent: header_actions, - type: "button", - ariaLabel: "Close", - onclick: () => owner.close(), - // "data-pc-name": "pcclosebutton", - // "data-p-disabled": "false", - // "data-p-severity": "secondary", - // "data-pc-group-section": "headericon", - // "data-pc-extend": "button", - // "data-pc-section": "root", - // [FIXME] Not sure how to do most of the SVG using $el - innerHTML: ' ' - } - ); - - const dialog_header = $el("div.p-dialog-header", - [ - $el("div", [ - $el("div", - { - id: "frame-title-container", - }, - [ - $el("h2.px-4", [ - $el(iconClass, { - style: { - "font-size": "1.25rem", - "margin-right": ".5rem" - } - }), - $el("span", { textContent: title }) - ]) - ] - ) - ]), - header_actions - ] - ); - - const contentFrame = $el("div.p-dialog-content", {}, normalizeContent(content)); - const manager_dialog = $el("div.p-dialog.p-component.global-dialog", { - id: dialogId, - parent: dialog_mask, - style: { - 'display': 'flex', - 'flex-direction': 'column', - 'pointer-events': 'auto', - 'margin': '0px', - }, - role: 'dialog', - ariaModal: 'true', - // [TODO] - // ariaLabbelledby: 'cm-title', - // maximized: 'false', - // data-pc-name: 'dialog', - // data-pc-section: 'root', - // data-pd-focustrap: 'true' - }, - [ dialog_header, contentFrame ] - ); - - const hidden_accessible = $el("span.p-hidden-accessible.p-hidden-focusable", { - parent: manager_dialog, - tabindex: "0", - role: "presentation", - ariaHidden: "true", - "data-p-hidden-accessible": "true", - "data-p-hidden-focusable": "true", - "data-pc-section": "firstfocusableelement" - }); - - return dialog_mask; -} - -export function buildGuiFrameCustomHeader(dialogId, customHeader, content, owner) { - const dialog_mask = $el("div.p-dialog-mask.p-overlay-mask.p-overlay-mask-enter", { - parent: document.body, - style: { - position: "fixed", - height: "100%", - width: "100%", - left: "0px", - top: "0px", - display: "flex", - justifyContent: "center", - alignItems: "center", - pointerEvents: "auto", - zIndex: "1000" - }, - onclick: (e) => { - if (e.target === dialog_mask) { - owner.close(); - } - } - // data-pc-section="mask" - }); - - const header_actions = $el("div.p-dialog-header-actions", { - // [TODO] - // data-pc-section="headeractions" - } - ); - - const close_button = $el("button.p-button.p-component.p-button-icon-only.p-button-secondary.p-button-rounded.p-button-text.p-dialog-close-button", { - parent: header_actions, - type: "button", - ariaLabel: "Close", - onclick: () => owner.close(), - // "data-pc-name": "pcclosebutton", - // "data-p-disabled": "false", - // "data-p-severity": "secondary", - // "data-pc-group-section": "headericon", - // "data-pc-extend": "button", - // "data-pc-section": "root", - // [FIXME] Not sure how to do most of the SVG using $el - innerHTML: ' ' - } - ); - - const _customHeader = normalizeContent(customHeader); - const dialog_header = $el("div.p-dialog-header", - [ - $el("div", [ - $el("div", - { - id: "frame-title-container", - }, - Array.isArray(_customHeader) ? _customHeader : [_customHeader] - ) - ]), - header_actions - ] - ); - - const contentFrame = $el("div.p-dialog-content", {}, normalizeContent(content)); - const manager_dialog = $el("div.p-dialog.p-component.global-dialog", { - id: dialogId, - parent: dialog_mask, - style: { - 'display': 'flex', - 'flex-direction': 'column', - 'pointer-events': 'auto', - 'margin': '0px', - }, - role: 'dialog', - ariaModal: 'true', - // [TODO] - // ariaLabbelledby: 'cm-title', - // maximized: 'false', - // data-pc-name: 'dialog', - // data-pc-section: 'root', - // data-pd-focustrap: 'true' - }, - [ dialog_header, contentFrame ] - ); - - const hidden_accessible = $el("span.p-hidden-accessible.p-hidden-focusable", { - parent: manager_dialog, - tabindex: "0", - role: "presentation", - ariaHidden: "true", - "data-p-hidden-accessible": "true", - "data-p-hidden-focusable": "true", - "data-pc-section": "firstfocusableelement" - }); - - return dialog_mask; -} \ No newline at end of file diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js deleted file mode 100644 index bcf7e9e5..00000000 --- a/js/comfyui-manager.js +++ /dev/null @@ -1,1738 +0,0 @@ -import { api } from "../../scripts/api.js"; -import { app } from "../../scripts/app.js"; -import { $el, ComfyDialog } from "../../scripts/ui.js"; -import { - SUPPORTED_OUTPUT_NODE_TYPES, - ShareDialog, - ShareDialogChooser, - getPotentialOutputsAndOutputNodes, - showOpenArtShareDialog, - showShareDialog, - showYouMLShareDialog -} from "./comfyui-share-common.js"; -import { OpenArtShareDialog } from "./comfyui-share-openart.js"; -import { - free_models, install_pip, install_via_git_url, manager_instance, - rebootAPI, setManagerInstance, show_message, customAlert, customPrompt, - infoToast, showTerminal, setNeedRestart, handle403Response -} from "./common.js"; -import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js"; -import { CustomNodesManager } from "./custom-nodes-manager.js"; -import { ModelManager } from "./model-manager.js"; -import { SnapshotManager } from "./snapshot.js"; -import { buildGuiFrame, createSettingsCombo } from "./comfyui-gui-builder.js"; - -let manager_version = await getVersion(); - -var docStyle = document.createElement('style'); -docStyle.innerHTML = ` -.comfy-toast { - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - background-color: rgba(0, 0, 0, 0.7); - color: white; - padding: 10px 20px; - border-radius: 5px; - z-index: 1000; - transition: opacity 0.5s; -} - -.comfy-toast-fadeout { - opacity: 0; -} - -#cm-manager-dialog { - width: 1000px; - height: auto; - box-sizing: content-box; - z-index: 1000; - overflow-y: auto; -} - -#cm-manager-dialog br { - margin-bottom: 1em; -} - -.cb-widget { - width: 400px; - height: 25px; - box-sizing: border-box; - z-index: 1000; - margin-top: 10px; - margin-bottom: 5px; -} - -.cb-widget-input { - width: 305px; - height: 25px; - box-sizing: border-box; -} -.cb-widget-input:disabled { - background-color: #444444; - color: white; -} - -.cb-widget-input-label { - width: 90px; - height: 25px; - box-sizing: border-box; - color: white; - text-align: right; - display: inline-block; - margin-right: 5px; -} - -.cm-menu-container { - padding : calc(var(--spacing)*2); - column-gap: 20px; - display: flex; - flex-wrap: wrap; - justify-content: center; - box-sizing: content-box; -} - -.cm-menu-column { - display: flex; - flex-direction: column; - flex: 1 1 auto; - width: 300px; - box-sizing: content-box; -} - -.cm-title { - background-color: black; - text-align: center; - height: 40px; - width: calc(100% - 10px); - font-weight: bold; - justify-content: center; - align-content: center; - vertical-align: middle; -} - -#custom-nodes-grid a { - color: #5555FF; - font-weight: bold; - text-decoration: none; -} - -#custom-nodes-grid a:hover { - color: #7777FF; - text-decoration: underline; -} - -#external-models-grid a { - color: #5555FF; - font-weight: bold; - text-decoration: none; -} - -#external-models-grid a:hover { - color: #7777FF; - text-decoration: underline; -} - -#alternatives-grid a { - color: #5555FF; - font-weight: bold; - text-decoration: none; -} - -#alternatives-grid a:hover { - color: #7777FF; - text-decoration: underline; -} - -.cm-notice-board { - width: auto; - height: 280px; - overflow: auto; - color: var(--input-text); - border: 1px solid var(--descrip-text); - padding: 5px 10px; - overflow-x: hidden; - box-sizing: content-box; -} - -.cm-notice-board > ul { - display: block; - list-style-type: disc; - margin-block-start: 1em; - margin-block-end: 1em; - margin-inline-start: 0px; - margin-inline-end: 0px; - padding-inline-start: 40px; -} - -.cm-conflicted-nodes-text { - background-color: #CCCC55 !important; - color: #AA3333 !important; - font-size: 10px; - border-radius: 5px; - padding: 10px; -} - -.cm-warn-note { - background-color: #101010 !important; - color: #FF3800 !important; - font-size: 13px; - border-radius: 5px; - padding: 10px; - overflow-x: hidden; - overflow: auto; -} - -.cm-info-note { - background-color: #101010 !important; - color: #FF3800 !important; - font-size: 13px; - border-radius: 5px; - padding: 10px; - overflow-x: hidden; - overflow: auto; -} -`; - -function is_legacy_front() { - let compareVersion = '1.2.49'; - try { - const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']; - if (typeof frontendVersion !== 'string') { - return false; - } - - function parseVersion(versionString) { - const parts = versionString.split('.').map(Number); - return parts.length === 3 && parts.every(part => !isNaN(part)) ? parts : null; - } - - const currentVersion = parseVersion(frontendVersion); - const comparisonVersion = parseVersion(compareVersion); - - if (!currentVersion || !comparisonVersion) { - return false; - } - - for (let i = 0; i < 3; i++) { - if (currentVersion[i] > comparisonVersion[i]) { - return false; - } else if (currentVersion[i] < comparisonVersion[i]) { - return true; - } - } - - return false; - } catch { - return true; - } -} - -document.head.appendChild(docStyle); - -var update_comfyui_button = null; -var switch_comfyui_button = null; -var update_all_button = null; -var restart_stop_button = null; -var update_policy_combo = null; - -let share_option = 'all'; -var is_updating = false; - - -// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts -const style = ` -#workflowgallery-button { - height: 50px; - padding: 0px !important; -} -#cm-nodeinfo-button { - -} -#cm-manual-button { - -} - -.cm-button { - width: auto; - position: relative; - overflow: hidden; - background-color: var(--comfy-menu-secondary-bg); - border-color: var(--border-color); - color: var(--input-text); -} - -.cm-button:hover { - filter: brightness(125%); -} - -.cm-button-red { - background-color: #500000 !important; - border-color: #88181b !important; - color: white !important; -} - -.cm-button-red:hover { - background-color: #88181b !important; -} - -.cm-button-orange { - font-weight: bold; - background-color: orange !important; - color: black !important; -} - -.cm-experimental-button { - width: 100%; -} - -.cm-experimental { - border: 1px solid #555; - border-radius: 5px; - padding: 10px; - align-items: center; - text-align: center; - justify-content: center; - box-sizing: border-box; -} - -.cm-experimental-legend { - margin-top: -20px; - margin-left: 50%; - width:auto; - height:20px; - font-size: 13px; - font-weight: bold; - background-color: #990000; - color: #CCFFFF; - border-radius: 5px; - text-align: center; - transform: translateX(-50%); - display: block; -} - -.cm-menu-combo { - cursor: pointer; - padding: 0.5em 0.5em; - border: 1px solid var(--border-color); - border-radius: 6px; - background: var(--comfy-menu-secondary-bg); -} - -.cm-menu-combo:hover { - filter: brightness(125%); -} - -.cm-small-button { - width: 120px; - height: 30px; - position: relative; - overflow: hidden; - box-sizing: border-box; - font-size: 17px !important; -} - -#cm-install-customnodes-button { - width: 200px; - height: 30px; - position: relative; - overflow: hidden; - box-sizing: border-box; - font-size: 17px !important; -} - -.cm-search-filter { - width: 200px; - height: 30px !important; - position: relative; - overflow: hidden; - box-sizing: border-box; -} - -.cb-node-label { - width: 400px; - height:28px; - color: black; - background-color: #777777; - font-size: 18px; - text-align: center; - font-weight: bold; -} - -#cm-close-button { - width: calc(100% - 65px); - bottom: 10px; - position: absolute; - overflow: hidden; -} - -#cm-save-button { - width: calc(100% - 65px); - bottom:40px; - position: absolute; - overflow: hidden; -} -#cm-save-button:disabled { - background-color: #444444; -} - -.pysssss-workflow-arrow-2 { - position: absolute; - top: 0; - bottom: 0; - right: 0; - font-size: 12px; - display: flex; - align-items: center; - width: 24px; - justify-content: center; - background: rgba(255,255,255,0.1); - content: "▼"; -} -.pysssss-workflow-arrow-2:after { - content: "▼"; - } - .pysssss-workflow-arrow-2:hover { - filter: brightness(1.6); - background-color: var(--comfy-menu-bg); - } -.pysssss-workflow-popup-2 ~ .litecontextmenu { - transform: scale(1.3); -} -#workflowgallery-button-menu { - z-index: 10000000000 !important; -} -#cm-manual-button-menu { - z-index: 10000000000 !important; -} -`; - -async function init_share_option() { - api.fetchApi('/manager/share_option') - .then(response => response.text()) - .then(data => { - share_option = data || 'all'; - }); -} - -async function init_notice(notice) { - api.fetchApi('/manager/notice') - .then(response => response.text()) - .then(data => { - notice.innerHTML = data; - }) -} - -await init_share_option(); - - -async function set_inprogress_mode() { - update_comfyui_button.disabled = true; - update_comfyui_button.style.backgroundColor = "gray"; - - update_all_button.disabled = true; - update_all_button.style.backgroundColor = "gray"; - - switch_comfyui_button.disabled = true; - switch_comfyui_button.style.backgroundColor = "gray"; - - restart_stop_button.innerText = 'Stop'; -} - - -async function reset_action_buttons() { - const isElectron = 'electronAPI' in window; - - if(isElectron) { - update_all_button.innerText = "Update All Custom Nodes"; - } - else { - update_all_button.innerText = "Update All"; - } - - update_comfyui_button.innerText = "Update ComfyUI"; - switch_comfyui_button.innerText = "Switch ComfyUI"; - restart_stop_button.innerText = 'Restart'; - - update_comfyui_button.disabled = false; - update_all_button.disabled = false; - switch_comfyui_button.disabled = false; - - update_comfyui_button.style.backgroundColor = ""; - update_all_button.style.backgroundColor = ""; - switch_comfyui_button.style.backgroundColor = ""; -} - -async function updateComfyUI() { - let prev_text = update_comfyui_button.innerText; - update_comfyui_button.innerText = "Updating ComfyUI..."; - - set_inprogress_mode(); - - const response = await api.fetchApi('/manager/queue/update_comfyui'); - - showTerminal(); - - is_updating = true; - await api.fetchApi('/manager/queue/start'); -} - -function showVersionSelectorDialog(versions, current, onSelect) { - const dialog = new ComfyDialog(); - dialog.element.style.zIndex = 1100; - dialog.element.style.width = "300px"; - dialog.element.style.padding = "0"; - dialog.element.style.backgroundColor = "#2a2a2a"; - dialog.element.style.border = "1px solid #3a3a3a"; - dialog.element.style.borderRadius = "8px"; - dialog.element.style.boxSizing = "border-box"; - dialog.element.style.overflow = "hidden"; - - const contentStyle = { - width: "300px", - display: "flex", - flexDirection: "column", - alignItems: "center", - padding: "20px", - boxSizing: "border-box", - gap: "15px" - }; - - let selectedVersion = versions[0]; - - const versionList = $el("select", { - multiple: true, - size: Math.min(10, versions.length), - style: { - width: "260px", - height: "auto", - backgroundColor: "#383838", - color: "#ffffff", - border: "1px solid #4a4a4a", - borderRadius: "4px", - padding: "5px", - boxSizing: "border-box" - } - }, - versions.map((v, index) => $el("option", { - value: v, - textContent: v, - selected: v === current - })) - ); - - versionList.addEventListener('change', (e) => { - selectedVersion = e.target.value; - Array.from(e.target.options).forEach(opt => { - opt.selected = opt.value === selectedVersion; - }); - }); - - const content = $el("div", { - style: contentStyle - }, [ - $el("h3", { - textContent: "Select Version", - style: { - color: "#ffffff", - backgroundColor: "#1a1a1a", - padding: "10px 15px", - margin: "0 0 10px 0", - width: "260px", - textAlign: "center", - borderRadius: "4px", - boxSizing: "border-box", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis" - } - }), - versionList, - $el("div", { - style: { - display: "flex", - justifyContent: "space-between", - width: "260px", - gap: "10px" - } - }, [ - $el("button", { - textContent: "Cancel", - onclick: () => dialog.close(), - style: { - flex: "1", - padding: "8px", - backgroundColor: "#4a4a4a", - color: "#ffffff", - border: "none", - borderRadius: "4px", - cursor: "pointer", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis" - } - }), - $el("button", { - textContent: "Select", - onclick: () => { - if (selectedVersion) { - onSelect(selectedVersion); - dialog.close(); - } else { - customAlert("Please select a version."); - } - }, - style: { - flex: "1", - padding: "8px", - backgroundColor: "#4CAF50", - color: "#ffffff", - border: "none", - borderRadius: "4px", - cursor: "pointer", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis" - } - }), - ]) - ]); - - dialog.show(content); -} - -async function switchComfyUI() { - switch_comfyui_button.disabled = true; - switch_comfyui_button.style.backgroundColor = "gray"; - - let res = await api.fetchApi(`/comfyui_manager/comfyui_versions`, { cache: "no-store" }); - - switch_comfyui_button.disabled = false; - switch_comfyui_button.style.backgroundColor = ""; - - if(res.status == 200) { - let obj = await res.json(); - - let versions = []; - let default_version; - - for(let v of obj.versions) { - default_version = v; - versions.push(v); - } - - showVersionSelectorDialog(versions, obj.current, async (selected_version) => { - if(selected_version == 'nightly') { - update_policy_combo.value = 'nightly-comfyui'; - api.fetchApi('/manager/policy/update?value=nightly-comfyui'); - } - else { - update_policy_combo.value = 'stable-comfyui'; - api.fetchApi('/manager/policy/update?value=stable-comfyui'); - } - - let response = await api.fetchApi(`/comfyui_manager/comfyui_switch_version?ver=${selected_version}`, { cache: "no-store" }); - if (response.status == 200) { - infoToast(`ComfyUI version is switched to ${selected_version}`); - } - else { - customAlert('Failed to switch ComfyUI version.'); - } - }); - } - else { - customAlert('Failed to fetch ComfyUI versions.'); - } -} - -async function onQueueStatus(event) { - const isElectron = 'electronAPI' in window; - - if(event.detail.status == 'in_progress') { - set_inprogress_mode(); - update_all_button.innerText = `in progress.. (${event.detail.done_count}/${event.detail.total_count})`; - } - else if(event.detail.status == 'done') { - reset_action_buttons(); - - if(!is_updating) { - return; - } - - is_updating = false; - - let success_list = []; - let failed_list = []; - let comfyui_state = null; - - for(let k in event.detail.nodepack_result){ - let v = event.detail.nodepack_result[k]; - - if(k == 'comfyui') { - comfyui_state = v; - continue; - } - - if(v.msg == 'success') { - success_list.push(k); - } - else if(v.msg != 'skip') - failed_list.push(k); - } - - let msg = ""; - - if(success_list.length == 0 && comfyui_state.startsWith('skip')) { - if(failed_list.length == 0) { - msg += "You are already up to date."; - } - } - else { - msg = "To apply the updates, you need to ComfyUI.
"; - - if(comfyui_state == 'success-nightly') { - msg += "ComfyUI has been updated to latest nightly version.

"; - infoToast("ComfyUI has been updated to the latest nightly version."); - } - else if(comfyui_state.startsWith('success-stable')) { - const ver = comfyui_state.split("-").pop(); - msg += `ComfyUI has been updated to ${ver}.

`; - infoToast(`ComfyUI has been updated to ${ver}`); - } - else if(comfyui_state == 'skip') { - msg += "ComfyUI is already up to date.

" - } - else if(comfyui_state != null) { - msg += "Failed to update ComfyUI.

" - } - - if(success_list.length > 0) { - msg += "The following custom nodes have been updated:
    "; - for(let x in success_list) { - let k = success_list[x]; - let url = event.detail.nodepack_result[k].url; - let title = event.detail.nodepack_result[k].title; - if(url) { - msg += `
  • ${title}
  • `; - } - else { - msg += `
  • ${k}
  • `; - } - } - msg += "
"; - } - - setNeedRestart(true); - } - - if(failed_list.length > 0) { - msg += '
The update for the following custom nodes has failed:
    '; - for(let x in failed_list) { - let k = failed_list[x]; - let url = event.detail.nodepack_result[k].url; - let title = event.detail.nodepack_result[k].title; - if(url) { - msg += `
  • ${title}
  • `; - } - else { - msg += `
  • ${k}
  • `; - } - } - - msg += '
' - } - - show_message(msg); - - const rebootButton = document.getElementById('cm-reboot-button5'); - rebootButton?.addEventListener("click", - async function() { - if(await rebootAPI()) { - manager_instance.close(); - } - }); - } -} - -api.addEventListener("cm-queue-status", onQueueStatus); - - -async function updateAll(update_comfyui) { - update_all_button.innerText = "Updating..."; - - set_inprogress_mode(); - - var mode = manager_instance.datasrc_combo.value; - - showTerminal(); - - if(update_comfyui) { - update_all_button.innerText = "Updating ComfyUI..."; - await api.fetchApi('/manager/queue/update_comfyui'); - } - - const response = await api.fetchApi(`/manager/queue/update_all?mode=${mode}`); - - if (response.status == 403) { - await handle403Response(response); - reset_action_buttons(); - } - else if (response.status == 401) { - customAlert('Another task is already in progress. Please stop the ongoing task first.'); - reset_action_buttons(); - } - else if(response.status == 200) { - is_updating = true; - await api.fetchApi('/manager/queue/start'); - } -} - -function newDOMTokenList(initialTokens) { - const tmp = document.createElement(`div`); - - const classList = tmp.classList; - if (initialTokens) { - initialTokens.forEach(token => { - classList.add(token); - }); - } - - return classList; - } - -/** - * Check whether the node is a potential output node (img, gif or video output) - */ -const isOutputNode = (node) => { - return SUPPORTED_OUTPUT_NODE_TYPES.includes(node.type); -} - -function restartOrStop() { - if(restart_stop_button.innerText == 'Restart'){ - rebootAPI(); - } - else { - api.fetchApi('/manager/queue/reset'); - infoToast('Cancel', 'Remaining tasks will stop after completing the current task.'); - } -} - -// ----------- -class ManagerMenuDialog extends ComfyDialog { - createControlsMid() { - let self = this; - const isElectron = 'electronAPI' in window; - - update_comfyui_button = - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Update ComfyUI", - style: { - display: isElectron ? 'none' : 'block' - }, - onclick: - () => updateComfyUI() - }); - - switch_comfyui_button = - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Switch ComfyUI", - style: { - display: isElectron ? 'none' : 'block' - }, - onclick: - () => switchComfyUI() - }); - - restart_stop_button = - $el("button.p-button.p-component.cm-button-red", { - type: "button", - textContent: "Restart", - onclick: () => restartOrStop() - }); - - if(isElectron) { - update_all_button = - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Update All Custom Nodes", - onclick: - () => updateAll(false) - }); - } - else { - update_all_button = - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Update All", - onclick: - () => updateAll(true) - }); - } - - const res = - [ - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Custom Nodes Manager", - onclick: - () => { - if(!CustomNodesManager.instance) { - CustomNodesManager.instance = new CustomNodesManager(app, self); - } - CustomNodesManager.instance.show(CustomNodesManager.ShowMode.NORMAL); - } - }), - - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Install Missing Custom Nodes", - onclick: - () => { - if(!CustomNodesManager.instance) { - CustomNodesManager.instance = new CustomNodesManager(app, self); - } - CustomNodesManager.instance.show(CustomNodesManager.ShowMode.MISSING); - } - }), - - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Custom Nodes In Workflow", - onclick: - () => { - if(!CustomNodesManager.instance) { - CustomNodesManager.instance = new CustomNodesManager(app, self); - } - CustomNodesManager.instance.show(CustomNodesManager.ShowMode.IN_WORKFLOW); - } - }), - - $el("div", {}, []), - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Model Manager", - onclick: - () => { - if(!ModelManager.instance) { - ModelManager.instance = new ModelManager(app, self); - } - ModelManager.instance.show(); - } - }), - - $el("button.p-button.p-component.cm-button", { - type: "button", - textContent: "Install via Git URL", - onclick: async () => { - var url = await customPrompt("Please enter the URL of the Git repository to install", ""); - - if (url !== null) { - install_via_git_url(url, self); - } - } - }), - - $el("div", {}, []), - update_all_button, - update_comfyui_button, - switch_comfyui_button, - // fetch_updates_button, - - $el("div", {}, []), - restart_stop_button, - ]; - - return res; - } - - createControlsLeft() { - const isElectron = 'electronAPI' in window; - - let self = this; - - // db mode - - this.datasrc_combo = document.createElement("select"); - this.datasrc_combo.setAttribute("title", "Configure where to retrieve node/model information. If set to 'local,' the channel is ignored, and if set to 'channel (remote),' it fetches the latest information each time the list is opened."); - this.datasrc_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled "; - this.datasrc_combo.appendChild($el('option', { value: 'cache', text: 'Channel (1day cache)' }, [])); - this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'Local' }, [])); - this.datasrc_combo.appendChild($el('option', { value: 'remote', text: 'Channel (remote)' }, [])); - - api.fetchApi('/manager/db_mode') - .then(response => response.text()) - .then(data => { this.datasrc_combo.value = data; }); - - this.datasrc_combo.addEventListener('change', function (event) { - api.fetchApi(`/manager/db_mode?value=${event.target.value}`); - }); - - const dbRetrievalSetttingItem = createSettingsCombo("DB", this.datasrc_combo); - - // preview method - let preview_combo = document.createElement("select"); - preview_combo.setAttribute("title", "Configure how latent variables will be decoded during preview in the sampling process."); - preview_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled"; - - // Loading state to prevent flash of enabled state - preview_combo.appendChild($el('option', { value: '', text: 'Loading...', disabled: true }, [])); - preview_combo.appendChild($el('option', { value: 'auto', text: 'Auto' }, [])); - preview_combo.appendChild($el('option', { value: 'taesd', text: 'TAESD (slow)' }, [])); - preview_combo.appendChild($el('option', { value: 'latent2rgb', text: 'Latent2RGB (fast)' }, [])); - preview_combo.appendChild($el('option', { value: 'none', text: 'None (very fast)' }, [])); - - // Start disabled to prevent flash - preview_combo.disabled = true; - preview_combo.value = ''; - - // Fetch current state - api.fetchApi('/manager/preview_method') - .then(response => response.text()) - .then(data => { - // Remove loading option - preview_combo.querySelector('option[value=""]')?.remove(); - - if (data === "DISABLED") { - // ComfyUI per-queue preview feature is active - preview_combo.disabled = true; - preview_combo.value = 'auto'; - - // Accessibility attributes - preview_combo.setAttribute("aria-disabled", "true"); - preview_combo.setAttribute("aria-label", - "Preview method setting (disabled - managed by ComfyUI). " + - "Use Settings > Execution > Live preview method instead." - ); - - // Tooltip for mouse users - preview_combo.setAttribute("title", - "This feature is now provided natively by ComfyUI. " + - "Please use 'Settings > Execution > Live preview method' instead." - ); - - // Visual feedback - preview_combo.style.opacity = '0.6'; - preview_combo.style.cursor = 'not-allowed'; - } else { - // Manager feature is active - preview_combo.disabled = false; - preview_combo.value = data; - - // Accessibility for enabled state - preview_combo.setAttribute("aria-label", - "Preview method setting. Select how latent variables are decoded during preview." - ); - } - }) - .catch(error => { - console.error('[ComfyUI-Manager] Failed to fetch preview method status:', error); - // Error recovery: fallback to enabled - preview_combo.querySelector('option[value=""]')?.remove(); - preview_combo.disabled = false; - preview_combo.value = 'auto'; - }); - - preview_combo.addEventListener('change', function (event) { - // Ignore if disabled - if (preview_combo.disabled) { - event.preventDefault(); - return; - } - - // Normal operation - api.fetchApi(`/manager/preview_method?value=${event.target.value}`) - .then(response => { - if (response.status === 403) { - // Feature transitioned to native - alert( - 'This feature is now provided natively by ComfyUI.\n' + - 'Please use \'Settings > Execution > Live preview method\' instead.' - ); - preview_combo.disabled = true; - preview_combo.style.opacity = '0.6'; - preview_combo.style.cursor = 'not-allowed'; - - // Update aria attributes - preview_combo.setAttribute("aria-disabled", "true"); - preview_combo.setAttribute("aria-label", - "Preview method setting (disabled - managed by ComfyUI). " + - "Use Settings > Execution > Live preview method instead." - ); - } - }) - .catch(error => { - console.error('[ComfyUI-Manager] Preview method update failed:', error); - }); - }); - - const previewSetttingItem = createSettingsCombo("Preview method", preview_combo); - - // channel - let channel_combo = document.createElement("select"); - channel_combo.setAttribute("title", "Configure the channel for retrieving data from the Custom Node list (including missing nodes) or the Model list."); - channel_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled"; - api.fetchApi('/manager/channel_url_list') - .then(response => response.json()) - .then(async data => { - try { - let urls = data.list; - for (let i in urls) { - if (urls[i] != '') { - let name_url = urls[i].split('::'); - channel_combo.appendChild($el('option', { value: name_url[0], text: `${name_url[0]}` }, [])); - } - } - - channel_combo.addEventListener('change', function (event) { - api.fetchApi(`/manager/channel_url_list?value=${event.target.value}`); - }); - - channel_combo.value = data.selected; - } - catch (exception) { - - } - }); - - const channelSetttingItem = createSettingsCombo("Channel", channel_combo); - - - // share - let share_combo = document.createElement("select"); - share_combo.setAttribute("title", "Hide the share button in the main menu or set the default action upon clicking it. Additionally, configure the default share site when sharing via the context menu's share button."); - share_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled"; - const share_options = [ - ['none', 'None'], - ['openart', 'OpenArt AI'], - ['youml', 'YouML'], - ['matrix', 'Matrix Server'], - ['comfyworkflows', 'ComfyWorkflows'], - ['copus', 'Copus'], - ['all', 'All'], - ]; - for (const option of share_options) { - share_combo.appendChild($el('option', { value: option[0], text: `${option[1]}` }, [])); - } - - api.fetchApi('/manager/share_option') - .then(response => response.text()) - .then(data => { - share_combo.value = data || 'all'; - share_option = data || 'all'; - }); - - share_combo.addEventListener('change', function (event) { - const value = event.target.value; - share_option = value; - api.fetchApi(`/manager/share_option?value=${value}`); - const shareButton = document.getElementById("shareButton"); - if (value === 'none') { - shareButton.style.display = "none"; - } else { - shareButton.style.display = "inline-block"; - } - }); - - const shareSetttingItem = createSettingsCombo("Share", share_combo); - - let component_policy_combo = document.createElement("select"); - component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use."); - component_policy_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled"; - component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Use workflow version' }, [])); - component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Use higher version' }, [])); - component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Use my version' }, [])); - api.fetchApi('/manager/policy/component') - .then(response => response.text()) - .then(data => { - component_policy_combo.value = data; - set_component_policy(data); - }); - - component_policy_combo.addEventListener('change', function (event) { - api.fetchApi(`/manager/policy/component?value=${event.target.value}`); - set_component_policy(event.target.value); - }); - - const componentSetttingItem = createSettingsCombo("Component", component_policy_combo); - - update_policy_combo = document.createElement("select"); - - update_policy_combo.setAttribute("title", "Sets the policy to be applied when performing an update."); - update_policy_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled"; - update_policy_combo.appendChild($el('option', { value: 'stable-comfyui', text: 'ComfyUI Stable Version' }, [])); - update_policy_combo.appendChild($el('option', { value: 'nightly-comfyui', text: 'ComfyUI Nightly Version' }, [])); - api.fetchApi('/manager/policy/update') - .then(response => response.text()) - .then(data => { - update_policy_combo.value = data; - }); - - update_policy_combo.addEventListener('change', function (event) { - api.fetchApi(`/manager/policy/update?value=${event.target.value}`); - }); - - const updateSetttingItem = createSettingsCombo("Update", update_policy_combo); - - if(isElectron) - updateSetttingItem.style.display = 'none'; - - return [ - dbRetrievalSetttingItem, - channelSetttingItem, - previewSetttingItem, - shareSetttingItem, - componentSetttingItem, - updateSetttingItem, - //[TODO] replace mt-2 with wrapper div with flex column gap - $el("filedset.cm-experimental.mt-auto", {}, [ - $el("legend.cm-experimental-legend", {}, ["EXPERIMENTAL"]), - $el("button.p-button.p-component.cm-button.cm-experimental-button", { - type: "button", - textContent: "Snapshot Manager", - onclick: - () => { - if(!SnapshotManager.instance) - SnapshotManager.instance = new SnapshotManager(app, self); - SnapshotManager.instance.show(); - } - }), - $el("button.p-button.p-component.cm-button.cm-experimental-button.mt-2", { - type: "button", - textContent: "Install PIP packages", - onclick: - async () => { - var url = await customPrompt("Please enumerate the pip packages to be installed.\n\nExample: insightface opencv-python-headless>=4.1.1\n", ""); - - if (url !== null) { - install_pip(url, self); - } - } - }) - ]), - ]; - } - - createControlsRight() { - const elts = [ - $el("button.p-button.p-component.cm-button", { - id: 'cm-manual-button', - type: "button", - textContent: "Community Manual", - onclick: () => { window.open("https://blenderneko.github.io/ComfyUI-docs/", "comfyui-community-manual"); } - }, [ - $el("div.pysssss-workflow-arrow-2", { - id: `cm-manual-button-arrow`, - onclick: (e) => { - e.preventDefault(); - e.stopPropagation(); - - LiteGraph.closeAllContextMenus(); - const menu = new LiteGraph.ContextMenu( - [ - { - title: "ComfyUI Docs", - callback: () => { window.open("https://docs.comfy.org/", "comfyui-official-manual"); }, - }, - { - title: "Comfy Custom Node How To", - callback: () => { window.open("https://github.com/chrisgoringe/Comfy-Custom-Node-How-To/wiki/aaa_index", "comfyui-community-manual1"); }, - }, - { - title: "ComfyUI Guide To Making Custom Nodes", - callback: () => { window.open("https://github.com/Suzie1/ComfyUI_Guide_To_Making_Custom_Nodes/wiki", "comfyui-community-manual2"); }, - }, - { - title: "ComfyUI Examples", - callback: () => { window.open("https://comfyanonymous.github.io/ComfyUI_examples", "comfyui-community-manual3"); }, - }, - { - title: "Close", - callback: () => { - LiteGraph.closeAllContextMenus(); - }, - } - ], - { - event: e, - scale: 1.3, - }, - window - ); - // set the id so that we can override the context menu's z-index to be above the comfyui manager menu - menu.root.id = "cm-manual-button-menu"; - menu.root.classList.add("pysssss-workflow-popup-2"); - }, - }) - ]), - - $el("button.p-button.p-component.cm-button", { - id: 'workflowgallery-button', - type: "button", - style: { - // ...(localStorage.getItem("wg_last_visited") ? {height: '50px'} : {}) - }, - onclick: (e) => { - const last_visited_site = localStorage.getItem("wg_last_visited") - if (!!last_visited_site) { - window.open(last_visited_site, last_visited_site); - } else { - this.handleWorkflowGalleryButtonClick(e) - } - }, - }, [ - $el("p", { - textContent: 'Workflow Gallery', - style: { - 'text-align': 'center', - 'color': 'var(--input-text)', - 'font-size': '18px', - 'margin': 0, - 'padding': 0, - } - }, [ - $el("p", { - id: 'workflowgallery-button-last-visited-label', - textContent: `(${localStorage.getItem("wg_last_visited") ? localStorage.getItem("wg_last_visited").split('/')[2] : 'none selected'})`, - style: { - 'text-align': 'center', - 'color': 'var(--input-text)', - 'font-size': '12px', - 'margin': 0, - 'padding': 0, - } - }) - ]), - $el("div.pysssss-workflow-arrow-2", { - id: `comfyworkflows-button-arrow`, - onclick: this.handleWorkflowGalleryButtonClick - }) - ]), - - $el("button.p-button.p-component.cm-button", { - id: 'cm-nodeinfo-button', - type: "button", - textContent: "Nodes Info", - onclick: () => { window.open("https://ltdrdata.github.io/", "comfyui-node-info"); } - }), - ]; - - var textarea = document.createElement("div"); - textarea.className = "cm-notice-board"; - elts.push(textarea); - - init_notice(textarea); - - return elts; - } - - constructor() { - super(); - - const content = $el("div.cm-menu-container", - [ - $el("div.cm-menu-column.gap-2", [...this.createControlsLeft()]), - $el("div.cm-menu-column.gap-2", [...this.createControlsMid()]), - $el("div.cm-menu-column.gap-2", [...this.createControlsRight()]) - ] - ); - - const frame = buildGuiFrame( - 'cm-manager-dialog', // dialog id - `ComfyUI Manager ${manager_version}`, // dialog title - "i.mdi.mdi-puzzle", // dialog icon class to show before title - content, // dialog content element - this - ); // send this so we can attach close functions - - this.element = frame; - } - - get isVisible() { - return this.element?.style?.display !== "none"; - } - - show() { - this.element.style.display = "flex"; - } - - toggleVisibility() { - if (this.isVisible) { - this.close(); - } else { - this.show(); - } - } - - handleWorkflowGalleryButtonClick(e) { - e.preventDefault(); - e.stopPropagation(); - LiteGraph.closeAllContextMenus(); - - // Modify the style of the button so that the UI can indicate the last - // visited site right away. - const modifyButtonStyle = (url) => { - const workflowGalleryButton = document.getElementById('workflowgallery-button'); - workflowGalleryButton.style.height = '50px'; - const lastVisitedLabel = document.getElementById('workflowgallery-button-last-visited-label'); - lastVisitedLabel.textContent = `(${url.split('/')[2]})`; - } - - const menu = new LiteGraph.ContextMenu( - [ - { - title: "Share your art", - callback: () => { - if (share_option === 'openart') { - showOpenArtShareDialog(); - return; - } else if (share_option === 'matrix' || share_option === 'comfyworkflows') { - showShareDialog(share_option); - return; - } else if (share_option === 'youml') { - showYouMLShareDialog(); - return; - } - - if (!ShareDialogChooser.instance) { - ShareDialogChooser.instance = new ShareDialogChooser(); - } - ShareDialogChooser.instance.show(); - }, - }, - { - title: "Open 'openart.ai'", - callback: () => { - const url = "https://openart.ai/workflows/dev"; - localStorage.setItem("wg_last_visited", url); - window.open(url, url); - modifyButtonStyle(url); - }, - }, - { - title: "Open 'youml.com'", - callback: () => { - const url = "https://youml.com/?from=comfyui-share"; - localStorage.setItem("wg_last_visited", url); - window.open(url, url); - modifyButtonStyle(url); - }, - }, - { - title: "Open 'comfyworkflows.com'", - callback: () => { - const url = "https://comfyworkflows.com/"; - localStorage.setItem("wg_last_visited", url); - window.open(url, url); - modifyButtonStyle(url); - }, - }, - { - title: "Open 'esheep'", - callback: () => { - const url = "https://www.esheep.com"; - localStorage.setItem("wg_last_visited", url); - window.open(url, url); - modifyButtonStyle(url); - }, - }, - { - title: "Open 'Copus.io'", - callback: () => { - const url = "https://www.copus.io"; - localStorage.setItem("wg_last_visited", url); - window.open(url, url); - modifyButtonStyle(url); - }, - }, - { - title: "Close", - callback: () => { - LiteGraph.closeAllContextMenus(); - }, - } - ], - { - event: e, - scale: 1.3, - }, - window - ); - // set the id so that we can override the context menu's z-index to be above the comfyui manager menu - menu.root.id = "workflowgallery-button-menu"; - menu.root.classList.add("pysssss-workflow-popup-2"); - } -} - -async function getVersion() { - let version = await api.fetchApi(`/manager/version`); - return await version.text(); -} - -app.registerExtension({ - name: "Comfy.ManagerMenu", - - aboutPageBadges: [ - { - label: `ComfyUI-Manager ${manager_version}`, - url: 'https://github.com/ltdrdata/ComfyUI-Manager', - icon: 'pi pi-th-large' - } - ], - - commands: [ - { - id: "Comfy.Manager.Menu.ToggleVisibility", - label: "Toggle Manager Menu Visibility", - icon: "mdi mdi-puzzle", - function: () => { - if (!manager_instance) { - setManagerInstance(new ManagerMenuDialog()); - manager_instance.show(); - } else { - manager_instance.toggleVisibility(); - } - }, - }, - { - id: "Comfy.Manager.CustomNodesManager.ToggleVisibility", - label: "Toggle Custom Nodes Manager Visibility", - icon: "pi pi-server", - function: () => { - if (CustomNodesManager.instance?.isVisible) { - CustomNodesManager.instance.close(); - return; - } - - if (!manager_instance) { - setManagerInstance(new ManagerMenuDialog()); - } - if (!CustomNodesManager.instance) { - CustomNodesManager.instance = new CustomNodesManager(app, self); - } - CustomNodesManager.instance.show(CustomNodesManager.ShowMode.NORMAL); - }, - } - ], - - init() { - $el("style", { - textContent: style, - parent: document.head, - }); - }, - async setup() { - let orig_clear = app.graph.clear; - app.graph.clear = function () { - orig_clear.call(app.graph); - load_components(); - }; - - load_components(); - - // Fetch and show startup alerts (critical errors like outdated ComfyUI) - // Poll until extensionManager.toast is ready (set in Vue onMounted) - const showStartupAlerts = async () => { - let toastWaitCount = 0; - const waitForToast = () => { - if (window['app']?.extensionManager?.toast) { - fetch('/manager/startup_alerts') - .then(response => response.ok ? response.json() : []) - .then(alerts => { - for (const alert of alerts) { - customAlert(alert.message); - } - }) - .catch(e => console.warn('[ComfyUI-Manager] Failed to fetch startup alerts:', e)); - } else if (toastWaitCount < 300) { // Max 30 seconds (300 * 100ms) - toastWaitCount++; - setTimeout(waitForToast, 100); - } else { - console.warn('[ComfyUI-Manager] Timeout waiting for toast. Startup alerts skipped.'); - } - }; - waitForToast(); - }; - showStartupAlerts(); - - const menu = document.querySelector(".comfy-menu"); - const separator = document.createElement("hr"); - - separator.style.margin = "20px 0"; - separator.style.width = "100%"; - menu.append(separator); - - try { - // new style Manager buttons - // unload models button into new style Manager button - let cmGroup = new (await import("../../scripts/ui/components/buttonGroup.js")).ComfyButtonGroup( - new(await import("../../scripts/ui/components/button.js")).ComfyButton({ - icon: "puzzle", - action: () => { - if(!manager_instance) - setManagerInstance(new ManagerMenuDialog()); - manager_instance.show(); - }, - tooltip: "ComfyUI Manager", - content: "Manager", - classList: "comfyui-button comfyui-menu-mobile-collapse primary" - }).element, - new(await import("../../scripts/ui/components/button.js")).ComfyButton({ - icon: "star", - action: () => { - if(!manager_instance) - setManagerInstance(new ManagerMenuDialog()); - - if(!CustomNodesManager.instance) { - CustomNodesManager.instance = new CustomNodesManager(app, self); - } - CustomNodesManager.instance.show(CustomNodesManager.ShowMode.FAVORITES); - }, - tooltip: "Show favorite custom node list" - }).element, - new(await import("../../scripts/ui/components/button.js")).ComfyButton({ - icon: "vacuum-outline", - action: () => { - free_models(); - }, - tooltip: "Unload Models" - }).element, - new(await import("../../scripts/ui/components/button.js")).ComfyButton({ - icon: "vacuum", - action: () => { - free_models(true); - }, - tooltip: "Free model and node cache" - }).element, - new(await import("../../scripts/ui/components/button.js")).ComfyButton({ - icon: "share", - action: () => { - if (share_option === 'openart') { - showOpenArtShareDialog(); - return; - } else if (share_option === 'matrix' || share_option === 'comfyworkflows') { - showShareDialog(share_option); - return; - } else if (share_option === 'youml') { - showYouMLShareDialog(); - return; - } - - if(!ShareDialogChooser.instance) { - ShareDialogChooser.instance = new ShareDialogChooser(); - } - ShareDialogChooser.instance.show(); - }, - tooltip: "Share" - }).element - ); - - app.menu?.settingsGroup.element.before(cmGroup.element); - } - catch(exception) { - console.log('ComfyUI is outdated. New style menu based features are disabled.'); - } - - // old style Manager button - const managerButton = document.createElement("button"); - managerButton.textContent = "Manager"; - managerButton.onclick = () => { - if(!manager_instance) - setManagerInstance(new ManagerMenuDialog()); - manager_instance.show(); - } - menu.append(managerButton); - - const shareButton = document.createElement("button"); - shareButton.id = "shareButton"; - shareButton.textContent = "Share"; - shareButton.onclick = () => { - if (share_option === 'openart') { - showOpenArtShareDialog(); - return; - } else if (share_option === 'matrix' || share_option === 'comfyworkflows') { - showShareDialog(share_option); - return; - } else if (share_option === 'youml') { - showYouMLShareDialog(); - return; - } - - if(!ShareDialogChooser.instance) { - ShareDialogChooser.instance = new ShareDialogChooser(); - } - ShareDialogChooser.instance.show(); - } - // make the background color a gradient of blue to green - shareButton.style.background = "linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; - shareButton.style.color = "black"; - - // Load share option from local storage to determine whether to show - // the share button. - const shouldShowShareButton = share_option !== 'none'; - shareButton.style.display = shouldShowShareButton ? "inline-block" : "none"; - - menu.append(shareButton); - }, - - async beforeRegisterNodeDef(nodeType, nodeData, app) { - this._addExtraNodeContextMenu(nodeType, app); - }, - - _addExtraNodeContextMenu(node, app) { - const origGetExtraMenuOptions = node.prototype.getExtraMenuOptions; - node.prototype.cm_menu_added = true; - node.prototype.getExtraMenuOptions = function (_, options) { - origGetExtraMenuOptions?.apply?.(this, arguments); - - if (node.category.startsWith('group nodes>')) { - options.push({ - content: "Save As Component", - callback: (obj) => { - if (!ComponentBuilderDialog.instance) { - ComponentBuilderDialog.instance = new ComponentBuilderDialog(); - } - ComponentBuilderDialog.instance.target_node = node; - ComponentBuilderDialog.instance.show(); - } - }, null); - } - - if (isOutputNode(node)) { - const { potential_outputs } = getPotentialOutputsAndOutputNodes([this]); - const hasOutput = potential_outputs.length > 0; - - // Check if the previous menu option is `null`. If it's not, - // then we need to add a `null` as a separator. - if (options[options.length - 1] !== null) { - options.push(null); - } - - options.push({ - content: "🏞️ Share Output", - disabled: !hasOutput, - callback: (obj) => { - if (!ShareDialog.instance) { - ShareDialog.instance = new ShareDialog(); - } - const shareButton = document.getElementById("shareButton"); - if (shareButton) { - const currentNode = this; - if (!OpenArtShareDialog.instance) { - OpenArtShareDialog.instance = new OpenArtShareDialog(); - } - OpenArtShareDialog.instance.selectedNodeId = currentNode.id; - if (!ShareDialog.instance) { - ShareDialog.instance = new ShareDialog(share_option); - } - ShareDialog.instance.selectedNodeId = currentNode.id; - shareButton.click(); - } - } - }, null); - } - } - }, -}); diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js deleted file mode 100644 index e6f3e103..00000000 --- a/js/comfyui-share-common.js +++ /dev/null @@ -1,1102 +0,0 @@ -import { api } from "../../scripts/api.js"; -import { app } from "../../scripts/app.js"; -import { $el, ComfyDialog } from "../../scripts/ui.js"; -import { CopusShareDialog } from "./comfyui-share-copus.js"; -import { OpenArtShareDialog } from "./comfyui-share-openart.js"; -import { YouMLShareDialog } from "./comfyui-share-youml.js"; -import { customAlert } from "./common.js"; - -export const SUPPORTED_OUTPUT_NODE_TYPES = [ - "PreviewImage", - "SaveImage", - "VHS_VideoCombine", - "ADE_AnimateDiffCombine", - "SaveAnimatedWEBP", - "CR Image Output" -] - -var docStyle = document.createElement('style'); -docStyle.innerHTML = ` -.cm-menu-container { - column-gap: 20px; - display: flex; - flex-wrap: wrap; - justify-content: center; -} - -.cm-menu-column { - display: flex; - flex-direction: column; -} - -.cm-title { - padding: 10px 10px 0 10p; - background-color: black; - text-align: center; - height: 45px; -} -`; -document.head.appendChild(docStyle); - -export function getPotentialOutputsAndOutputNodes(nodes) { - const potential_outputs = []; - const potential_output_nodes = []; - - // iterate over the array of nodes to find the ones that are marked as SaveImage - // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - if (!SUPPORTED_OUTPUT_NODE_TYPES.includes(node.type)) { - continue; - } - - if (node.type === "SaveImage" || node.type === "CR Image Output") { - // check if node has an 'images' array property - if (node.hasOwnProperty("images") && Array.isArray(node.images)) { - // iterate over the images array and add each image to the potential_outputs array - for (let j = 0; j < node.images.length; j++) { - potential_output_nodes.push(node); - potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title, "node_id": node.id }); - } - } - } - else if (node.type === "PreviewImage") { - // check if node has an 'images' array property - if (node.hasOwnProperty("images") && Array.isArray(node.images)) { - // iterate over the images array and add each image to the potential_outputs array - for (let j = 0; j < node.images.length; j++) { - potential_output_nodes.push(node); - potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title, "node_id": node.id }); - } - } - } - else if (node.type === "VHS_VideoCombine") { - // check if node has a 'widgets' array property, with type 'image' - if (node.hasOwnProperty("widgets") && Array.isArray(node.widgets)) { - // iterate over the widgets array and add each image to the potential_outputs array - for (let j = 0; j < node.widgets.length; j++) { - if (node.widgets[j].type === "image") { - const widgetValue = node.widgets[j].value; - const parsedURLVals = parseURLPath(widgetValue); - - // ensure that the parsedURLVals have 'filename', 'subfolder', 'type', and 'format' properties - if (parsedURLVals.hasOwnProperty("filename") && parsedURLVals.hasOwnProperty("subfolder") && parsedURLVals.hasOwnProperty("type") && parsedURLVals.hasOwnProperty("format")) { - if (parsedURLVals.type !== "output") { - // TODO - } - potential_output_nodes.push(node); - potential_outputs.push({ "type": "output", 'title': node.title, "node_id": node.id , "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": widgetValue, "format": parsedURLVals.format } }); - } - } else if (node.widgets[j].type === "preview") { - const widgetValue = node.widgets[j].value; - const parsedURLVals = widgetValue.params; - - if(!parsedURLVals.format?.startsWith('image')) { - // video isn't supported format - continue; - } - - // ensure that the parsedURLVals have 'filename', 'subfolder', 'type', and 'format' properties - if (parsedURLVals.hasOwnProperty("filename") && parsedURLVals.hasOwnProperty("subfolder") && parsedURLVals.hasOwnProperty("type") && parsedURLVals.hasOwnProperty("format")) { - if (parsedURLVals.type !== "output") { - // TODO - } - potential_output_nodes.push(node); - potential_outputs.push({ "type": "output", 'title': node.title, "node_id": node.id , "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": `/view?filename=${parsedURLVals.filename}&subfolder=${parsedURLVals.subfolder}&type=${parsedURLVals.type}&format=${parsedURLVals.format}`, "format": parsedURLVals.format } }); - } - } - } - } - } - else if (node.type === "ADE_AnimateDiffCombine") { - // check if node has a 'widgets' array property, with type 'image' - if (node.hasOwnProperty("widgets") && Array.isArray(node.widgets)) { - // iterate over the widgets array and add each image to the potential_outputs array - for (let j = 0; j < node.widgets.length; j++) { - if (node.widgets[j].type === "image") { - const widgetValue = node.widgets[j].value; - const parsedURLVals = parseURLPath(widgetValue); - // ensure that the parsedURLVals have 'filename', 'subfolder', 'type', and 'format' properties - if (parsedURLVals.hasOwnProperty("filename") && parsedURLVals.hasOwnProperty("subfolder") && parsedURLVals.hasOwnProperty("type") && parsedURLVals.hasOwnProperty("format")) { - if (parsedURLVals.type !== "output") { - // TODO - continue; - } - potential_output_nodes.push(node); - potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "type": parsedURLVals.type, "value": widgetValue, "format": parsedURLVals.format } }); - } - } - } - } - } - else if (node.type === "SaveAnimatedWEBP") { - // check if node has an 'images' array property - if (node.hasOwnProperty("images") && Array.isArray(node.images)) { - // iterate over the images array and add each image to the potential_outputs array - for (let j = 0; j < node.images.length; j++) { - potential_output_nodes.push(node); - potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); - } - } - } - } - - // Note: make sure that two arrays are the same length - return { potential_outputs, potential_output_nodes }; -} - - -export function parseURLPath(urlPath) { - // Extract the query string from the URL path - var queryString = urlPath.split('?')[1]; - - // Use the URLSearchParams API to parse the query string - var params = new URLSearchParams(queryString); - - // Create an object to store the parsed parameters - var parsedParams = {}; - - // Iterate over each parameter and add it to the object - for (var pair of params.entries()) { - parsedParams[pair[0]] = pair[1]; - } - - // Return the object with the parsed parameters - return parsedParams; -} - - -export const shareToEsheep= () => { - app.graphToPrompt() - .then(prompt => { - const nodes = app.graph._nodes - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - const workflow = prompt['workflow'] - api.fetchApi(`/manager/set_esheep_workflow_and_images`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - workflow: workflow, - images: potential_outputs - }) - }).then(response => { - var domain = window.location.hostname; - var port = window.location.port; - port = port || (window.location.protocol === 'http:' ? '80' : window.location.protocol === 'https:' ? '443' : ''); - var full_domin = domain + ':' + port - window.open('https://www.esheep.com/app/workflow_upload?from_local='+ full_domin, '_blank'); - }); - }) -} - -export const showCopusShareDialog = () => { - if (!CopusShareDialog.instance) { - CopusShareDialog.instance = new CopusShareDialog(); - } - - return app.graphToPrompt() - .then(prompt => { - return app.graph._nodes; - }) - .then(nodes => { - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - CopusShareDialog.instance.show({ potential_outputs, potential_output_nodes}); - }) -} - -export const showOpenArtShareDialog = () => { - if (!OpenArtShareDialog.instance) { - OpenArtShareDialog.instance = new OpenArtShareDialog(); - } - - return app.graphToPrompt() - .then(prompt => { - // console.log({ prompt }) - return app.graph._nodes; - }) - .then(nodes => { - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - OpenArtShareDialog.instance.show({ potential_outputs, potential_output_nodes}); - }) -} - - -export const showYouMLShareDialog = () => { - if (!YouMLShareDialog.instance) { - YouMLShareDialog.instance = new YouMLShareDialog(); - } - - return app.graphToPrompt() - .then(prompt => { - return app.graph._nodes; - }) - .then(nodes => { - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - YouMLShareDialog.instance.show(potential_outputs, potential_output_nodes); - }) -} - - -export const showShareDialog = async (share_option) => { - if (!ShareDialog.instance) { - ShareDialog.instance = new ShareDialog(share_option); - } - return app.graphToPrompt() - .then(prompt => { - // console.log({ prompt }) - return app.graph._nodes; - }) - .then(nodes => { - // console.log({ nodes }); - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - if (potential_outputs.length === 0) { - if (potential_output_nodes.length === 0) { - // todo: add support for other output node types (animatediff combine, etc.) - const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); - customAlert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); - } else { - customAlert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); - } - return false; - } - ShareDialog.instance.show({ potential_outputs, potential_output_nodes, share_option }); - return true; - }); -} - -export class ShareDialogChooser extends ComfyDialog { - static instance = null; - constructor() { - super(); - this.element = $el("div.comfy-modal", { - parent: document.body, style: { - 'overflow-y': "auto", - } - }, - [$el("div.comfy-modal-content", - {}, - [...this.createButtons()]), - ]); - this.selectedNodeId = null; - } - createButtons() { - const buttons = [ - { - key: "openart", - textContent: "OpenArt AI", - website: "https://openart.ai/workflows/", - description: "Share ComfyUI workflows and art on OpenArt.ai", - onclick: () => { - showOpenArtShareDialog(); - this.close(); - } - }, - { - key: "youml", - textContent: "YouML", - website: "https://youml.com", - description: "Share your workflow or transform it into an interactive app on YouML.com", - onclick: () => { - showYouMLShareDialog(); - this.close(); - } - }, - { - key: "matrix", - textContent: "Matrix Server", - website: "https://app.element.io/#/room/%23comfyui_space%3Amatrix.org", - description: "Share your art on the official ComfyUI matrix server", - onclick: async () => { - showShareDialog('matrix').then((suc) => { - suc && this.close(); - }) - } - }, - { - key: "comfyworkflows", - textContent: "ComfyWorkflows", - website: "https://comfyworkflows.com", - description: "Share & browse thousands of ComfyUI workflows and art 🎨

ComfyWorkflows.com", - onclick: () => { - showShareDialog('comfyworkflows').then((suc) => { - suc && this.close(); - }) - } - }, - { - key: "esheep", - textContent: "eSheep", - website: "https://www.esheep.com", - description: "Share & download thousands of ComfyUI workflows on esheep.com", - onclick: () => { - shareToEsheep(); - this.close(); - } - }, - { - key: "Copus", - textContent: "Copus", - website: "https://www.copus.io", - description: "🔴 Earn simple. Get paid from your ComfyUI workflows—no revenue sharing. Ever.", - onclick: () => { - showCopusShareDialog(); - this.close(); - } - }, - ]; - - function createShareButtonsWithDescriptions() { - // Responsive container - const container = $el("div", { - style: { - display: "flex", - 'flex-wrap': 'wrap', - 'justify-content': 'space-around', - 'padding': '10px', - } - }); - - buttons.forEach(b => { - const button = $el("button", - { - type: "button", - textContent: b.textContent, - onclick: b.onclick, - style: { - 'width': '25%', - 'minWidth': '200px', - 'background-color': b.backgroundColor || '', - 'border-radius': '5px', - 'cursor': 'pointer', - 'padding': '5px 5px', - 'margin-bottom': '5px', - 'transition': 'background-color 0.3s', - 'position':'relative' - } - }, - [ - $el("span", { style: { - } }), - ] - ); - button.addEventListener('mouseover', () => { - button.style.backgroundColor = '#007BFF'; // Change color on hover - }); - button.addEventListener('mouseout', () => { - button.style.backgroundColor = b.backgroundColor || ''; - }); - - const description = $el("p", { - innerHTML: b.description, - style: { - 'text-align': 'left', - color: 'var(--input-text)', - 'font-size': '14px', - 'margin-bottom': '0', - }, - }); - - const copus_ui =$el("div", { style: { - 'position': 'absolute', - 'height': '100%', - 'left': '-25px', - 'top': '-26px', - 'width': '100%', - 'z-index':'-1', - 'background':'url("https://static.copus.io/images/client/202412/test/f28ac6ef8f4c6f3d5d50856a272ed02c.png")', - 'background-repeat': 'no-repeat', - } }); - const copus_ui_bottom =$el("div", { style: { - 'position': 'absolute', - 'height': '100%', - 'left': '25px', - 'bottom': '-26px', - 'width': '100%', - 'transform':'scale(-1, -1)', - 'z-index':'-1', - 'background':'url("https://static.copus.io/images/client/202412/test/f28ac6ef8f4c6f3d5d50856a272ed02c.png")', - 'background-repeat': 'no-repeat', - } }); - - const websiteLink = $el("a", { - textContent: "🌐 Website", - href: b.website, - target: "_blank", - style: { - color: 'var(--input-text)', - 'margin-left': '10px', - 'font-size': '12px', - 'text-decoration': 'none', - 'align-self': 'center', - }, - }); - - // Add highlight to the website link - websiteLink.addEventListener('mouseover', () => { - websiteLink.style.opacity = '0.7'; - }); - - websiteLink.addEventListener('mouseout', () => { - websiteLink.style.opacity = '1'; - }); - - const buttonLinkContainer = $el("div", { - style: { - display: 'flex', - 'align-items': 'center', - 'margin-bottom': '10px', - } - }, [button, websiteLink]); - const column = $el("div", { - style: { - 'flex-basis': '100%', - 'margin': '10px', - 'padding': '10px 20px', - 'border': '1px solid #ddd', - 'border-radius': '5px', - 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)', - 'position':'relative' - } - }, [buttonLinkContainer, description - , - b.key ==='Copus' ? - copus_ui - :'', - b.key ==='Copus' ? - copus_ui_bottom - :'', - ]); - - container.appendChild(column); - }); - - return container; - } - - return [ - $el("p", { - textContent: 'Choose a platform to share your workflow', - style: { - 'text-align': 'center', - 'color': 'var(--input-text)', - 'font-size': '18px', - 'margin-bottom': '10px', - }, - } - ), - - $el("div.cm-menu-container", { - id: "comfyui-share-container" - }, [ - $el("div.cm-menu-column", [ - createShareButtonsWithDescriptions(), - $el("br", {}, []), - ]), - ]), - $el("div.cm-menu-container", { - id: "comfyui-share-container" - }, [ - $el("button", { - type: "button", - style: { - margin: "0 25px", - width: "100%", - }, - textContent: "Close", - onclick: () => { - this.close() - } - }), - $el("br", {}, []), - ]), - ]; - } - show() { - this.element.style.display = "block"; - this.element.style.zIndex = 1099; - } -} -export class ShareDialog extends ComfyDialog { - static instance = null; - static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; - static cw_sharekey = ""; - - constructor(share_option) { - super(); - this.share_option = share_option; - this.element = $el("div.comfy-modal", { - parent: document.body, style: { - 'overflow-y': "auto", - } - }, - [$el("div.comfy-modal-content", - {}, - [...this.createButtons()]), - ]); - this.selectedOutputIndex = 0; - } - - createButtons() { - this.radio_buttons = $el("div", { - id: "selectOutputImages", - }, []); - - this.is_nsfw_checkbox = $el("input", { type: 'checkbox', id: "is_nsfw" }, []) - const is_nsfw_checkbox_text = $el("label", { - }, [" Is this NSFW?"]) - this.is_nsfw_checkbox.style.color = "var(--fg-color)"; - this.is_nsfw_checkbox.checked = false; - - this.matrix_destination_checkbox = $el("input", { type: 'checkbox', id: "matrix_destination" }, []) - const matrix_destination_checkbox_text = $el("label", {}, [" ComfyUI Matrix server"]) - this.matrix_destination_checkbox.style.color = "var(--fg-color)"; - this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; //true; - - 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)"; - this.comfyworkflows_destination_checkbox.checked = this.share_option !== 'matrix'; - - this.matrix_homeserver_input = $el("input", { type: 'text', id: "matrix_homeserver", placeholder: "matrix.org", value: ShareDialog.matrix_auth.homeserver || 'matrix.org' }, []); - this.matrix_username_input = $el("input", { type: 'text', placeholder: "Username", value: ShareDialog.matrix_auth.username || '' }, []); - this.matrix_password_input = $el("input", { type: 'password', placeholder: "Password", value: ShareDialog.matrix_auth.password || '' }, []); - - this.cw_sharekey_input = $el("input", { type: 'text', placeholder: "Share key (found on your profile page)", value: ShareDialog.cw_sharekey || '' }, []); - this.cw_sharekey_input.style.width = "100%"; - - this.credits_input = $el("input", { - type: "text", - placeholder: "This will be used to give credits", - required: false, - }, []); - - this.title_input = $el("input", { - type: "text", - placeholder: "ex: My awesome art", - required: false - }, []); - - this.description_input = $el("textarea", { - placeholder: "ex: Trying out a new workflow... ", - required: false, - }, []); - - this.share_button = $el("button", { - type: "submit", - textContent: "Share", - style: { - backgroundColor: "blue" - } - }, []); - - this.final_message = $el("div", { - style: { - color: "white", - textAlign: "center", - // marginTop: "10px", - // backgroundColor: "black", - padding: "10px", - } - }, []); - - this.share_finalmessage_container = $el("div.cm-menu-container", { - id: "comfyui-share-finalmessage-container", - style: { - display: "none", - } - }, [ - $el("div.cm-menu-column", [ - this.final_message, - $el("button", { - type: "button", - textContent: "Close", - onclick: () => { - // Reset state - this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; - this.comfyworkflows_destination_checkbox.checked = this.share_option !== 'matrix'; - this.share_button.textContent = "Share"; - this.share_button.style.display = "inline-block"; - this.final_message.innerHTML = ""; - this.final_message.style.color = "white"; - this.credits_input.value = ""; - this.title_input.value = ""; - this.description_input.value = ""; - this.is_nsfw_checkbox.checked = false; - this.selectedOutputIndex = 0; - - // hide the final message - this.share_finalmessage_container.style.display = "none"; - - // show the share container - this.share_container.style.display = "flex"; - - this.close() - } - }), - ]) - ]); - this.share_container = $el("div.cm-menu-container", { - id: "comfyui-share-container" - }, [ - $el("div.cm-menu-column", [ - $el("details", { - style: { - border: "1px solid #999", - padding: "5px", - borderRadius: "5px", - backgroundColor: "#222" - } - }, [ - $el("summary", { - style: { - color: "white", - cursor: "pointer", - } - }, [`Matrix account`]), - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Homeserver", - style: { - marginRight: "10px", - } - }, []), - this.matrix_homeserver_input, - ]), - - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Username", - style: { - marginRight: "10px", - } - }, []), - this.matrix_username_input, - ]), - - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Password", - style: { - marginRight: "10px", - } - }, []), - this.matrix_password_input, - ]), - - ]), - $el("details", { - style: { - border: "1px solid #999", - marginTop: "10px", - padding: "5px", - borderRadius: "5px", - backgroundColor: "#222" - }, - }, [ - $el("summary", { - style: { - color: "white", - cursor: "pointer", - } - }, [`Comfyworkflows.com account`]), - $el("h4", { - textContent: "Share key (found on your profile page)", - }, []), - $el("p", { size: 3, color: "white" }, ["If provided, your art will be saved to your account. Otherwise, it will be shared anonymously."]), - this.cw_sharekey_input, - ]), - - $el("div", {}, [ - $el("p", { - size: 3, color: "white", style: { - color: 'var(--input-text)' - } - }, [`Select where to share your art:`]), - this.matrix_destination_checkbox, - matrix_destination_checkbox_text, - $el("br", {}, []), - this.comfyworkflows_destination_checkbox, - comfyworkflows_destination_checkbox_text, - ]), - - $el("h4", { - textContent: "Credits (optional)", - size: 3, - color: "white", - style: { - color: 'var(--input-text)' - } - }, []), - this.credits_input, - // $el("br", {}, []), - - $el("h4", { - textContent: "Title (optional)", - size: 3, - color: "white", - style: { - color: 'var(--input-text)' - } - }, []), - this.title_input, - // $el("br", {}, []), - - $el("h4", { - textContent: "Description (optional)", - size: 3, - color: "white", - style: { - color: 'var(--input-text)' - } - }, []), - this.description_input, - $el("br", {}, []), - - $el("div", {}, [this.is_nsfw_checkbox, is_nsfw_checkbox_text]), - // $el("br", {}, []), - - // this.final_message, - // $el("br", {}, []), - ]), - $el("div.cm-menu-column", [ - this.radio_buttons, - $el("br", {}, []), - - this.share_button, - - $el("button", { - type: "button", - textContent: "Close", - onclick: () => { - // Reset state - this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; - this.comfyworkflows_destination_checkbox.checked = this.share_option !== 'matrix'; - this.share_button.textContent = "Share"; - this.share_button.style.display = "inline-block"; - this.final_message.innerHTML = ""; - this.final_message.style.color = "white"; - this.credits_input.value = ""; - this.title_input.value = ""; - this.description_input.value = ""; - this.is_nsfw_checkbox.checked = false; - this.selectedOutputIndex = 0; - - // hide the final message - this.share_finalmessage_container.style.display = "none"; - - // show the share container - this.share_container.style.display = "flex"; - - this.close() - } - }), - $el("br", {}, []), - ]), - ]); - - // get the user's existing matrix auth and share key - ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; - try { - api.fetchApi(`/manager/get_matrix_auth`) - .then(response => response.json()) - .then(data => { - ShareDialog.matrix_auth = data; - this.matrix_homeserver_input.value = ShareDialog.matrix_auth.homeserver; - this.matrix_username_input.value = ShareDialog.matrix_auth.username; - this.matrix_password_input.value = ShareDialog.matrix_auth.password; - }) - .catch(error => { - // console.log(error); - }); - } catch (error) { - // console.log(error); - } - - // get the user's existing comfyworkflows share key - ShareDialog.cw_sharekey = ""; - try { - // console.log("Fetching comfyworkflows share key") - api.fetchApi(`/manager/get_comfyworkflows_auth`) - .then(response => response.json()) - .then(data => { - ShareDialog.cw_sharekey = data.comfyworkflows_sharekey; - this.cw_sharekey_input.value = ShareDialog.cw_sharekey; - }) - .catch(error => { - // console.log(error); - }); - } catch (error) { - // console.log(error); - } - - this.share_button.onclick = async () => { - const prompt = await app.graphToPrompt(); - const nodes = app.graph._nodes; - - // console.log({ prompt, nodes }); - - const destinations = []; - if (this.matrix_destination_checkbox.checked) { - destinations.push("matrix"); - } - if (this.comfyworkflows_destination_checkbox.checked) { - destinations.push("comfyworkflows"); - } - - // if destinations includes matrix, make an api call to /manager/check_matrix to ensure that the user has configured their matrix settings - if (destinations.includes("matrix")) { - let definedMatrixAuth = !!this.matrix_homeserver_input.value && !!this.matrix_username_input.value && !!this.matrix_password_input.value; - if (!definedMatrixAuth) { - customAlert("Please set your Matrix account details."); - return; - } - } - - if (destinations.includes("comfyworkflows") && !this.cw_sharekey_input.value && false) { //!confirm("You have NOT set your ComfyWorkflows.com share key. Your art will NOT be connected to your account (it will be shared anonymously). Continue?")) { - return; - } - - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - - // console.log({ potential_outputs, potential_output_nodes }) - - if (potential_outputs.length === 0) { - if (potential_output_nodes.length === 0) { - // todo: add support for other output node types (animatediff combine, etc.) - const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); - customAlert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); - } else { - customAlert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); - } - this.selectedOutputIndex = 0; - this.close(); - return; - } - - // Change the text of the share button to "Sharing..." to indicate that the share process has started - this.share_button.textContent = "Sharing..."; - - const response = await api.fetchApi(`/manager/share`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - matrix_auth: { - homeserver: this.matrix_homeserver_input.value, - username: this.matrix_username_input.value, - password: this.matrix_password_input.value, - }, - cw_auth: { - cw_sharekey: this.cw_sharekey_input.value, - }, - share_destinations: destinations, - credits: this.credits_input.value, - title: this.title_input.value, - description: this.description_input.value, - is_nsfw: this.is_nsfw_checkbox.checked, - prompt, - potential_outputs, - selected_output_index: this.selectedOutputIndex, - // potential_output_nodes - }) - }); - - if (response.status != 200) { - try { - const response_json = await response.json(); - if (response_json.error) { - customAlert(response_json.error); - this.close(); - return; - } else { - customAlert("Failed to share your art. Please try again."); - this.close(); - return; - } - } catch (e) { - customAlert("Failed to share your art. Please try again."); - this.close(); - return; - } - } - - const response_json = await response.json(); - - if (response_json.comfyworkflows.url) { - this.final_message.innerHTML = "Your art has been shared: " + response_json.comfyworkflows.url + ""; - if (response_json.matrix.success) { - this.final_message.innerHTML += "
Your art has been shared in the ComfyUI Matrix server's #share channel!"; - } - } else { - if (response_json.matrix.success) { - this.final_message.innerHTML = "Your art has been shared in the ComfyUI Matrix server's #share channel!"; - } - } - - this.final_message.style.color = "green"; - - // hide #comfyui-share-container and show #comfyui-share-finalmessage-container - this.share_container.style.display = "none"; - this.share_finalmessage_container.style.display = "block"; - - // hide the share button - this.share_button.textContent = "Shared!"; - this.share_button.style.display = "none"; - // this.close(); - } - - const res = - [ - $el("tr.td", { width: "100%" }, [ - $el("font", { size: 6, color: "white" }, [`Share your art`]), - ]), - $el("br", {}, []), - - this.share_finalmessage_container, - this.share_container, - ]; - - res[0].style.padding = "10px 10px 10px 10px"; - res[0].style.backgroundColor = "black"; //"linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; - res[0].style.textAlign = "center"; - res[0].style.height = "45px"; - return res; - } - - show({potential_outputs, potential_output_nodes, share_option}) { - // Sort `potential_output_nodes` by node ID to make the order always - // consistent, but we should also keep `potential_outputs` in the same - // order as `potential_output_nodes`. - const potential_output_to_order = {}; - potential_output_nodes.forEach((node, index) => { - if (node.id in potential_output_to_order) { - potential_output_to_order[node.id][1].push(potential_outputs[index]); - } else { - potential_output_to_order[node.id] = [node, [potential_outputs[index]]]; - } - }) - // Sort the object `potential_output_to_order` by key (node ID) - const sorted_potential_output_to_order = Object.fromEntries( - Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id) - ); - const sorted_potential_outputs = [] - const sorted_potential_output_nodes = [] - for (const [key, value] of Object.entries(sorted_potential_output_to_order)) { - sorted_potential_output_nodes.push(value[0]); - sorted_potential_outputs.push(...value[1]); - } - potential_output_nodes = sorted_potential_output_nodes; - potential_outputs = sorted_potential_outputs; - - // console.log({ potential_outputs, potential_output_nodes }) - this.radio_buttons.innerHTML = ""; // clear the radio buttons - let is_radio_button_checked = false; // only check the first radio button if multiple images from the same node - const new_radio_buttons = $el("div", { - id: "selectOutput-Options", - style: { - 'overflow-y': 'scroll', - 'max-height': '400px', - } - }, potential_outputs.map((output, index) => { - const {node_id} = output; - const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) - let radio_button_img; - if (output.type === "image" || output.type === "temp") { - radio_button_img = $el("img", { src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, style: { width: "auto", height: "100px" } }, []); - } else if (output.type === "output") { - radio_button_img = $el("img", { src: output.output.value, style: { width: "auto", height: "100px" } }, []); - } else { - // unsupported output type - // this should never happen - // TODO - radio_button_img = $el("img", { src: "", style: { width: "auto", height: "100px" } }, []); - } - const radio_button_text = $el("label", { - // style: { - // color: 'var(--input-text)' - // } - }, [output.title]) - radio_button.style.color = "var(--fg-color)"; - - // Make the radio button checked if it's the selected node, - // otherwise make the first radio button checked. - if (this.selectedNodeId) { - if (this.selectedNodeId === node_id && !is_radio_button_checked) { - radio_button.checked = true; - is_radio_button_checked = true; - } - } else { - radio_button.checked = index === 0; - } - - if (radio_button.checked) { - this.selectedOutputIndex = index; - } - - radio_button.onchange = () => { - this.selectedOutputIndex = parseInt(radio_button.value); - }; - - return $el("div", { - style: { - display: "flex", - 'align-items': 'center', - 'justify-content': 'space-between', - 'margin-bottom': '10px', - } - }, [radio_button, radio_button_text, radio_button_img]); - })); - const header = $el("h3", { - textContent: "Select an image to share", - size: 3, - color: "white", - style: { - 'text-align': 'center', - color: 'var(--input-text)', - backgroundColor: 'black', - padding: '10px', - 'margin-top': '0px', - } - }, [ - $el("p", { - textContent: "Scroll to see all outputs", - size: 2, - color: "white", - style: { - 'text-align': 'center', - color: 'var(--input-text)', - 'margin-bottom': '5px', - 'font-style': 'italic', - 'font-size': '12px', - }, - }, []) - ]); - this.radio_buttons.appendChild(header); - // this.radio_buttons.appendChild(subheader); - this.radio_buttons.appendChild(new_radio_buttons); - this.element.style.display = "block"; - - share_option = share_option || this.share_option; - if (share_option === 'comfyworkflows') { - this.matrix_destination_checkbox.checked = false; - this.comfyworkflows_destination_checkbox.checked = true; - } else { - this.matrix_destination_checkbox.checked = true; - this.comfyworkflows_destination_checkbox.checked = false; - } - } -} diff --git a/js/comfyui-share-copus.js b/js/comfyui-share-copus.js deleted file mode 100644 index 46288e59..00000000 --- a/js/comfyui-share-copus.js +++ /dev/null @@ -1,1168 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { $el, ComfyDialog } from "../../scripts/ui.js"; -import { customAlert } from "./common.js"; - -const env = "prod"; - -let DEFAULT_HOMEPAGE_URL = "https://copus.io"; - -let API_ENDPOINT = "https://api.client.prod.copus.io"; - -if (env !== "prod") { - API_ENDPOINT = "https://api.test.copus.io"; - DEFAULT_HOMEPAGE_URL = "https://test.copus.io"; -} - -const style = ` - .copus-share-dialog a { - color: #f8f8f8; - } - .copus-share-dialog a:hover { - color: #007bff; - } - .output_label { - border: 5px solid transparent; - } - .output_label:hover { - border: 5px solid #59E8C6; - } - .output_label.checked { - border: 5px solid #59E8C6; - } -`; - -// Shared component styles -const sectionStyle = { - marginBottom: 0, - padding: 0, - borderRadius: "8px", - boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)", - display: "flex", - flexDirection: "column", - justifyContent: "center", - position: "relative", -}; - -export class CopusShareDialog extends ComfyDialog { - static instance = null; - - constructor() { - super(); - $el("style", { - textContent: style, - parent: document.head, - }); - this.element = $el( - "div.comfy-modal.copus-share-dialog", - { - parent: document.body, - style: { - "overflow-y": "auto", - }, - }, - [$el("div.comfy-modal-content", {}, [...this.createButtons()])] - ); - this.selectedOutputIndex = 0; - this.selectedOutput_lock = 0; - this.selectedNodeId = null; - this.uploadedImages = []; - this.allFilesImages = []; - this.selectedFile = null; - this.allFiles = []; - this.titleNum = 0; - } - - createButtons() { - const inputStyle = { - display: "block", - minWidth: "500px", - width: "100%", - padding: "10px", - margin: "10px 0", - borderRadius: "4px", - border: "1px solid #ddd", - boxSizing: "border-box", - }; - - const textAreaStyle = { - display: "block", - minWidth: "500px", - width: "100%", - padding: "10px", - margin: "10px 0", - borderRadius: "4px", - border: "1px solid #ddd", - boxSizing: "border-box", - minHeight: "100px", - background: "#222", - resize: "vertical", - color: "#f2f2f2", - fontFamily: "Arial", - fontWeight: "400", - fontSize: "15px", - }; - - const hyperLinkStyle = { - display: "block", - marginBottom: "15px", - fontWeight: "bold", - fontSize: "14px", - }; - - const labelStyle = { - color: "#f8f8f8", - display: "block", - margin: "10px 0 0 0", - fontWeight: "bold", - textDecoration: "none", - }; - - const buttonStyle = { - padding: "10px 80px", - margin: "10px 5px", - borderRadius: "4px", - border: "none", - cursor: "pointer", - color: "#fff", - backgroundColor: "#007bff", - }; - - // upload images input - this.uploadImagesInput = $el("input", { - type: "file", - multiple: false, - style: inputStyle, - accept: "image/*", - }); - - this.uploadImagesInput.addEventListener("change", async (e) => { - const file = e.target.files[0]; - if (!file) { - this.previewImage.src = ""; - this.previewImage.style.display = "none"; - return; - } - const reader = new FileReader(); - reader.onload = async (e) => { - const imgData = e.target.result; - this.previewImage.src = imgData; - this.previewImage.style.display = "block"; - this.selectedFile = null; - // Once user uploads an image, we uncheck all radio buttons - this.radioButtons.forEach((ele) => { - ele.checked = false; - ele.parentElement.classList.remove("checked"); - }); - - // Add the opacity style toggle here to indicate that they only need - // to upload one image or choose one from the outputs. - this.outputsSection.style.opacity = 0.35; - this.uploadImagesInput.style.opacity = 1; - }; - reader.readAsDataURL(file); - }); - - // preview image - this.previewImage = $el("img", { - src: "", - style: { - width: "100%", - maxHeight: "100px", - objectFit: "contain", - display: "none", - marginTop: "10px", - }, - }); - - this.keyInput = $el("input", { - type: "password", - placeholder: "Copy & paste your API key", - style: inputStyle, - }); - this.TitleInput = $el("input", { - type: "text", - placeholder: "Title (Required)", - style: inputStyle, - maxLength: "70", - oninput: () => { - const titleNum = this.TitleInput.value.length; - titleNumDom.textContent = `${titleNum}/70`; - }, - }); - this.SubTitleInput = $el("input", { - type: "text", - placeholder: "Subtitle (Optional)", - style: inputStyle, - maxLength: "350", - oninput: () => { - const titleNum = this.SubTitleInput.value.length; - subTitleNumDom.textContent = `${titleNum}/350`; - }, - }); - this.LockInput = $el("input", { - type: "text", - 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; - // Use a regular expression to match a number with up to two decimal places - const regex = /^\d*\.?\d{0,2}$/; - if (!regex.test(input)) { - // If the input doesn't match, remove the last entered character - event.target.value = input.slice(0, -1); - } - const numericValue = parseFloat(input); - if (numericValue > 9999) { - input = "9999"; - } - // Update the input field with the valid value - event.target.value = input; - }, - }); - this.descriptionInput = $el("textarea", { - placeholder: "Content (Optional)", - style: { - ...textAreaStyle, - minHeight: "100px", - }, - }); - - // Header Section - const headerSection = $el("h3", { - textContent: "Share your workflow to Copus", - size: 3, - color: "white", - style: { - "text-align": "center", - color: "white", - margin: "0 0 10px 0", - }, - }); - this.getAPIKeyLink = $el( - "a", - { - style: { - ...hyperLinkStyle, - color: "#59E8C6", - }, - href: `${DEFAULT_HOMEPAGE_URL}?fromPage=comfyUI`, - target: "_blank", - }, - ["👉 Get your API key here"] - ); - const linkSection = $el( - "div", - { - style: { - marginTop: "10px", - display: "flex", - flexDirection: "column", - }, - }, - [ - // this.communityLink, - this.getAPIKeyLink, - ] - ); - - // Account Section - const accountSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["1️⃣ Copus API Key"]), - this.keyInput, - ]); - - // Output Upload Section - const outputUploadSection = $el("div", { style: sectionStyle }, [ - $el( - "label", - { - style: { - ...labelStyle, - margin: "10px 0 0 0", - }, - }, - ["2️⃣ Image/Thumbnail (Required)"] - ), - this.previewImage, - this.uploadImagesInput, - ]); - - // Outputs Section - this.outputsSection = $el( - "div", - { - id: "selectOutputs", - }, - [] - ); - - const titleNumDom = $el( - "label", - { - style: { - fontSize: "12px", - position: "absolute", - right: "10px", - bottom: "-10px", - color: "#999", - }, - }, - ["0/70"] - ); - const subTitleNumDom = $el( - "label", - { - style: { - fontSize: "12px", - position: "absolute", - right: "10px", - bottom: "-10px", - color: "#999", - }, - }, - ["0/350"] - ); - const descriptionNumDom = $el( - "label", - { - style: { - fontSize: "12px", - position: "absolute", - right: "10px", - bottom: "-10px", - color: "#999", - }, - }, - ["0/70"] - ); - // Additional Inputs Section - const additionalInputsSection = $el("div", { style: { ...sectionStyle } }, [ - $el("label", { style: labelStyle }, ["3️⃣ Title "]), - this.TitleInput, - titleNumDom, - ]); - const SubtitleSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["4️⃣ Subtitle "]), - this.SubTitleInput, - subTitleNumDom, - ]); - const DescriptionSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["5️⃣ Description "]), - this.descriptionInput, - // descriptionNumDom, - ]); - // switch between outputs section and additional inputs section - this.radioButtons_lock = []; - - this.radioButtonsCheck_lock = $el("input", { - type: "radio", - name: "output_type_lock", - value: "0", - id: "blockchain1_lock", - checked: true, - }); - this.radioButtonsCheckOff_lock = $el("input", { - type: "radio", - name: "output_type_lock", - value: "1", - id: "blockchain_lock", - }); - - const blockChainSection_lock = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["6️⃣ Download threshold"]), - $el( - "label", - { - style: { - marginTop: "10px", - display: "flex", - alignItems: "center", - cursor: "pointer", - }, - }, - [ - this.radioButtonsCheck_lock, - $el( - "div", - { - style: { - marginLeft: "5px", - display: "flex", - alignItems: "center", - position: "relative", - }, - }, - [ - $el("span", { style: { marginLeft: "5px" } }, ["ON"]), - $el( - "span", - { - style: { - marginLeft: "20px", - marginRight: "10px", - color: "#fff", - }, - }, - ["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, - ] - ), - ] - ), - $el( - "label", - { style: { display: "flex", alignItems: "center", cursor: "pointer" } }, - [ - this.radioButtonsCheckOff_lock, - $el( - "div", - { - style: { - marginLeft: "5px", - display: "flex", - alignItems: "center", - }, - }, - [$el("span", { style: { marginLeft: "5px" } }, ["OFF"])] - ), - ] - ), - - $el( - "p", - { style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } }, - [ - ] - ), - ]); - - this.radioButtons = []; - - this.radioButtonsCheck = $el("input", { - type: "radio", - name: "output_type", - value: "0", - id: "blockchain1", - checked: true, - }); - this.radioButtonsCheckOff = $el("input", { - type: "radio", - name: "output_type", - value: "1", - id: "blockchain", - }); - - const blockChainSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["8️⃣ Store on blockchain "]), - $el( - "label", - { - style: { - marginTop: "10px", - display: "flex", - alignItems: "center", - cursor: "pointer", - }, - }, - [ - this.radioButtonsCheck, - $el("span", { style: { marginLeft: "5px" } }, ["ON"]), - ] - ), - $el( - "label", - { style: { display: "flex", alignItems: "center", cursor: "pointer" } }, - [ - this.radioButtonsCheckOff, - $el("span", { style: { marginLeft: "5px" } }, ["OFF"]), - ] - ), - $el( - "p", - { style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } }, - ["Secure ownership with a permanent & decentralized storage"] - ), - ]); - - this.ratingRadioButtonsCheck0 = $el("input", { - type: "radio", - name: "content_rating", - value: "0", - id: "content_rating0", - }); - this.ratingRadioButtonsCheck1 = $el("input", { - type: "radio", - name: "content_rating", - value: "1", - id: "content_rating1", - }); - this.ratingRadioButtonsCheck2 = $el("input", { - type: "radio", - name: "content_rating", - value: "2", - id: "content_rating2", - }); - this.ratingRadioButtonsCheck_1 = $el("input", { - type: "radio", - name: "content_rating", - value: "-1", - id: "content_rating_1", - checked: true, - }); - - // content rating - const contentRatingSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["7️⃣ Content rating "]), - $el( - "label", - { - style: { - marginTop: "10px", - display: "flex", - alignItems: "center", - cursor: "pointer", - }, - }, - [ - this.ratingRadioButtonsCheck0, - $el("img", { - style: { - width: "12px", - height: "12px", - marginLeft: "5px", - }, - src: "https://static.copus.io/images/client/202507/test/b9f17da83b054d53cd0cb4508c2c30dc.png", - }), - $el("span", { style: { marginLeft: "5px", color: "#fff" } }, [ - "All ages", - ]), - ] - ), - $el( - "p", - { style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } }, - ["Safe for all viewers; no profanity, violence, or mature themes."] - ), - $el( - "label", - { style: { display: "flex", alignItems: "center", cursor: "pointer" } }, - [ - this.ratingRadioButtonsCheck1, - $el("img", { - style: { - width: "12px", - height: "12px", - marginLeft: "5px", - }, - src: "https://static.copus.io/images/client/202507/test/7848bc0d3690671df21c7cf00c4cfc81.png", - }), - $el("span", { style: { marginLeft: "5px", color: "#fff" } }, [ - "13+ (Teen)", - ]), - ] - ), - $el( - "p", - { style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } }, - [ - "Mild language, light themes, or cartoon violence; no explicit content. ", - ] - ), - $el( - "label", - { style: { display: "flex", alignItems: "center", cursor: "pointer" } }, - [ - this.ratingRadioButtonsCheck2, - $el("img", { - style: { - width: "12px", - height: "12px", - marginLeft: "5px", - }, - src: "https://static.copus.io/images/client/202507/test/bc51839c208d68d91173e43c23bff039.png", - }), - $el("span", { style: { marginLeft: "5px", color: "#fff" } }, [ - "18+ (Explicit)", - ]), - ] - ), - $el( - "p", - { style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } }, - [ - "Explicit content, including sexual content, strong violence, or intense themes. ", - ] - ), - $el( - "label", - { style: { display: "flex", alignItems: "center", cursor: "pointer" } }, - [ - this.ratingRadioButtonsCheck_1, - $el("img", { - style: { - width: "12px", - height: "12px", - marginLeft: "5px", - }, - src: "https://static.copus.io/images/client/202507/test/5c802fdcaaea4e7bbed37393eec0d5ba.png", - }), - $el("span", { style: { marginLeft: "5px", color: "#fff" } }, [ - "Not Rated", - ]), - ] - ), - $el( - "p", - { style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } }, - ["No age rating provided."] - ), - ]); - - // Message Section - this.message = $el( - "div", - { - style: { - color: "#ff3d00", - textAlign: "center", - padding: "10px", - fontSize: "20px", - }, - }, - [] - ); - - this.shareButton = $el("button", { - type: "submit", - textContent: "Share", - style: buttonStyle, - onclick: () => { - this.handleShareButtonClick(); - }, - }); - - // Share and Close Buttons - const buttonsSection = $el( - "div", - { - style: { - textAlign: "right", - marginTop: "20px", - display: "flex", - justifyContent: "space-between", - }, - }, - [ - $el("button", { - type: "button", - textContent: "Close", - style: { - ...buttonStyle, - backgroundColor: undefined, - }, - onclick: () => { - this.close(); - }, - }), - this.shareButton, - ] - ); - - // Composing the full layout - const layout = [ - headerSection, - linkSection, - accountSection, - outputUploadSection, - this.outputsSection, - additionalInputsSection, - SubtitleSection, - DescriptionSection, - // contestSection, - blockChainSection_lock, - contentRatingSection, - blockChainSection, - this.message, - buttonsSection, - ]; - - return layout; - } - /** - * api - * @param {url} path - * @param {params} options - * @param {statusText} statusText - * @returns - */ - async fetchApi(path, options, statusText) { - if (statusText) { - this.message.textContent = statusText; - } - const fullPath = new URL(API_ENDPOINT + path); - const response = await fetch(fullPath, options); - if (!response.ok) { - throw new Error(response.statusText); - } - if (statusText) { - this.message.textContent = ""; - } - const data = await response.json(); - return { - ok: response.ok, - statusText: response.statusText, - status: response.status, - data, - }; - } - /** - * @param {file} uploadFile - */ - async uploadThumbnail(uploadFile, type) { - const form = new FormData(); - form.append("file", uploadFile); - form.append("apiToken", this.keyInput.value); - try { - const res = await this.fetchApi( - `/client/common/opus/uploadImage`, - { - method: "POST", - body: form, - }, - "Uploading thumbnail..." - ); - if (res.status && res.data.status && res.data) { - const { data } = res.data; - if (type) { - this.allFilesImages.push({ - url: data, - }); - } - this.uploadedImages.push({ - url: data, - }); - } else { - throw new Error( - "make sure your API key is correct and try again later" - ); - } - } catch (e) { - if (e?.response?.status === 413) { - throw new Error("File size is too large (max 20MB)"); - } else { - throw new Error("Error uploading thumbnail: " + e.message); - } - } - } - - async handleShareButtonClick() { - this.message.textContent = ""; - try { - this.shareButton.disabled = true; - this.shareButton.textContent = "Sharing..."; - await this.share(); - } catch (e) { - customAlert(e.message); - } - this.shareButton.disabled = false; - this.shareButton.textContent = "Share"; - } - /** - * share - * @param {string} title - * @param {string} subtitle - * @param {string} content - * @param {boolean} storeOnChain - * @param {string} coverUrl - * @param {string[]} imageUrls - * @param {string} apiToken - */ - async share() { - const prompt = await app.graphToPrompt(); - const workflowJSON = prompt["workflow"]; - const form_values = { - title: this.TitleInput.value, - subTitle: this.SubTitleInput.value, - content: this.descriptionInput.value, - storeOnChain: this.radioButtonsCheck.checked ? true : false, - lockState: this.radioButtonsCheck_lock.checked ? 2 : 0, - unlockPrice: this.LockInput.value, - rating: this.ratingRadioButtonsCheck0.checked - ? 0 - : this.ratingRadioButtonsCheck1.checked - ? 1 - : this.ratingRadioButtonsCheck2.checked - ? 2 - : -1, - }; - - if (!this.keyInput.value) { - throw new Error("API key is required"); - } - - if (!this.uploadImagesInput.files[0] && !this.selectedFile) { - throw new Error("Thumbnail is required"); - } - - if (!form_values.title) { - throw new Error("Title is required"); - } - - if (this.radioButtonsCheck_lock.checked) { - if (!this.LockInput.value) { - throw new Error("Price is required"); - } - } - - if (!this.uploadedImages.length) { - if (this.selectedFile) { - await this.uploadThumbnail(this.selectedFile); - } else { - for (const file of this.uploadImagesInput.files) { - try { - await this.uploadThumbnail(file); - } catch (e) { - this.uploadedImages = []; - throw new Error(e.message); - } - } - - if (this.uploadImagesInput.files.length === 0) { - throw new Error("No thumbnail uploaded"); - } - } - } - if (this.allFiles.length > 0) { - for (const file of this.allFiles) { - try { - await this.uploadThumbnail(file, true); - } catch (e) { - this.allFilesImages = []; - throw new Error(e.message); - } - } - } - try { - const res = await this.fetchApi( - "/client/common/opus/shareFromComfyUI", - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - workflowJson: workflowJSON, - apiToken: this.keyInput.value, - coverUrl: this.uploadedImages[0].url, - imageUrls: this.allFilesImages.map((image) => image.url), - ...form_values, - }), - }, - "Uploading workflow..." - ); - - if (res.status && res.data.status && res.data) { - localStorage.setItem("copus_token", this.keyInput.value); - const { data } = res.data; - if (data) { - const url = `${DEFAULT_HOMEPAGE_URL}/work/${data}`; - this.message.innerHTML = `Workflow has been shared successfully. Click here to view it.`; - this.previewImage.src = ""; - this.previewImage.style.display = "none"; - this.uploadedImages = []; - this.allFilesImages = []; - this.allFiles = []; - this.TitleInput.value = ""; - this.SubTitleInput.value = ""; - this.descriptionInput.value = ""; - this.selectedFile = null; - } - } - } catch (e) { - throw new Error("Error sharing workflow: " + e.message); - } - } - - async fetchImageBlob(url) { - const response = await fetch(url); - const blob = await response.blob(); - return blob; - } - - async show({ potential_outputs, potential_output_nodes } = {}) { - // Sort `potential_output_nodes` by node ID to make the order always - // consistent, but we should also keep `potential_outputs` in the same - // order as `potential_output_nodes`. - const potential_output_to_order = {}; - potential_output_nodes.forEach((node, index) => { - if (node.id in potential_output_to_order) { - potential_output_to_order[node.id][1].push(potential_outputs[index]); - } else { - potential_output_to_order[node.id] = [node, [potential_outputs[index]]]; - } - }); - // Sort the object `potential_output_to_order` by key (node ID) - const sorted_potential_output_to_order = Object.fromEntries( - Object.entries(potential_output_to_order).sort( - (a, b) => a[0].id - b[0].id - ) - ); - const sorted_potential_outputs = []; - const sorted_potential_output_nodes = []; - for (const [key, value] of Object.entries( - sorted_potential_output_to_order - )) { - sorted_potential_output_nodes.push(value[0]); - sorted_potential_outputs.push(...value[1]); - } - potential_output_nodes = sorted_potential_output_nodes; - potential_outputs = sorted_potential_outputs; - const apiToken = localStorage.getItem("copus_token"); - this.message.innerHTML = ""; - this.message.textContent = ""; - this.element.style.display = "block"; - this.previewImage.src = ""; - this.previewImage.style.display = "none"; - this.keyInput.value = apiToken != null ? apiToken : ""; - this.uploadedImages = []; - this.allFilesImages = []; - this.allFiles = []; - // If `selectedNodeId` is provided, we will select the corresponding radio - // button for the node. In addition, we move the selected radio button to - // the top of the list. - if (this.selectedNodeId) { - const index = potential_output_nodes.findIndex( - (node) => node.id === this.selectedNodeId - ); - if (index >= 0) { - this.selectedOutputIndex = index; - } - } - - this.radioButtons = []; - const new_radio_buttons = $el( - "div", - { - id: "selectOutput-Options", - style: { - "overflow-y": "scroll", - "max-height": "200px", - display: "grid", - "grid-template-columns": "repeat(auto-fit, minmax(100px, 1fr))", - "grid-template-rows": "auto", - "grid-column-gap": "10px", - "grid-row-gap": "10px", - "margin-bottom": "10px", - padding: "10px", - "border-radius": "8px", - "box-shadow": "0 2px 4px rgba(0, 0, 0, 0.05)", - "background-color": "var(--bg-color)", - }, - }, - potential_outputs.map((output, index) => { - const { node_id } = output; - const radio_button = $el( - "input", - { - type: "radio", - name: "selectOutputImages", - value: index, - required: index === 0, - }, - [] - ); - let radio_button_img; - let filename; - if (output.type === "image" || output.type === "temp") { - radio_button_img = $el( - "img", - { - src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, - style: { - width: "100px", - height: "100px", - objectFit: "cover", - borderRadius: "5px", - }, - }, - [] - ); - filename = output.image.filename; - } else if (output.type === "output") { - radio_button_img = $el( - "img", - { - src: output.output.value, - style: { - width: "auto", - height: "100px", - objectFit: "cover", - borderRadius: "5px", - }, - }, - [] - ); - filename = output.filename; - } else { - // unsupported output type - // this should never happen - radio_button_img = $el( - "img", - { - src: "", - style: { width: "auto", height: "100px" }, - }, - [] - ); - } - const radio_button_text = $el( - "span", - { - style: { - color: "gray", - display: "block", - fontSize: "12px", - overflowX: "hidden", - textOverflow: "ellipsis", - textWrap: "nowrap", - maxWidth: "100px", - }, - }, - [output.title] - ); - const node_id_chip = $el( - "span", - { - style: { - color: "#FBFBFD", - display: "block", - backgroundColor: "rgba(0, 0, 0, 0.5)", - fontSize: "12px", - overflowX: "hidden", - padding: "2px 3px", - textOverflow: "ellipsis", - textWrap: "nowrap", - maxWidth: "100px", - position: "absolute", - top: "3px", - left: "3px", - borderRadius: "3px", - }, - }, - [`Node: ${node_id}`] - ); - radio_button.style.color = "var(--fg-color)"; - radio_button.checked = this.selectedOutputIndex === index; - - radio_button.onchange = async () => { - this.selectedOutputIndex = parseInt(radio_button.value); - - // Remove the "checked" class from all radio buttons - this.radioButtons.forEach((ele) => { - ele.parentElement.classList.remove("checked"); - }); - radio_button.parentElement.classList.add("checked"); - - this.fetchImageBlob(radio_button_img.src).then((blob) => { - const file = new File([blob], filename, { - type: blob.type, - }); - this.previewImage.src = radio_button_img.src; - this.previewImage.style.display = "block"; - this.selectedFile = file; - }); - - // Add the opacity style toggle here to indicate that they only need - // to upload one image or choose one from the outputs. - this.outputsSection.style.opacity = 1; - this.uploadImagesInput.style.opacity = 0.35; - }; - - if (radio_button.checked) { - this.fetchImageBlob(radio_button_img.src).then((blob) => { - const file = new File([blob], filename, { - type: blob.type, - }); - this.previewImage.src = radio_button_img.src; - this.previewImage.style.display = "block"; - this.selectedFile = file; - }); - // Add the opacity style toggle here to indicate that they only need - // to upload one image or choose one from the outputs. - this.outputsSection.style.opacity = 1; - this.uploadImagesInput.style.opacity = 0.35; - } - this.radioButtons.push(radio_button); - let src = ""; - if (output.type === "image" || output.type === "temp") { - filename = output.image.filename; - src = `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`; - } else if (output.type === "output") { - src = output.output.value; - filename = output.filename; - } - if (src) { - this.fetchImageBlob(src).then((blob) => { - const file = new File([blob], filename, { - type: blob.type, - }); - this.allFiles.push(file); - }); - } - return $el( - `label.output_label${radio_button.checked ? ".checked" : ""}`, - { - style: { - display: "flex", - flexDirection: "column", - alignItems: "center", - justifyContent: "center", - marginBottom: "10px", - cursor: "pointer", - position: "relative", - }, - }, - [radio_button_img, radio_button_text, radio_button, node_id_chip] - ); - }) - ); - - const header = $el( - "p", - { - textContent: - this.radioButtons.length === 0 - ? "Queue Prompt to see the outputs" - : "Or choose one from the outputs (scroll to see all)", - size: 2, - color: "white", - style: { - color: "white", - margin: "0 0 5px 0", - fontSize: "12px", - }, - }, - [] - ); - this.outputsSection.innerHTML = ""; - this.outputsSection.appendChild(header); - this.outputsSection.appendChild(new_radio_buttons); - } -} diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js deleted file mode 100644 index 1c96a8c7..00000000 --- a/js/comfyui-share-openart.js +++ /dev/null @@ -1,746 +0,0 @@ -import {app} from "../../scripts/app.js"; -import {api} from "../../scripts/api.js"; -import {ComfyDialog, $el} from "../../scripts/ui.js"; -import { customAlert } from "./common.js"; - -const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key"; -const DEFAULT_HOMEPAGE_URL = "https://openart.ai/workflows/dev?developer=true"; -//const DEFAULT_HOMEPAGE_URL = "http://localhost:8080/workflows/dev?developer=true"; - -const API_ENDPOINT = "https://openart.ai/api"; -//const API_ENDPOINT = "http://localhost:8080/api"; - -const style = ` - .openart-share-dialog a { - color: #f8f8f8; - } - .openart-share-dialog a:hover { - color: #007bff; - } - .output_label { - border: 5px solid transparent; - } - .output_label:hover { - border: 5px solid #59E8C6; - } - .output_label.checked { - border: 5px solid #59E8C6; - } -`; - -// Shared component styles -const sectionStyle = { - marginBottom: 0, - padding: 0, - borderRadius: "8px", - boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)", - display: "flex", - flexDirection: "column", - justifyContent: "center", -}; - -export class OpenArtShareDialog extends ComfyDialog { - static instance = null; - - constructor() { - super(); - $el("style", { - textContent: style, - parent: document.head, - }); - this.element = $el( - "div.comfy-modal.openart-share-dialog", - { - parent: document.body, - style: { - "overflow-y": "auto", - }, - }, - [$el("div.comfy-modal-content", {}, [...this.createButtons()])] - ); - this.selectedOutputIndex = 0; - this.selectedNodeId = null; - this.uploadedImages = []; - this.selectedFile = null; - } - - async readKey() { - let key = "" - try { - key = await api.fetchApi(`/manager/get_openart_auth`) - .then(response => response.json()) - .then(data => { - return data.openart_key; - }) - .catch(error => { - // console.log(error); - }); - } catch (error) { - // console.log(error); - } - return key || ""; - } - - async saveKey(value) { - await api.fetchApi(`/manager/set_openart_auth`, { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - openart_key: value - }) - }); - } - - createButtons() { - const inputStyle = { - display: "block", - minWidth: "500px", - width: "100%", - padding: "10px", - margin: "10px 0", - borderRadius: "4px", - border: "1px solid #ddd", - boxSizing: "border-box", - }; - - const hyperLinkStyle = { - display: "block", - marginBottom: "15px", - fontWeight: "bold", - fontSize: "14px", - }; - - const labelStyle = { - color: "#f8f8f8", - display: "block", - margin: "10px 0 0 0", - fontWeight: "bold", - textDecoration: "none", - }; - - const buttonStyle = { - padding: "10px 80px", - margin: "10px 5px", - borderRadius: "4px", - border: "none", - cursor: "pointer", - color: "#fff", - backgroundColor: "#007bff", - }; - - // upload images input - this.uploadImagesInput = $el("input", { - type: "file", - multiple: false, - style: inputStyle, - accept: "image/*", - }); - - this.uploadImagesInput.addEventListener("change", async (e) => { - const file = e.target.files[0]; - if (!file) { - this.previewImage.src = ""; - this.previewImage.style.display = "none"; - return; - } - const reader = new FileReader(); - reader.onload = async (e) => { - const imgData = e.target.result; - this.previewImage.src = imgData; - this.previewImage.style.display = "block"; - this.selectedFile = null - // Once user uploads an image, we uncheck all radio buttons - this.radioButtons.forEach((ele) => { - ele.checked = false; - ele.parentElement.classList.remove("checked"); - }); - - // Add the opacity style toggle here to indicate that they only need - // to upload one image or choose one from the outputs. - this.outputsSection.style.opacity = 0.35; - this.uploadImagesInput.style.opacity = 1; - }; - reader.readAsDataURL(file); - }); - - // preview image - this.previewImage = $el("img", { - src: "", - style: { - width: "100%", - maxHeight: "100px", - objectFit: "contain", - display: "none", - marginTop: '10px', - }, - }); - - this.keyInput = $el("input", { - type: "password", - placeholder: "Copy & paste your API key", - style: inputStyle, - }); - this.NameInput = $el("input", { - type: "text", - placeholder: "Title (required)", - style: inputStyle, - }); - this.descriptionInput = $el("textarea", { - placeholder: "Description (optional)", - style: { - ...inputStyle, - minHeight: "100px", - }, - }); - - // Header Section - const headerSection = $el("h3", { - textContent: "Share your workflow to OpenArt", - size: 3, - color: "white", - style: { - 'text-align': 'center', - color: 'var(--input-text)', - margin: '0 0 10px 0', - } - }); - - // LinkSection - this.communityLink = $el("a", { - style: hyperLinkStyle, - href: DEFAULT_HOMEPAGE_URL, - target: "_blank" - }, ["👉 Check out thousands of workflows shared from the community"]) - this.getAPIKeyLink = $el("a", { - style: { - ...hyperLinkStyle, - color: "#59E8C6" - }, - href: DEFAULT_HOMEPAGE_URL, - target: "_blank" - }, ["👉 Get your API key here"]) - const linkSection = $el( - "div", - { - style: { - marginTop: "10px", - display: "flex", - flexDirection: "column", - }, - }, - [ - this.communityLink, - this.getAPIKeyLink, - ] - ); - - // Account Section - const accountSection = $el("div", {style: sectionStyle}, [ - $el("label", {style: labelStyle}, ["1️⃣ OpenArt API Key"]), - this.keyInput, - ]); - - // Output Upload Section - const outputUploadSection = $el("div", {style: sectionStyle}, [ - $el("label", { - style: { - ...labelStyle, - margin: "10px 0 0 0" - } - }, ["2️⃣ Image/Thumbnail (Required)"]), - this.previewImage, - this.uploadImagesInput, - ]); - - // Outputs Section - this.outputsSection = $el("div", { - id: "selectOutputs", - }, []); - - // Additional Inputs Section - const additionalInputsSection = $el("div", {style: sectionStyle}, [ - $el("label", {style: labelStyle}, ["3️⃣ Workflow Information"]), - this.NameInput, - this.descriptionInput, - ]); - - // OpenArt Contest Section - /* - this.joinContestCheckbox = $el("input", { - type: 'checkbox', - id: "join_contest"s - }, []) - this.joinContestDescription = $el("a", { - style: { - ...hyperLinkStyle, - display: 'inline-block', - color: "#59E8C6", - fontSize: '12px', - marginLeft: '10px', - marginBottom: 0, - }, - href: "https://contest.openart.ai/", - target: "_blank" - }, ["🏆 I'm participating in the OpenArt workflow contest"]) - this.joinContestLabel = $el("label", { - style: { - display: 'flex', - alignItems: 'center', - cursor: 'pointer', - } - }, [this.joinContestCheckbox, this.joinContestDescription]) - const contestSection = $el("div", {style: sectionStyle}, [ - this.joinContestLabel, - ]); - */ - - // Message Section - this.message = $el( - "div", - { - style: { - color: "#ff3d00", - textAlign: "center", - padding: "10px", - fontSize: "20px", - }, - }, - [] - ); - - this.shareButton = $el("button", { - type: "submit", - textContent: "Share", - style: buttonStyle, - onclick: () => { - this.handleShareButtonClick(); - }, - }); - - // Share and Close Buttons - const buttonsSection = $el( - "div", - { - style: { - textAlign: "right", - marginTop: "20px", - display: "flex", - justifyContent: "space-between", - }, - }, - [ - $el("button", { - type: "button", - textContent: "Close", - style: { - ...buttonStyle, - backgroundColor: undefined, - }, - onclick: () => { - this.close(); - }, - }), - this.shareButton, - ] - ); - - // Composing the full layout - const layout = [ - headerSection, - linkSection, - accountSection, - outputUploadSection, - this.outputsSection, - additionalInputsSection, - // contestSection, - this.message, - buttonsSection, - ]; - - return layout; - } - - async fetchApi(path, options, statusText) { - if (statusText) { - this.message.textContent = statusText; - } - const addSearchParams = (url, params = {}) => - new URL( - `${url.origin}${url.pathname}?${new URLSearchParams([ - ...Array.from(url.searchParams.entries()), - ...Object.entries(params), - ])}` - ); - - const fullPath = addSearchParams(new URL(API_ENDPOINT + path), { - workflow_api_key: this.keyInput.value, - }); - - const response = await fetch(fullPath, options); - - if (!response.ok) { - throw new Error(response.statusText); - } - - if (statusText) { - this.message.textContent = ""; - } - const data = await response.json(); - return { - ok: response.ok, - statusText: response.statusText, - status: response.status, - data, - }; - } - - async uploadThumbnail(uploadFile) { - const form = new FormData(); - form.append("file", uploadFile); - try { - const res = await this.fetchApi( - `/workflows/upload_thumbnail`, - { - method: "POST", - body: form, - }, - "Uploading thumbnail..." - ); - - if (res.ok && res.data) { - const {image_url, width, height} = res.data; - this.uploadedImages.push({ - url: image_url, - width, - height, - }); - } - } catch (e) { - if (e?.response?.status === 413) { - throw new Error("File size is too large (max 20MB)"); - } else { - throw new Error("Error uploading thumbnail: " + e.message); - } - } - } - - async handleShareButtonClick() { - this.message.textContent = ""; - await this.saveKey(this.keyInput.value); - try { - this.shareButton.disabled = true; - this.shareButton.textContent = "Sharing..."; - await this.share(); - } catch (e) { - customAlert(e.message); - } - this.shareButton.disabled = false; - this.shareButton.textContent = "Share"; - } - - async share() { - const prompt = await app.graphToPrompt(); - const workflowJSON = prompt["workflow"]; - const workflowAPIJSON = prompt["output"]; - const form_values = { - name: this.NameInput.value, - description: this.descriptionInput.value, - }; - - if (!this.keyInput.value) { - throw new Error("API key is required"); - } - - if (!this.uploadImagesInput.files[0] && !this.selectedFile) { - throw new Error("Thumbnail is required"); - } - - if (!form_values.name) { - throw new Error("Title is required"); - } - - const current_snapshot = await api.fetchApi(`/snapshot/get_current`) - .then(response => response.json()) - .catch(error => { - // console.log(error); - }); - - - if (!this.uploadedImages.length) { - if (this.selectedFile) { - await this.uploadThumbnail(this.selectedFile); - } else { - for (const file of this.uploadImagesInput.files) { - try { - await this.uploadThumbnail(file); - } catch (e) { - this.uploadedImages = []; - throw new Error(e.message); - } - } - - if (this.uploadImagesInput.files.length === 0) { - throw new Error("No thumbnail uploaded"); - } - } - } - - // const join_contest = this.joinContestCheckbox.checked; - - try { - const response = await this.fetchApi( - "/workflows/publish", - { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - workflow_json: workflowJSON, - upload_images: this.uploadedImages, - form_values, - advanced_config: { - workflow_api_json: workflowAPIJSON, - snapshot: current_snapshot, - }, - // join_contest, - }), - }, - "Uploading workflow..." - ); - - if (response.ok) { - const {workflow_id} = response.data; - if (workflow_id) { - const url = `https://openart.ai/workflows/-/-/${workflow_id}`; - this.message.innerHTML = `Workflow has been shared successfully. Click here to view it.`; - this.previewImage.src = ""; - this.previewImage.style.display = "none"; - this.uploadedImages = []; - this.NameInput.value = ""; - this.descriptionInput.value = ""; - this.radioButtons.forEach((ele) => { - ele.checked = false; - ele.parentElement.classList.remove("checked"); - }); - this.selectedOutputIndex = 0; - this.selectedNodeId = null; - this.selectedFile = null; - } - } - } catch (e) { - throw new Error("Error sharing workflow: " + e.message); - } - } - - async fetchImageBlob(url) { - const response = await fetch(url); - const blob = await response.blob(); - return blob; - } - - async show({potential_outputs, potential_output_nodes} = {}) { - // Sort `potential_output_nodes` by node ID to make the order always - // consistent, but we should also keep `potential_outputs` in the same - // order as `potential_output_nodes`. - const potential_output_to_order = {}; - potential_output_nodes.forEach((node, index) => { - if (node.id in potential_output_to_order) { - potential_output_to_order[node.id][1].push(potential_outputs[index]); - } else { - potential_output_to_order[node.id] = [node, [potential_outputs[index]]]; - } - }) - // Sort the object `potential_output_to_order` by key (node ID) - const sorted_potential_output_to_order = Object.fromEntries( - Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id) - ); - const sorted_potential_outputs = [] - const sorted_potential_output_nodes = [] - for (const [key, value] of Object.entries(sorted_potential_output_to_order)) { - sorted_potential_output_nodes.push(value[0]); - sorted_potential_outputs.push(...value[1]); - } - potential_output_nodes = sorted_potential_output_nodes; - potential_outputs = sorted_potential_outputs; - - this.message.innerHTML = ""; - this.message.textContent = ""; - this.element.style.display = "block"; - this.previewImage.src = ""; - this.previewImage.style.display = "none"; - const key = await this.readKey(); - this.keyInput.value = key; - this.uploadedImages = []; - - // If `selectedNodeId` is provided, we will select the corresponding radio - // button for the node. In addition, we move the selected radio button to - // the top of the list. - if (this.selectedNodeId) { - const index = potential_output_nodes.findIndex(node => node.id === this.selectedNodeId); - if (index >= 0) { - this.selectedOutputIndex = index; - } - } - - this.radioButtons = []; - const new_radio_buttons = $el("div", - { - id: "selectOutput-Options", - style: { - 'overflow-y': 'scroll', - 'max-height': '200px', - - 'display': 'grid', - 'grid-template-columns': 'repeat(auto-fit, minmax(100px, 1fr))', - 'grid-template-rows': 'auto', - 'grid-column-gap': '10px', - 'grid-row-gap': '10px', - 'margin-bottom': '10px', - 'padding': '10px', - 'border-radius': '8px', - 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.05)', - 'background-color': 'var(--bg-color)', - } - }, - potential_outputs.map((output, index) => { - const {node_id} = output; - const radio_button = $el("input", { - type: 'radio', - name: "selectOutputImages", - value: index, - required: index === 0 - }, []) - let radio_button_img; - let filename; - if (output.type === "image" || output.type === "temp") { - radio_button_img = $el("img", { - src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, - style: { - width: "100px", - height: "100px", - objectFit: "cover", - borderRadius: "5px" - } - }, []); - filename = output.image.filename - } else if (output.type === "output") { - radio_button_img = $el("img", { - src: output.output.value, - style: { - width: "auto", - height: "100px", - objectFit: "cover", - borderRadius: "5px" - } - }, []); - filename = output.filename - } else { - // unsupported output type - // this should never happen - // TODO - radio_button_img = $el("img", { - src: "", - style: {width: "auto", height: "100px"} - }, []); - } - const radio_button_text = $el("span", { - style: { - color: 'gray', - display: 'block', - fontSize: '12px', - overflowX: 'hidden', - textOverflow: 'ellipsis', - textWrap: 'nowrap', - maxWidth: '100px', - } - }, [output.title]) - const node_id_chip = $el("span", { - style: { - color: '#FBFBFD', - display: 'block', - backgroundColor: 'rgba(0, 0, 0, 0.5)', - fontSize: '12px', - overflowX: 'hidden', - padding: '2px 3px', - textOverflow: 'ellipsis', - textWrap: 'nowrap', - maxWidth: '100px', - position: 'absolute', - top: '3px', - left: '3px', - borderRadius: '3px', - } - }, [`Node: ${node_id}`]) - radio_button.style.color = "var(--fg-color)"; - radio_button.checked = this.selectedOutputIndex === index; - - radio_button.onchange = async () => { - this.selectedOutputIndex = parseInt(radio_button.value); - - // Remove the "checked" class from all radio buttons - this.radioButtons.forEach((ele) => { - ele.parentElement.classList.remove("checked"); - }); - radio_button.parentElement.classList.add("checked"); - - this.fetchImageBlob(radio_button_img.src).then((blob) => { - const file = new File([blob], filename, { - type: blob.type, - }); - this.previewImage.src = radio_button_img.src; - this.previewImage.style.display = "block"; - this.selectedFile = file; - }) - - // Add the opacity style toggle here to indicate that they only need - // to upload one image or choose one from the outputs. - this.outputsSection.style.opacity = 1; - this.uploadImagesInput.style.opacity = 0.35; - }; - - if (radio_button.checked) { - this.fetchImageBlob(radio_button_img.src).then((blob) => { - const file = new File([blob], filename, { - type: blob.type, - }); - this.previewImage.src = radio_button_img.src; - this.previewImage.style.display = "block"; - this.selectedFile = file; - }) - // Add the opacity style toggle here to indicate that they only need - // to upload one image or choose one from the outputs. - this.outputsSection.style.opacity = 1; - this.uploadImagesInput.style.opacity = 0.35; - } - - this.radioButtons.push(radio_button); - - return $el(`label.output_label${radio_button.checked ? '.checked' : ''}`, { - style: { - display: "flex", - flexDirection: "column", - alignItems: "center", - justifyContent: "center", - marginBottom: "10px", - cursor: "pointer", - position: 'relative', - } - }, [radio_button_img, radio_button_text, radio_button, node_id_chip]); - }) - ); - - const header = - $el("p", { - textContent: this.radioButtons.length === 0 ? "Queue Prompt to see the outputs" : "Or choose one from the outputs (scroll to see all)", - size: 2, - color: "white", - style: { - color: 'var(--input-text)', - margin: '0 0 5px 0', - fontSize: '12px', - }, - }, []) - this.outputsSection.innerHTML = ""; - this.outputsSection.appendChild(header); - this.outputsSection.appendChild(new_radio_buttons); - } -} diff --git a/js/comfyui-share-youml.js b/js/comfyui-share-youml.js deleted file mode 100644 index efd8916f..00000000 --- a/js/comfyui-share-youml.js +++ /dev/null @@ -1,569 +0,0 @@ -import {app} from "../../scripts/app.js"; -import {api} from "../../scripts/api.js"; -import {ComfyDialog, $el} from "../../scripts/ui.js"; -import { customAlert } from "./common.js"; - -const BASE_URL = "https://youml.com"; -//const BASE_URL = "http://localhost:3000"; -const DEFAULT_HOMEPAGE_URL = `${BASE_URL}/?from=comfyui`; -const TOKEN_PAGE_URL = `${BASE_URL}/my-token`; -const API_ENDPOINT = `${BASE_URL}/api`; - -const style = ` - .youml-share-dialog { - overflow-y: auto; - } - .youml-share-dialog .dialog-header { - text-align: center; - color: white; - margin: 0 0 10px 0; - } - .youml-share-dialog .dialog-section { - margin-bottom: 0; - padding: 0; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - display: flex; - flex-direction: column; - justify-content: center; - } - .youml-share-dialog input, .youml-share-dialog textarea { - display: block; - min-width: 500px; - width: 100%; - padding: 10px; - margin: 10px 0; - border-radius: 4px; - border: 1px solid #ddd; - box-sizing: border-box; - } - .youml-share-dialog textarea { - color: var(--input-text); - background-color: var(--comfy-input-bg); - } - .youml-share-dialog .workflow-description { - min-height: 75px; - } - .youml-share-dialog label { - color: #f8f8f8; - display: block; - margin: 5px 0 0 0; - font-weight: bold; - text-decoration: none; - } - .youml-share-dialog .action-button { - padding: 10px 80px; - margin: 10px 5px; - border-radius: 4px; - border: none; - cursor: pointer; - } - .youml-share-dialog .share-button { - color: #fff; - background-color: #007bff; - } - .youml-share-dialog .close-button { - background-color: none; - } - .youml-share-dialog .action-button-panel { - text-align: right; - display: flex; - justify-content: space-between; - } - .youml-share-dialog .status-message { - color: #fd7909; - text-align: center; - padding: 5px; - font-size: 18px; - } - .youml-share-dialog .status-message a { - color: white; - } - .youml-share-dialog .output-panel { - overflow: auto; - max-height: 180px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); - grid-template-rows: auto; - grid-column-gap: 10px; - grid-row-gap: 10px; - margin-bottom: 10px; - padding: 10px; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - background-color: var(--bg-color); - } - .youml-share-dialog .output-panel .output-image { - width: 100px; - height: 100px; - objectFit: cover; - borderRadius: 5px; - } - - .youml-share-dialog .output-panel .radio-button { - color:var(--fg-color); - } - .youml-share-dialog .output-panel .radio-text { - color: gray; - display: block; - font-size: 12px; - overflow-x: hidden; - text-overflow: ellipsis; - text-wrap: nowrap; - max-width: 100px; - } - .youml-share-dialog .output-panel .node-id { - color: #FBFBFD; - display: block; - background-color: rgba(0, 0, 0, 0.5); - font-size: 12px; - overflow-x: hidden; - padding: 2px 3px; - text-overflow: ellipsis; - text-wrap: nowrap; - max-width: 100px; - position: absolute; - top: 3px; - left: 3px; - border-radius: 3px; - } - .youml-share-dialog .output-panel .output-label { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-bottom: 10px; - cursor: pointer; - position: relative; - border: 5px solid transparent; - } - .youml-share-dialog .output-panel .output-label:hover { - border: 5px solid #007bff; - } - .youml-share-dialog .output-panel .output-label.checked { - border: 5px solid #007bff; - } - .youml-share-dialog .missing-output-message{ - color: #fd7909; - font-size: 16px; - margin-bottom:10px - } - .youml-share-dialog .select-output-message{ - color: white; - margin-bottom:5px - } -`; - -export class YouMLShareDialog extends ComfyDialog { - static instance = null; - - constructor() { - super(); - $el("style", { - textContent: style, - parent: document.head, - }); - this.element = $el( - "div.comfy-modal.youml-share-dialog", - { - parent: document.body, - }, - [$el("div.comfy-modal-content", {}, [...this.createLayout()])] - ); - this.selectedOutputIndex = 0; - this.selectedNodeId = null; - this.uploadedImages = []; - this.selectedFile = null; - } - - async loadToken() { - let key = "" - try { - const response = await api.fetchApi(`/manager/youml/settings`) - const settings = await response.json() - return settings.token - } catch (error) { - } - return key || ""; - } - - async saveToken(value) { - await api.fetchApi(`/manager/youml/settings`, { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - token: value - }) - }); - } - - createLayout() { - // Header Section - const headerSection = $el("h3.dialog-header", { - textContent: "Share your workflow to YouML.com", - size: 3, - }); - - // Workflow Info Section - this.nameInput = $el("input", { - type: "text", - placeholder: "Name (required)", - }); - this.descriptionInput = $el("textarea.workflow-description", { - placeholder: "Description (optional, markdown supported)", - }); - const workflowMetadata = $el("div.dialog-section", {}, [ - $el("label", {}, ["Workflow info"]), - this.nameInput, - this.descriptionInput, - ]); - - // Outputs Section - this.outputsSection = $el("div.dialog-section", { - id: "selectOutputs", - }, []); - - const outputUploadSection = $el("div.dialog-section", {}, [ - $el("label", {}, ["Thumbnail"]), - this.outputsSection, - ]); - - // API Token Section - this.apiTokenInput = $el("input", { - type: "password", - placeholder: "Copy & paste your API token", - }); - const getAPITokenButton = $el("button", { - href: DEFAULT_HOMEPAGE_URL, - target: "_blank", - onclick: () => window.open(TOKEN_PAGE_URL, "_blank"), - }, ["Get your API Token"]) - - const apiTokenSection = $el("div.dialog-section", {}, [ - $el("label", {}, ["YouML API Token"]), - this.apiTokenInput, - getAPITokenButton, - ]); - - // Message Section - this.message = $el("div.status-message", {}, []); - - // Share and Close Buttons - this.shareButton = $el("button.action-button.share-button", { - type: "submit", - textContent: "Share", - onclick: () => { - this.handleShareButtonClick(); - }, - }); - - const buttonsSection = $el( - "div.action-button-panel", - {}, - [ - $el("button.action-button.close-button", { - type: "button", - textContent: "Close", - onclick: () => { - this.close(); - }, - }), - this.shareButton, - ] - ); - - // Composing the full layout - const layout = [ - headerSection, - workflowMetadata, - outputUploadSection, - apiTokenSection, - this.message, - buttonsSection, - ]; - - return layout; - } - - async fetchYoumlApi(path, options, statusText) { - if (statusText) { - this.message.textContent = statusText; - } - - const fullPath = new URL(API_ENDPOINT + path) - - const fetchOptions = Object.assign({}, options) - - fetchOptions.headers = { - ...fetchOptions.headers, - "Authorization": `Bearer ${this.apiTokenInput.value}`, - "User-Agent": "ComfyUI-Manager-Youml/1.0.0", - } - - const response = await fetch(fullPath, fetchOptions); - - if (!response.ok) { - throw new Error(response.statusText + " " + (await response.text())); - } - - if (statusText) { - this.message.textContent = ""; - } - const data = await response.json(); - return { - ok: response.ok, - statusText: response.statusText, - status: response.status, - data, - }; - } - - async uploadThumbnail(uploadFile, recipeId) { - const form = new FormData(); - form.append("file", uploadFile, uploadFile.name); - try { - const res = await this.fetchYoumlApi( - `/v1/comfy/recipes/${recipeId}/thumbnail`, - { - method: "POST", - body: form, - }, - "Uploading thumbnail..." - ); - - } catch (e) { - if (e?.response?.status === 413) { - throw new Error("File size is too large (max 20MB)"); - } else { - throw new Error("Error uploading thumbnail: " + e.message); - } - } - } - - async handleShareButtonClick() { - this.message.textContent = ""; - await this.saveToken(this.apiTokenInput.value); - try { - this.shareButton.disabled = true; - this.shareButton.textContent = "Sharing..."; - await this.share(); - } catch (e) { - customAlert(e.message); - } finally { - this.shareButton.disabled = false; - this.shareButton.textContent = "Share"; - } - } - - async share() { - const prompt = await app.graphToPrompt(); - const workflowJSON = prompt["workflow"]; - const workflowAPIJSON = prompt["output"]; - const form_values = { - name: this.nameInput.value, - description: this.descriptionInput.value, - }; - - if (!this.apiTokenInput.value) { - throw new Error("API token is required"); - } - - if (!this.selectedFile) { - throw new Error("Thumbnail is required"); - } - - if (!form_values.name) { - throw new Error("Title is required"); - } - - - try { - let snapshotData = null; - try { - const snapshot = await api.fetchApi(`/snapshot/get_current`) - snapshotData = await snapshot.json() - } catch (e) { - console.error("Failed to get snapshot", e) - } - - const request = { - name: this.nameInput.value, - description: this.descriptionInput.value, - workflowUiJson: JSON.stringify(workflowJSON), - workflowApiJson: JSON.stringify(workflowAPIJSON), - } - - if (snapshotData) { - request.snapshotJson = JSON.stringify(snapshotData) - } - - const response = await this.fetchYoumlApi( - "/v1/comfy/recipes", - { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify(request), - }, - "Uploading workflow..." - ); - - if (response.ok) { - const {id, recipePageUrl, editorPageUrl} = response.data; - if (id) { - let messagePrefix = "Workflow has been shared." - if (this.selectedFile) { - try { - await this.uploadThumbnail(this.selectedFile, id); - } catch (e) { - console.error("Thumbnail upload failed: ", e); - messagePrefix = "Workflow has been shared, but thumbnail upload failed. You can create a thumbnail on YouML later." - } - } - this.message.innerHTML = `${messagePrefix} To turn your workflow into an interactive app, ` + - `visit it on YouML`; - - this.uploadedImages = []; - this.nameInput.value = ""; - this.descriptionInput.value = ""; - this.radioButtons.forEach((ele) => { - ele.checked = false; - ele.parentElement.classList.remove("checked"); - }); - this.selectedOutputIndex = 0; - this.selectedNodeId = null; - this.selectedFile = null; - } - } - } catch (e) { - throw new Error("Error sharing workflow: " + e.message); - } - } - - async fetchImageBlob(url) { - const response = await fetch(url); - const blob = await response.blob(); - return blob; - } - - async show(potentialOutputs, potentialOutputNodes) { - const potentialOutputsToOrder = {}; - potentialOutputNodes.forEach((node, index) => { - if (node.id in potentialOutputsToOrder) { - potentialOutputsToOrder[node.id][1].push(potentialOutputs[index]); - } else { - potentialOutputsToOrder[node.id] = [node, [potentialOutputs[index]]]; - } - }) - const sortedPotentialOutputsToOrder = Object.fromEntries( - Object.entries(potentialOutputsToOrder).sort((a, b) => a[0].id - b[0].id) - ); - const sortedPotentialOutputs = [] - const sortedPotentiaOutputNodes = [] - for (const [key, value] of Object.entries(sortedPotentialOutputsToOrder)) { - sortedPotentiaOutputNodes.push(value[0]); - sortedPotentialOutputs.push(...value[1]); - } - potentialOutputNodes = sortedPotentiaOutputNodes; - potentialOutputs = sortedPotentialOutputs; - - - // If `selectedNodeId` is provided, we will select the corresponding radio - // button for the node. In addition, we move the selected radio button to - // the top of the list. - if (this.selectedNodeId) { - const index = potentialOutputNodes.findIndex(node => node.id === this.selectedNodeId); - if (index >= 0) { - this.selectedOutputIndex = index; - } - } - - this.radioButtons = []; - const newRadioButtons = $el("div.output-panel", - { - id: "selectOutput-Options", - }, - potentialOutputs.map((output, index) => { - const {node_id: nodeId} = output; - const radioButton = $el("input.radio-button", { - type: "radio", - name: "selectOutputImages", - value: index, - required: index === 0 - }, []) - let radioButtonImage; - let filename; - if (output.type === "image" || output.type === "temp") { - radioButtonImage = $el("img.output-image", { - src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, - }, []); - filename = output.image.filename - } else if (output.type === "output") { - radioButtonImage = $el("img.output-image", { - src: output.output.value, - }, []); - filename = output.output.filename - } else { - radioButtonImage = $el("img.output-image", { - src: "", - }, []); - } - const radioButtonText = $el("span.radio-text", {}, [output.title]) - const nodeIdChip = $el("span.node-id", {}, [`Node: ${nodeId}`]) - radioButton.checked = this.selectedOutputIndex === index; - - radioButton.onchange = async () => { - this.selectedOutputIndex = parseInt(radioButton.value); - - // Remove the "checked" class from all radio buttons - this.radioButtons.forEach((ele) => { - ele.parentElement.classList.remove("checked"); - }); - radioButton.parentElement.classList.add("checked"); - - this.fetchImageBlob(radioButtonImage.src).then((blob) => { - const file = new File([blob], filename, { - type: blob.type, - }); - this.selectedFile = file; - }) - }; - - if (radioButton.checked) { - this.fetchImageBlob(radioButtonImage.src).then((blob) => { - const file = new File([blob], filename, { - type: blob.type, - }); - this.selectedFile = file; - }) - } - - this.radioButtons.push(radioButton); - - return $el(`label.output-label${radioButton.checked ? '.checked' : ''}`, {}, - [radioButtonImage, radioButtonText, radioButton, nodeIdChip]); - }) - ); - - let header; - if (this.radioButtons.length === 0) { - header = $el("div.missing-output-message", {textContent: "Queue Prompt to see the outputs and select a thumbnail"}, []) - } else { - header = $el("div.select-output-message", {textContent: "Choose one from the outputs (scroll to see all)"}, []) - } - - this.outputsSection.innerHTML = ""; - this.outputsSection.appendChild(header); - if (this.radioButtons.length > 0) { - this.outputsSection.appendChild(newRadioButtons); - } - - this.message.innerHTML = ""; - this.message.textContent = ""; - - const token = await this.loadToken(); - this.apiTokenInput.value = token; - this.uploadedImages = []; - - this.element.style.display = "block"; - } -} diff --git a/js/common.js b/js/common.js deleted file mode 100644 index b8193055..00000000 --- a/js/common.js +++ /dev/null @@ -1,670 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; -import { $el, ComfyDialog } from "../../scripts/ui.js"; -import { getBestPosition, getPositionStyle, getRect } from './popover-helper.js'; - - -function internalCustomConfirm(message, confirmMessage, cancelMessage) { - return new Promise((resolve) => { - // transparent bg - const modalOverlay = document.createElement('div'); - modalOverlay.style.position = 'fixed'; - modalOverlay.style.top = 0; - modalOverlay.style.left = 0; - modalOverlay.style.width = '100%'; - modalOverlay.style.height = '100%'; - modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; - modalOverlay.style.display = 'flex'; - modalOverlay.style.alignItems = 'center'; - modalOverlay.style.justifyContent = 'center'; - modalOverlay.style.zIndex = '1101'; - - // Modal window container (dark bg) - const modalDialog = document.createElement('div'); - modalDialog.style.backgroundColor = '#333'; - modalDialog.style.padding = '20px'; - modalDialog.style.borderRadius = '4px'; - modalDialog.style.maxWidth = '400px'; - modalDialog.style.width = '80%'; - modalDialog.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.5)'; - modalDialog.style.color = '#fff'; - - // Display message - const modalMessage = document.createElement('p'); - modalMessage.textContent = message; - modalMessage.style.margin = '0'; - modalMessage.style.padding = '0 0 20px'; - modalMessage.style.wordBreak = 'keep-all'; - - // Button container - const modalButtons = document.createElement('div'); - modalButtons.style.display = 'flex'; - modalButtons.style.justifyContent = 'flex-end'; - - // Confirm button (green) - const confirmButton = document.createElement('button'); - if(confirmMessage) - confirmButton.textContent = confirmMessage; - else - confirmButton.textContent = 'Confirm'; - confirmButton.style.marginLeft = '10px'; - confirmButton.style.backgroundColor = '#28a745'; // green - confirmButton.style.color = '#fff'; - confirmButton.style.border = 'none'; - confirmButton.style.padding = '6px 12px'; - confirmButton.style.borderRadius = '4px'; - confirmButton.style.cursor = 'pointer'; - confirmButton.style.fontWeight = 'bold'; - - // Cancel button (red) - const cancelButton = document.createElement('button'); - if(cancelMessage) - cancelButton.textContent = cancelMessage; - else - cancelButton.textContent = 'Cancel'; - - cancelButton.style.marginLeft = '10px'; - cancelButton.style.backgroundColor = '#dc3545'; // red - cancelButton.style.color = '#fff'; - cancelButton.style.border = 'none'; - cancelButton.style.padding = '6px 12px'; - cancelButton.style.borderRadius = '4px'; - cancelButton.style.cursor = 'pointer'; - cancelButton.style.fontWeight = 'bold'; - - const closeModal = () => { - document.body.removeChild(modalOverlay); - }; - - confirmButton.addEventListener('click', () => { - closeModal(); - resolve(true); - }); - - cancelButton.addEventListener('click', () => { - closeModal(); - resolve(false); - }); - - modalButtons.appendChild(confirmButton); - modalButtons.appendChild(cancelButton); - modalDialog.appendChild(modalMessage); - modalDialog.appendChild(modalButtons); - modalOverlay.appendChild(modalDialog); - document.body.appendChild(modalOverlay); - }); -} - -export function show_message(msg) { - app.ui.dialog.show(msg); - app.ui.dialog.element.style.zIndex = 1100; -} - -export async function handle403Response(res, defaultMessage) { - try { - const data = await res.json(); - if(data.error === 'comfyui_outdated') { - show_message('ComfyUI version is outdated.
Please update ComfyUI to use Manager normally.'); - } else { - show_message(defaultMessage || 'This action is not allowed with this security level configuration.'); - } - } catch { - show_message(defaultMessage || 'This action is not allowed with this security level configuration.'); - } -} - -export async function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -export async function customConfirm(message) { - try { - let res = await - window['app'].extensionManager.dialog - .confirm({ - title: 'Confirm', - message: message - }); - - return res; - } - catch { - let res = await internalCustomConfirm(message); - return res; - } -} - - -export function customAlert(message) { - try { - window['app'].extensionManager.toast.addAlert(message); - } - catch { - alert(message); - } -} - -export function infoToast(summary, message) { - try { - app.extensionManager.toast.add({ - severity: 'info', - summary: summary, - detail: message, - life: 3000 - }) - } - catch { - // do nothing - } -} - - -export async function customPrompt(title, message) { - try { - let res = await - window['app'].extensionManager.dialog - .prompt({ - title: title, - message: message - }); - - return res; - } - catch { - return prompt(title, message) - } -} - - -export async function rebootAPI() { - if ('electronAPI' in window) { - window.electronAPI.restartApp(); - return true; - } - - const isConfirmed = await customConfirm("Are you sure you'd like to reboot the server?"); - if (isConfirmed) { - try { - const response = await api.fetchApi("/manager/reboot"); - if (response.status == 403) { - await handle403Response(response); - return false; - } - } - catch(exception) {} - } - - return false; -} - - -export var manager_instance = null; - -export function setManagerInstance(obj) { - manager_instance = obj; -} - -export function showToast(message, duration = 3000) { - const toast = $el("div.comfy-toast", {textContent: message}); - document.body.appendChild(toast); - setTimeout(() => { - toast.classList.add("comfy-toast-fadeout"); - setTimeout(() => toast.remove(), 500); - }, duration); -} - -function isValidURL(url) { - if(url.includes('&')) - return false; - - const http_pattern = /^(https?|ftp):\/\/[^\s$?#]+$/; - const ssh_pattern = /^(.+@|ssh:\/\/).+:.+$/; - return http_pattern.test(url) || ssh_pattern.test(url); -} - -export async function install_pip(packages) { - if(packages.includes('&')) - app.ui.dialog.show(`Invalid PIP package enumeration: '${packages}'`); - - const res = await api.fetchApi("/customnode/install/pip", { - method: "POST", - body: packages, - }); - - if(res.status == 403) { - await handle403Response(res); - return; - } - - if(res.status == 200) { - show_message(`PIP package installation is processed.
To apply the pip packages, please click the button in ComfyUI.`); - - const rebootButton = document.getElementById('cm-reboot-button3'); - const self = this; - - rebootButton.addEventListener("click", rebootAPI); - } - else { - show_message(`Failed to install '${packages}'
See terminal log.`); - } -} - -export async function install_via_git_url(url, manager_dialog) { - if(!url) { - return; - } - - if(!isValidURL(url)) { - show_message(`Invalid Git url '${url}'`); - return; - } - - show_message(`Wait...

Installing '${url}'`); - - const res = await api.fetchApi("/customnode/install/git_url", { - method: "POST", - body: url, - }); - - if(res.status == 403) { - await handle403Response(res); - return; - } - - if(res.status == 200) { - show_message(`'${url}' is installed
To apply the installed custom node, please ComfyUI.`); - - const rebootButton = document.getElementById('cm-reboot-button4'); - const self = this; - - rebootButton.addEventListener("click", - async function() { - if(await rebootAPI()) { - manager_instance.close(); - } - }); - } - else { - show_message(`Failed to install '${url}'
See terminal log.`); - } -} - -export async function free_models(free_execution_cache) { - try { - let mode = ""; - if(free_execution_cache) { - mode = '{"unload_models": true, "free_memory": true}'; - } - else { - mode = '{"unload_models": true}'; - } - - let res = await api.fetchApi(`/free`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: mode - }); - - if (res.status == 200) { - if(free_execution_cache) { - showToast("'Models' and 'Execution Cache' have been cleared.", 3000); - } - else { - showToast("Models' have been unloaded.", 3000); - } - } else { - showToast('Unloading of models failed. Installed ComfyUI may be an outdated version.', 5000); - } - } catch (error) { - showToast('An error occurred while trying to unload models.', 5000); - } -} - -export function md5(inputString) { - const hc = '0123456789abcdef'; - const rh = n => {let j,s='';for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;} - const ad = (x,y) => {let l=(x&0xFFFF)+(y&0xFFFF);let m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);} - const rl = (n,c) => (n<>>(32-c)); - const cm = (q,a,b,x,s,t) => ad(rl(ad(ad(a,q),ad(x,t)),s),b); - const ff = (a,b,c,d,x,s,t) => cm((b&c)|((~b)&d),a,b,x,s,t); - const gg = (a,b,c,d,x,s,t) => cm((b&d)|(c&(~d)),a,b,x,s,t); - const hh = (a,b,c,d,x,s,t) => cm(b^c^d,a,b,x,s,t); - const ii = (a,b,c,d,x,s,t) => cm(c^(b|(~d)),a,b,x,s,t); - const sb = x => { - let i;const nblk=((x.length+8)>>6)+1;const blks=[];for(i=0;i>2]|=x.charCodeAt(i)<<((i%4)*8);} - blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks; - } - let i,x=sb(inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd; - for(i=0;i { - err = e; - }); - - if (!res) { - return { - status: 400, - error: new Error("Unknown Error") - } - } - - const { status, statusText } = res; - if (err) { - return { - status, - error: err - } - } - - if (status !== 200) { - return { - status, - error: new Error(statusText || "Unknown Error") - } - } - - const data = await res.json(); - if (!data) { - return { - status, - error: new Error(`Failed to load data: ${route}`) - } - } - return { - status, - data - } -} - -// https://cenfun.github.io/open-icons/ -export const icons = { - search: '', - conflicts: '', - passed: '', - download: '', - close: '', - arrowRight: '' -} - -export function sanitizeHTML(str) { - return str - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} - -export function showTerminal() { - try { - const panel = app.extensionManager.bottomPanel; - const isTerminalVisible = panel.bottomPanelVisible && panel.activeBottomPanelTab.id === 'logs-terminal'; - if (!isTerminalVisible) - panel.toggleBottomPanelTab('logs-terminal'); - } - catch(exception) { - // do nothing - } -} - -let need_restart = false; - -export function setNeedRestart(value) { - need_restart = value; -} - -async function onReconnected(event) { - if(need_restart) { - setNeedRestart(false); - - const confirmed = await customConfirm("To apply the changes to the node pack's installation status, you need to refresh the browser. Would you like to refresh?"); - if (!confirmed) { - return; - } - - window.location.reload(true); - } -} - -api.addEventListener('reconnected', onReconnected); - -const storeId = "comfyui-manager-grid"; -let timeId; -export function storeColumnWidth(gridId, columnItem) { - clearTimeout(timeId); - timeId = setTimeout(() => { - let data = {}; - const dataStr = localStorage.getItem(storeId); - if (dataStr) { - try { - data = JSON.parse(dataStr); - } catch (e) {} - } - - if (!data[gridId]) { - data[gridId] = {}; - } - - data[gridId][columnItem.id] = columnItem.width; - - localStorage.setItem(storeId, JSON.stringify(data)); - - }, 200) -} - -export function restoreColumnWidth(gridId, columns) { - const dataStr = localStorage.getItem(storeId); - if (!dataStr) { - return; - } - let data; - try { - data = JSON.parse(dataStr); - } catch (e) {} - if(!data) { - return; - } - const widthMap = data[gridId]; - if (!widthMap) { - return; - } - - columns.forEach(columnItem => { - const w = widthMap[columnItem.id]; - if (w) { - columnItem.width = w; - } - }); - -} - -export function getTimeAgo(dateStr) { - const date = new Date(dateStr); - - if (!date || !(date instanceof Date) || isNaN(date.getTime())) { - return ""; - } - - const units = [ - { max: 2760000, value: 60000, name: 'minute', past: 'a minute ago', future: 'in a minute' }, - { max: 72000000, value: 3600000, name: 'hour', past: 'an hour ago', future: 'in an hour' }, - { max: 518400000, value: 86400000, name: 'day', past: 'yesterday', future: 'tomorrow' }, - { max: 2419200000, value: 604800000, name: 'week', past: 'last week', future: 'in a week' }, - { max: 28512000000, value: 2592000000, name: 'month', past: 'last month', future: 'in a month' } - ]; - const diff = Date.now() - date.getTime(); - // less than a minute - if (Math.abs(diff) < 60000) - return 'just now'; - for (let i = 0; i < units.length; i++) { - if (Math.abs(diff) < units[i].max) { - return format(diff, units[i].value, units[i].name, units[i].past, units[i].future, diff < 0); - } - } - function format(diff, divisor, unit, past, future, isInTheFuture) { - const val = Math.round(Math.abs(diff) / divisor); - if (isInTheFuture) - return val <= 1 ? future : 'in ' + val + ' ' + unit + 's'; - return val <= 1 ? past : val + ' ' + unit + 's ago'; - } - return format(diff, 31536000000, 'year', 'last year', 'in a year', diff < 0); -}; - -export const loadCss = (cssFile) => { - const cssPath = import.meta.resolve(cssFile); - //console.log(cssPath); - const $link = document.createElement("link"); - $link.setAttribute("rel", 'stylesheet'); - $link.setAttribute("href", cssPath); - document.head.appendChild($link); -}; - -export const copyText = (text) => { - return new Promise((resolve) => { - let err; - try { - navigator.clipboard.writeText(text); - } catch (e) { - err = e; - } - if (err) { - resolve(false); - } else { - resolve(true); - } - }); -}; - -function renderPopover($elem, target, options = {}) { - // async microtask - queueMicrotask(() => { - - const containerRect = getRect(window); - const targetRect = getRect(target); - const elemRect = getRect($elem); - - const positionInfo = getBestPosition( - containerRect, - targetRect, - elemRect, - options.positions - ); - const style = getPositionStyle(positionInfo, { - bgColor: options.bgColor, - borderColor: options.borderColor, - borderRadius: options.borderRadius - }); - - $elem.style.top = positionInfo.top + "px"; - $elem.style.left = positionInfo.left + "px"; - $elem.style.background = style.background; - - }); -} - -let $popover; -export function hidePopover() { - if ($popover) { - $popover.remove(); - $popover = null; - } -} -export function showPopover(target, text, className, options) { - hidePopover(); - $popover = document.createElement("div"); - $popover.className = ['cn-popover', className].filter(it => it).join(" "); - document.body.appendChild($popover); - $popover.innerHTML = text; - $popover.style.display = "block"; - renderPopover($popover, target, { - borderRadius: 10, - ... options - }); -} - -let $tooltip; -export function hideTooltip(target) { - if ($tooltip) { - $tooltip.style.display = "none"; - $tooltip.innerHTML = ""; - $tooltip.style.top = "0px"; - $tooltip.style.left = "0px"; - } -} -export function showTooltip(target, text, className = 'cn-tooltip', styleMap = {}) { - if (!$tooltip) { - $tooltip = document.createElement("div"); - $tooltip.className = className; - $tooltip.style.cssText = ` - pointer-events: none; - position: fixed; - z-index: 10001; - padding: 20px; - color: #1e1e1e; - max-width: 350px; - filter: drop-shadow(1px 5px 5px rgb(0 0 0 / 30%)); - ${Object.keys(styleMap).map(k=>k+":"+styleMap[k]+";").join("")} - `; - document.body.appendChild($tooltip); - } - - $tooltip.innerHTML = text; - $tooltip.style.display = "block"; - renderPopover($tooltip, target, { - positions: ['top', 'bottom', 'right', 'center'], - bgColor: "#ffffff", - borderColor: "#cccccc", - borderRadius: 5 - }); -} - -function initTooltip () { - const mouseenterHandler = (e) => { - const target = e.target; - const text = target.getAttribute('tooltip'); - if (text) { - showTooltip(target, text); - } - }; - const mouseleaveHandler = (e) => { - const target = e.target; - const text = target.getAttribute('tooltip'); - if (text) { - hideTooltip(target); - } - }; - document.body.removeEventListener('mouseenter', mouseenterHandler, true); - document.body.removeEventListener('mouseleave', mouseleaveHandler, true); - document.body.addEventListener('mouseenter', mouseenterHandler, true); - document.body.addEventListener('mouseleave', mouseleaveHandler, true); -} - -initTooltip(); \ No newline at end of file diff --git a/js/components-manager.js b/js/components-manager.js deleted file mode 100644 index 8d4234ba..00000000 --- a/js/components-manager.js +++ /dev/null @@ -1,812 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js" -import { sleep, show_message, customConfirm, customAlert } from "./common.js"; -import { GroupNodeConfig, GroupNodeHandler } from "../../extensions/core/groupNode.js"; -import { ComfyDialog, $el } from "../../scripts/ui.js"; - -const SEPARATOR = ">" - -let pack_map = {}; -let rpack_map = {}; - -export function getPureName(node) { - // group nodes/ - let category = null; - if(node.category) { - category = node.category.substring(12); - } - else { - category = node.constructor.category?.substring(12); - } - if(category) { - let purename = node.comfyClass.substring(category.length+1); - return purename; - } - else if(node.comfyClass.startsWith('workflow/') || node.comfyClass.startsWith(`workflow${SEPARATOR}`)) { - return node.comfyClass.substring(9); - } - else { - return node.comfyClass; - } -} - -function isValidVersionString(version) { - const versionPattern = /^(\d+)\.(\d+)(\.(\d+))?$/; - - const match = version.match(versionPattern); - - return match !== null && - parseInt(match[1], 10) >= 0 && - parseInt(match[2], 10) >= 0 && - (!match[3] || parseInt(match[4], 10) >= 0); -} - -function register_pack_map(name, data) { - if(data.packname) { - pack_map[data.packname] = name; - rpack_map[name] = data; - } - else { - rpack_map[name] = data; - } -} - -function storeGroupNode(name, data, register=true) { - let extra = app.graph.extra; - if (!extra) app.graph.extra = extra = {}; - let groupNodes = extra.groupNodes; - if (!groupNodes) extra.groupNodes = groupNodes = {}; - groupNodes[name] = data; - - if(register) { - register_pack_map(name, data); - } -} - -export async function load_components() { - let data = await api.fetchApi('/manager/component/loads', {method: "POST"}); - let components = await data.json(); - - let start_time = Date.now(); - let failed = []; - let failed2 = []; - - for(let name in components) { - if(app.graph.extra?.groupNodes?.[name]) { - if(data) { - let data = components[name]; - - let category = data.packname; - if(data.category) { - category += SEPARATOR + data.category; - } - if(category == '') { - category = 'components'; - } - - const config = new GroupNodeConfig(name, data); - await config.registerType(category); - - register_pack_map(name, data); - continue; - } - } - - let nodeData = components[name]; - - storeGroupNode(name, nodeData); - - const config = new GroupNodeConfig(name, nodeData); - - while(true) { - try { - let category = nodeData.packname; - if(nodeData.category) { - category += SEPARATOR + nodeData.category; - } - if(category == '') { - category = 'components'; - } - - await config.registerType(category); - register_pack_map(name, nodeData); - break; - } - catch { - let elapsed_time = Date.now() - start_time; - if (elapsed_time > 5000) { - failed.push(name); - break; - } else { - await sleep(100); - } - } - } - } - - // fallback1 - for(let i in failed) { - let name = failed[i]; - - if(app.graph.extra?.groupNodes?.[name]) { - continue; - } - - let nodeData = components[name]; - - storeGroupNode(name, nodeData); - - const config = new GroupNodeConfig(name, nodeData); - while(true) { - try { - let category = nodeData.packname; - if(nodeData.workflow.category) { - category += SEPARATOR + nodeData.category; - } - if(category == '') { - category = 'components'; - } - - await config.registerType(category); - register_pack_map(name, nodeData); - break; - } - catch { - let elapsed_time = Date.now() - start_time; - if (elapsed_time > 10000) { - failed2.push(name); - break; - } else { - await sleep(100); - } - } - } - } - - // fallback2 - for(let name in failed2) { - let name = failed2[i]; - - let nodeData = components[name]; - - storeGroupNode(name, nodeData); - - const config = new GroupNodeConfig(name, nodeData); - while(true) { - try { - let category = nodeData.workflow.packname; - if(nodeData.workflow.category) { - category += SEPARATOR + nodeData.category; - } - if(category == '') { - category = 'components'; - } - - await config.registerType(category); - register_pack_map(name, nodeData); - break; - } - catch { - let elapsed_time = Date.now() - start_time; - if (elapsed_time > 30000) { - failed.push(name); - break; - } else { - await sleep(100); - } - } - } - } -} - -async function save_as_component(node, version, author, prefix, nodename, packname, category) { - let component_name = `${prefix}::${nodename}`; - - let subgraph = app.graph.extra?.groupNodes?.[component_name]; - if(!subgraph) { - subgraph = app.graph.extra?.groupNodes?.[getPureName(node)]; - } - - subgraph.version = version; - subgraph.author = author; - subgraph.datetime = Date.now(); - subgraph.packname = packname; - subgraph.category = category; - - let body = - { - name: component_name, - workflow: subgraph - }; - - pack_map[packname] = component_name; - rpack_map[component_name] = subgraph; - - const res = await api.fetchApi('/manager/component/save', { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - if(res.status == 200) { - storeGroupNode(component_name, subgraph); - const config = new GroupNodeConfig(component_name, subgraph); - - let category = body.workflow.packname; - if(body.workflow.category) { - category += SEPARATOR + body.workflow.category; - } - if(category == '') { - category = 'components'; - } - - await config.registerType(category); - - let path = await res.text(); - show_message(`Component '${component_name}' is saved into:\n${path}`); - } - else - show_message(`Failed to save component.`); -} - -async function import_component(component_name, component, mode) { - if(mode) { - let body = - { - name: component_name, - workflow: component - }; - - const res = await api.fetchApi('/manager/component/save', { - method: "POST", - headers: { "Content-Type": "application/json", }, - body: JSON.stringify(body) - }); - } - - let category = component.packname; - if(component.category) { - category += SEPARATOR + component.category; - } - if(category == '') { - category = 'components'; - } - - storeGroupNode(component_name, component); - const config = new GroupNodeConfig(component_name, component); - await config.registerType(category); -} - -function restore_to_loaded_component(component_name) { - if(rpack_map[component_name]) { - let component = rpack_map[component_name]; - storeGroupNode(component_name, component, false); - const config = new GroupNodeConfig(component_name, component); - config.registerType(component.category); - } -} - -// Using a timestamp prevents duplicate pastes and ensures the prevention of re-deletion of litegrapheditor_clipboard. -let last_paste_timestamp = null; - -function versionCompare(v1, v2) { - let ver1; - let ver2; - if(v1 && v1 != '') { - ver1 = v1.split('.'); - ver1[0] = parseInt(ver1[0]); - ver1[1] = parseInt(ver1[1]); - if(ver1.length == 2) - ver1.push(0); - else - ver1[2] = parseInt(ver2[2]); - } - else { - ver1 = [0,0,0]; - } - - if(v2 && v2 != '') { - ver2 = v2.split('.'); - ver2[0] = parseInt(ver2[0]); - ver2[1] = parseInt(ver2[1]); - if(ver2.length == 2) - ver2.push(0); - else - ver2[2] = parseInt(ver2[2]); - } - else { - ver2 = [0,0,0]; - } - - if(ver1[0] > ver2[0]) - return -1; - else if(ver1[0] < ver2[0]) - return 1; - - if(ver1[1] > ver2[1]) - return -1; - else if(ver1[1] < ver2[1]) - return 1; - - if(ver1[2] > ver2[2]) - return -1; - else if(ver1[2] < ver2[2]) - return 1; - - return 0; -} - -function checkVersion(name, component) { - let msg = ''; - if(rpack_map[name]) { - let old_version = rpack_map[name].version; - if(!old_version || old_version == '') { - msg = ` '${name}' Upgrade (V0.0 -> V${component.version})`; - } - else { - let c = versionCompare(old_version, component.version); - if(c < 0) { - msg = ` '${name}' Downgrade (V${old_version} -> V${component.version})`; - } - else if(c > 0) { - msg = ` '${name}' Upgrade (V${old_version} -> V${component.version})`; - } - else { - msg = ` '${name}' Same version (V${component.version})`; - } - } - } - else { - msg = `'${name}' NEW (V${component.version})`; - } - - return msg; -} - -async function handle_import_components(components) { - let msg = 'Components:\n'; - let cnt = 0; - for(let name in components) { - let component = components[name]; - let v = checkVersion(name, component); - - if(cnt < 10) { - msg += v + '\n'; - } - else if (cnt == 10) { - msg += '...\n'; - } - else { - // do nothing - } - - cnt++; - } - - let last_name = null; - msg += '\nWill you load components?\n'; - const confirmed = await customConfirm(msg); - if(confirmed) { - const mode = await customConfirm('\nWill you save components?\n(cancel=load without save)'); - - for(let name in components) { - let component = components[name]; - import_component(name, component, mode); - last_name = name; - } - - if(mode) { - show_message('Components are saved.'); - } - else { - show_message('Components are loaded.'); - } - } - - if(cnt == 1 && last_name) { - const node = LiteGraph.createNode(`workflow${SEPARATOR}${last_name}`); - node.pos = [app.canvas.graph_mouse[0], app.canvas.graph_mouse[1]]; - app.canvas.graph.add(node, false); - } -} - -async function handlePaste(e) { - let data = (e.clipboardData || window.clipboardData); - const items = data.items; - for(const item of items) { - if(item.kind == 'string' && item.type == 'text/plain') { - data = data.getData("text/plain"); - try { - let json_data = JSON.parse(data); - if(json_data.kind == 'ComfyUI Components' && last_paste_timestamp != json_data.timestamp) { - last_paste_timestamp = json_data.timestamp; - await handle_import_components(json_data.components); - - // disable paste node - localStorage.removeItem("litegrapheditor_clipboard", null); - } - else { - console.log('This components are already pasted: ignored'); - } - } - catch { - // nothing to do - } - } - } -} - -document.addEventListener("paste", handlePaste); - - -export class ComponentBuilderDialog extends ComfyDialog { - constructor() { - super(); - } - - clear() { - while (this.element.children.length) { - this.element.removeChild(this.element.children[0]); - } - } - - show() { - this.invalidateControl(); - - this.element.style.display = "block"; - this.element.style.zIndex = 1099; - this.element.style.width = "500px"; - this.element.style.height = "480px"; - } - - invalidateControl() { - this.clear(); - - let self = this; - - const close_button = $el("button", { id: "cm-close-button", type: "button", textContent: "Close", onclick: () => self.close() }); - this.save_button = $el("button", - { id: "cm-save-button", type: "button", textContent: "Save", onclick: () => - { - save_as_component(self.target_node, self.version_string.value.trim(), self.author.value.trim(), self.node_prefix.value.trim(), - self.getNodeName(), self.getPackName(), self.category.value.trim()); - } - }); - - let default_nodename = getPureName(this.target_node).trim(); - - let groupNode = app.graph.extra.groupNodes[default_nodename]; - let default_packname = groupNode.packname; - if(!default_packname) { - default_packname = ''; - } - - let default_category = groupNode.category; - if(!default_category) { - default_category = ''; - } - - this.default_ver = groupNode.version; - if(!this.default_ver) { - this.default_ver = '0.0'; - } - - let default_author = groupNode.author; - if(!default_author) { - default_author = ''; - } - - let delimiterIndex = default_nodename.indexOf('::'); - let default_prefix = ""; - if(delimiterIndex != -1) { - default_prefix = default_nodename.substring(0, delimiterIndex); - default_nodename = default_nodename.substring(delimiterIndex + 2); - } - - if(!default_prefix) { - this.save_button.disabled = true; - } - - this.pack_list = this.createPackListCombo(); - - let version_string = this.createLabeledInput('input version (e.g. 1.0)', '*Version : ', this.default_ver); - this.version_string = version_string[1]; - this.version_string.disabled = true; - - let author = this.createLabeledInput('input author (e.g. Dr.Lt.Data)', 'Author : ', default_author); - this.author = author[1]; - - let node_prefix = this.createLabeledInput('input node prefix (e.g. mypack)', '*Prefix : ', default_prefix); - this.node_prefix = node_prefix[1]; - - let manual_nodename = this.createLabeledInput('input node name (e.g. MAKE_BASIC_PIPE)', 'Nodename : ', default_nodename); - this.manual_nodename = manual_nodename[1]; - - let manual_packname = this.createLabeledInput('input pack name (e.g. mypack)', 'Packname : ', default_packname); - this.manual_packname = manual_packname[1]; - - let category = this.createLabeledInput('input category (e.g. util/pipe)', 'Category : ', default_category); - this.category = category[1]; - - this.node_label = this.createNodeLabel(); - - let author_mode = this.createAuthorModeCheck(); - this.author_mode = author_mode[0]; - - const content = - $el("div.comfy-modal-content", - [ - $el("tr.cm-title", {}, [ - $el("font", {size:6, color:"white"}, [`ComfyUI-Manager: Component Builder`])] - ), - $el("br", {}, []), - $el("div.cm-menu-container", - [ - author_mode[0], - author_mode[1], - category[0], - author[0], - node_prefix[0], - manual_nodename[0], - manual_packname[0], - version_string[0], - this.pack_list, - $el("br", {}, []), - this.node_label - ]), - - $el("br", {}, []), - this.save_button, - close_button, - ] - ); - - content.style.width = '100%'; - content.style.height = '100%'; - - this.element = $el("div.comfy-modal", { id:'cm-manager-dialog', parent: document.body }, [ content ]); - } - - validateInput() { - let msg = ""; - - if(!isValidVersionString(this.version_string.value)) { - msg += 'Invalid version string: '+event.value+"\n"; - } - - if(this.node_prefix.value.trim() == '') { - msg += 'Node prefix cannot be empty\n'; - } - - if(this.manual_nodename.value.trim() == '') { - msg += 'Node name cannot be empty\n'; - } - - if(msg != '') { -// alert(msg); - } - - this.save_button.disabled = msg != ""; - } - - getPackName() { - if(this.pack_list.selectedIndex == 0) { - return this.manual_packname.value.trim(); - } - - return this.pack_list.value.trim(); - } - - getNodeName() { - if(this.manual_nodename.value.trim() != '') { - return this.manual_nodename.value.trim(); - } - - return getPureName(this.target_node); - } - - createAuthorModeCheck() { - let check = $el("input",{type:'checkbox', id:"author-mode"},[]) - const check_label = $el("label",{for:"author-mode"},["Enable author mode"]); - check_label.style.color = "var(--fg-color)"; - check_label.style.cursor = "pointer"; - check.checked = false; - - let self = this; - check.onchange = () => { - self.version_string.disabled = !check.checked; - - if(!check.checked) { - self.version_string.value = self.default_ver; - } - else { - customAlert('If you are not the author, it is not recommended to change the version, as it may cause component update issues.'); - } - }; - - return [check, check_label]; - } - - createNodeLabel() { - let label = $el('p'); - label.className = 'cb-node-label'; - if(this.target_node.comfyClass.includes('::')) - label.textContent = getPureName(this.target_node); - else - label.textContent = " _::" + getPureName(this.target_node); - return label; - } - - createLabeledInput(placeholder, label, value) { - let textbox = $el('input.cb-widget-input', {type:'text', placeholder:placeholder, value:value}, []); - - let self = this; - textbox.onchange = () => { - this.validateInput.call(self); - this.node_label.textContent = this.node_prefix.value + "::" + this.manual_nodename.value; - } - let row = $el('span.cb-widget', {}, [ $el('span.cb-widget-input-label', label), textbox]); - - return [row, textbox]; - } - - createPackListCombo() { - let combo = document.createElement("select"); - combo.className = "cb-widget"; - let default_packname_option = { value: '##manual', text: 'Packname: Manual' }; - - combo.appendChild($el('option', default_packname_option, [])); - for(let name in pack_map) { - combo.appendChild($el('option', { value: name, text: 'Packname: '+ name }, [])); - } - - let self = this; - combo.onchange = function () { - if(combo.selectedIndex == 0) { - self.manual_packname.disabled = false; - } - else { - self.manual_packname.disabled = true; - } - }; - - return combo; - } -} - -let orig_handleFile = app.handleFile; - -async function handleFile(file, ...args) { - if (file.name?.endsWith(".json") || file.name?.endsWith(".pack")) { - const reader = new FileReader(); - reader.onload = async () => { - let is_component = false; - const jsonContent = JSON.parse(reader.result); - for(let name in jsonContent) { - let cand = jsonContent[name]; - is_component = cand.datetime && cand.version; - break; - } - - if(is_component) { - await handle_import_components(jsonContent); - } - else { - orig_handleFile.call(app, file, ...args); - } - }; - reader.readAsText(file); - - return; - } - - orig_handleFile.call(app, file, ...args); -} - -app.handleFile = handleFile; - -let current_component_policy = 'workflow'; -try { - api.fetchApi('/manager/policy/component') - .then(response => response.text()) - .then(data => { current_component_policy = data; }); -} -catch {} - -function getChangedVersion(groupNodes) { - if(!Object.keys(pack_map).length || !groupNodes) - return null; - - let res = {}; - for(let component_name in groupNodes) { - let data = groupNodes[component_name]; - - if(rpack_map[component_name]) { - let v = versionCompare(data.version, rpack_map[component_name].version); - res[component_name] = v; - } - } - - return res; -} - -const loadGraphData = app.loadGraphData; -app.loadGraphData = async function () { - if(arguments.length == 0) - return await loadGraphData.apply(this, arguments); - - let graphData = arguments[0]; - let groupNodes = graphData.extra?.groupNodes; - let res = getChangedVersion(groupNodes); - - if(res) { - let target_components = null; - switch(current_component_policy) { - case 'higher': - target_components = Object.keys(res).filter(key => res[key] == 1); - break; - - case 'mine': - target_components = Object.keys(res); - break; - - default: - // do nothing - } - - if(target_components) { - for(let i in target_components) { - let component_name = target_components[i]; - let component = rpack_map[component_name]; - if(component && graphData.extra?.groupNodes) { - graphData.extra.groupNodes[component_name] = component; - } - } - } - } - else { - console.log('Empty components: policy ignored'); - } - - arguments[0] = graphData; - return await loadGraphData.apply(this, arguments); -}; - -export function set_component_policy(v) { - current_component_policy = v; -} - -let graphToPrompt = app.graphToPrompt; -app.graphToPrompt = async function () { - let p = await graphToPrompt.call(app); - try { - let groupNodes = p.workflow.extra?.groupNodes; - if(groupNodes) { - p.workflow.extra = { ... p.workflow.extra}; - - // get used group nodes - let used_group_nodes = new Set(); - for(let node of p.workflow.nodes) { - if(node.type.startsWith(`workflow/`) || node.type.startsWith(`workflow${SEPARATOR}`)) { - used_group_nodes.add(node.type.substring(9)); - } - } - - // remove unused group nodes - let new_groupNodes = {}; - for (let key in p.workflow.extra.groupNodes) { - if (used_group_nodes.has(key)) { - new_groupNodes[key] = p.workflow.extra.groupNodes[key]; - } - } - p.workflow.extra.groupNodes = new_groupNodes; - } - } - catch(e) { - console.log(`Failed to filtering group nodes: ${e}`); - } - - return p; -} diff --git a/js/custom-nodes-manager.css b/js/custom-nodes-manager.css deleted file mode 100644 index 4ea82580..00000000 --- a/js/custom-nodes-manager.css +++ /dev/null @@ -1,729 +0,0 @@ -.cn-manager { - --grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; - z-index: 1099; - width: 80vw; - height: 75vh; - min-height: 30em; - display: flex; - flex-direction: column; - gap: 10px; - color: var(--fg-color); - font-family: arial, sans-serif; - text-underline-offset: 3px; - outline: none; - margin: calc(var(--spacing)*2); -} - -.cn-manager .cn-flex-auto { - flex: auto; -} - -.cn-manager button { - width: auto; - position: relative; - overflow: hidden; - font-size: 16px; - color: var(--input-text); - background-color: var(--comfy-input-bg); - border-color: var(--border-color); - margin: 0; - min-width: 100px; -} - -.cn-manager button:hover { - filter: brightness(125%); -} - -.cn-manager button:disabled, -.cn-manager input:disabled, -.cn-manager select:disabled { - color: gray; -} - -.cn-manager button:disabled { - background-color: var(--comfy-input-bg); -} - -.cn-manager .cn-manager-restart { - display: none; - background-color: #500000 !important; - border-color: #88181b !important; - color: white !important; -} - -.cn-manager .cn-manager-restart:hover { - background-color: #88181b !important; -} - -.cn-manager .cn-manager-stop { - display: none; - background-color: #500000; - color: white; -} - -.cn-manager .cn-manager-back { - align-items: center; - justify-content: center; -} - -.arrow-icon { - height: 1em; - width: 1em; - margin-right: 5px; - transform: translateY(2px); -} - -.cn-icon { - display: block; - width: 16px; - height: 16px; -} - -.cn-icon svg { - display: block; - margin: 0; - pointer-events: none; -} - -.cn-manager-header { - display: flex; - flex-wrap: wrap; - gap: 5px; - align-items: center; -} - -.cn-manager-header label { - display: flex; - gap: 5px; - align-items: center; -} - -.cn-manager-filter { - height: 28px; - line-height: 28px; - - cursor: pointer; - padding: 0.5em 0.5em; - border: 1px solid var(--border-color); - border-radius: 6px; - background: var(--comfy-input-bg); -} - -.cn-manager-filter:hover { - filter: brightness(125%); -} - -.cn-manager-keywords { - height: 28px; - line-height: 28px; - padding: 0 5px 0 26px; - background: var(--comfy-input-bg); - background-size: 16px; - background-position: 5px center; - background-repeat: no-repeat; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E"); - - border: 1px solid var(--border-color); - border-radius: 6px; - - outline-color: transparent; -} - -.cn-manager-status { - padding-left: 10px; -} - -.cn-manager-grid { - flex: auto; - border: 1px solid var(--border-color); - overflow: hidden; - position: relative; -} - -.cn-manager-selection { - display: flex; - flex-wrap: wrap; - gap: 10px; - align-items: center; -} - -.cn-manager-message { - position: relative; -} - -.cn-manager-footer { - display: flex; - flex-wrap: wrap; - gap: 10px; - align-items: center; -} - -.cn-manager-grid .tg-turbogrid { - font-family: var(--grid-font); - font-size: 15px; - background: var(--bg-color); -} - -.cn-manager-grid .tg-turbogrid .tg-highlight::after { - position: absolute; - top: 0; - left: 0; - content: ""; - display: block; - width: 100%; - height: 100%; - box-sizing: border-box; - background-color: #80bdff11; - pointer-events: none; -} - -.cn-manager-grid .cn-pack-name a { - color: skyblue; - text-decoration: none; - word-break: break-word; -} - -.cn-manager-grid .cn-pack-desc a { - color: #5555FF; - font-weight: bold; - text-decoration: none; -} - -.cn-manager-grid .tg-cell a:hover { - text-decoration: underline; -} - -.cn-manager-grid .cn-pack-version { - line-height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - height: 100%; - gap: 5px; -} - -.cn-manager-grid .cn-pack-nodes { - line-height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - gap: 5px; - cursor: pointer; - height: 100%; -} - -.cn-manager-grid .cn-pack-nodes:hover { - text-decoration: underline; -} - -.cn-manager-grid .cn-pack-conflicts { - color: orange; -} - -.cn-popover { - position: fixed; - z-index: 10000; - padding: 20px; - color: #1e1e1e; - filter: drop-shadow(1px 5px 5px rgb(0 0 0 / 30%)); - overflow: hidden; -} - -.cn-flyover { - position: absolute; - top: 0; - right: 0; - z-index: 1000; - display: none; - width: 50%; - height: 100%; - background-color: var(--comfy-menu-bg); - animation-duration: 0.2s; - animation-fill-mode: both; - flex-direction: column; -} - -.cn-flyover::before { - position: absolute; - top: 0; - content: ""; - z-index: 10; - display: block; - width: 10px; - height: 100%; - pointer-events: none; - left: -10px; - background-image: linear-gradient(to left, rgb(0 0 0 / 20%), rgb(0 0 0 / 0%)); -} - -.cn-flyover-header { - height: 45px; - display: flex; - align-items: center; - gap: 5px; - border-bottom: 1px solid var(--border-color); -} - -.cn-flyover-close { - display: flex; - align-items: center; - padding: 0 10px; - justify-content: center; - cursor: pointer; - opacity: 0.8; - height: 100%; -} - -.cn-flyover-close:hover { - opacity: 1; -} - -.cn-flyover-close svg { - display: block; - margin: 0; - pointer-events: none; - width: 20px; - height: 20px; -} - -.cn-flyover-title { - display: flex; - align-items: center; - font-weight: bold; - gap: 10px; - flex: auto; -} - -.cn-flyover-body { - height: calc(100% - 45px); - overflow-y: auto; - position: relative; - background-color: var(--comfy-menu-secondary-bg); -} - -@keyframes cn-slide-in-right { - from { - visibility: visible; - transform: translate3d(100%, 0, 0); - } - - to { - transform: translate3d(0, 0, 0); - } -} - -.cn-slide-in-right { - animation-name: cn-slide-in-right; -} - -@keyframes cn-slide-out-right { - from { - transform: translate3d(0, 0, 0); - } - - to { - visibility: hidden; - transform: translate3d(100%, 0, 0); - } -} - -.cn-slide-out-right { - animation-name: cn-slide-out-right; -} - -.cn-nodes-list { - width: 100%; -} - -.cn-nodes-row { - display: flex; - align-items: center; - gap: 10px; -} - -.cn-nodes-row:nth-child(odd) { - background-color: rgb(0 0 0 / 5%); -} - -.cn-nodes-row:hover { - background-color: rgb(0 0 0 / 10%); -} - -.cn-nodes-sn { - text-align: right; - min-width: 35px; - color: var(--drag-text); - flex-shrink: 0; - font-size: 12px; - padding: 8px 5px; -} - -.cn-nodes-name { - cursor: pointer; - white-space: nowrap; - flex-shrink: 0; - position: relative; - padding: 8px 5px; -} - -.cn-nodes-name::after { - content: attr(action); - position: absolute; - pointer-events: none; - top: 50%; - left: 100%; - transform: translate(5px, -50%); - font-size: 12px; - color: var(--drag-text); - background-color: var(--comfy-input-bg); - border-radius: 10px; - border: 1px solid var(--border-color); - padding: 3px 8px; - display: none; -} - -.cn-nodes-name.action::after { - display: block; -} - -.cn-nodes-name:hover { - text-decoration: underline; -} - -.cn-nodes-conflict .cn-nodes-name, -.cn-nodes-conflict .cn-icon { - color: orange; -} - -.cn-conflicts-list { - display: flex; - flex-wrap: wrap; - gap: 5px; - align-items: center; - padding: 5px 0; -} - -.cn-conflicts-list b { - font-weight: normal; - color: var(--descrip-text); -} - -.cn-nodes-pack { - cursor: pointer; - color: skyblue; -} - -.cn-nodes-pack:hover { - text-decoration: underline; -} - -.cn-pack-badge { - font-size: 12px; - font-weight: normal; - background-color: var(--comfy-input-bg); - border-radius: 10px; - border: 1px solid var(--border-color); - padding: 3px 8px; - color: var(--error-text); -} - -.cn-preview { - min-width: 300px; - max-width: 500px; - min-height: 120px; - overflow: hidden; - font-size: 12px; - pointer-events: none; - padding: 12px; - color: var(--fg-color); -} - -.cn-preview-header { - display: flex; - gap: 8px; - align-items: center; - border-bottom: 1px solid var(--comfy-input-bg); - padding: 5px 10px; -} - -.cn-preview-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: grey; - position: relative; - filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 30%)); -} - -.cn-preview-dot.cn-preview-optional::after { - content: ""; - position: absolute; - pointer-events: none; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--comfy-input-bg); - border-radius: 50%; - width: 3px; - height: 3px; -} - -.cn-preview-dot.cn-preview-grid { - border-radius: 0; -} - -.cn-preview-dot.cn-preview-grid::before { - content: ''; - position: absolute; - border-left: 1px solid var(--comfy-input-bg); - border-right: 1px solid var(--comfy-input-bg); - width: 4px; - height: 100%; - left: 2px; - top: 0; - z-index: 1; -} - -.cn-preview-dot.cn-preview-grid::after { - content: ''; - position: absolute; - border-top: 1px solid var(--comfy-input-bg); - border-bottom: 1px solid var(--comfy-input-bg); - width: 100%; - height: 4px; - left: 0; - top: 2px; - z-index: 1; -} - -.cn-preview-name { - flex: auto; - font-size: 14px; -} - -.cn-preview-io { - display: flex; - justify-content: space-between; - padding: 10px 10px; -} - -.cn-preview-column > div { - display: flex; - gap: 10px; - align-items: center; - height: 18px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.cn-preview-input { - justify-content: flex-start; -} - -.cn-preview-output { - justify-content: flex-end; -} - -.cn-preview-list { - display: flex; - flex-direction: column; - gap: 3px; - padding: 0 10px 10px 10px; -} - -.cn-preview-switch { - position: relative; - display: flex; - justify-content: space-between; - align-items: center; - background: var(--bg-color); - border: 2px solid var(--border-color); - border-radius: 10px; - text-wrap: nowrap; - padding: 2px 20px; - gap: 10px; -} - -.cn-preview-switch::before, -.cn-preview-switch::after { - position: absolute; - pointer-events: none; - top: 50%; - transform: translate(0, -50%); - color: var(--fg-color); - opacity: 0.8; -} - -.cn-preview-switch::before { - content: "◀"; - left: 5px; -} - -.cn-preview-switch::after { - content: "▶"; - right: 5px; -} - -.cn-preview-value { - color: var(--descrip-text); -} - -.cn-preview-string { - min-height: 30px; - max-height: 300px; - background: var(--bg-color); - color: var(--descrip-text); - border-radius: 3px; - padding: 3px 5px; - overflow-y: auto; - overflow-x: hidden; -} - -.cn-preview-description { - margin: 0px 10px 10px 10px; - padding: 6px; - background: var(--border-color); - color: var(--descrip-text); - border-radius: 5px; - font-style: italic; - word-break: break-word; -} - -.cn-tag-list { - display: flex; - flex-wrap: wrap; - gap: 5px; - align-items: center; - margin-bottom: 5px; -} - -.cn-tag-list > div { - background-color: var(--border-color); - border-radius: 5px; - padding: 0 5px; -} - -.cn-install-buttons { - display: flex; - flex-direction: column; - gap: 3px; - padding: 3px; - align-items: center; - justify-content: center; - height: 100%; -} - -.cn-install-buttons button { - padding: 4px 8px; -} - -.cn-selected-buttons { - display: flex; - gap: 5px; - align-items: center; - padding-right: 20px; -} - -.cn-manager .cn-btn-enable { - background-color: #333399; - color: white; -} - -.cn-manager .cn-btn-disable { - background-color: #442277; - color: white; -} - -.cn-manager .cn-btn-update { - background-color: #1155AA; - color: white; -} - -.cn-manager .cn-btn-try-update { - background-color: Gray; - color: white; -} - -.cn-manager .cn-btn-try-fix { - background-color: #6495ED; - color: white; -} - -.cn-manager .cn-btn-import-failed { - background-color: #AA1111; - font-size: 10px; - font-weight: bold; - color: white; -} - -.cn-manager .cn-btn-install { - background-color: black; - color: white; -} - -.cn-manager .cn-btn-try-install { - background-color: Gray; - color: white; -} - -.cn-manager .cn-btn-uninstall { - background-color: #993333; - color: white; -} - -.cn-manager .cn-btn-reinstall { - background-color: #993333; - color: white; -} - -.cn-manager .cn-btn-switch { - background-color: #448833; - color: white; - -} - -@keyframes cn-btn-loading-bg { - 0% { - left: 0; - } - 100% { - left: -105px; - } -} - -.cn-manager button.cn-btn-loading { - position: relative; - overflow: hidden; - border-color: rgb(0 119 207 / 80%); - background-color: var(--comfy-input-bg); -} - -.cn-manager button.cn-btn-loading::after { - position: absolute; - top: 0; - left: 0; - content: ""; - width: 500px; - height: 100%; - background-image: repeating-linear-gradient( - -45deg, - rgb(0 119 207 / 30%), - rgb(0 119 207 / 30%) 10px, - transparent 10px, - transparent 15px - ); - animation: cn-btn-loading-bg 2s linear infinite; -} - -.cn-manager-light .cn-pack-name a { - color: blue; -} - -.cn-manager-light .cm-warn-note { - background-color: #ccc !important; -} - -.cn-manager-light .cn-btn-install { - background-color: #333; -} \ No newline at end of file diff --git a/js/custom-nodes-manager.js b/js/custom-nodes-manager.js deleted file mode 100644 index b290df61..00000000 --- a/js/custom-nodes-manager.js +++ /dev/null @@ -1,2204 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { api } from "../../scripts/api.js"; -import { buildGuiFrameCustomHeader, createSettingsCombo } from "./comfyui-gui-builder.js"; - -import { - manager_instance, rebootAPI, install_via_git_url, - fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt, - sanitizeHTML, infoToast, showTerminal, setNeedRestart, - storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss, - showPopover, hidePopover, handle403Response -} from "./common.js"; - -// https://cenfun.github.io/turbogrid/api.html -import TG from "./turbogrid.esm.js"; - -loadCss("./custom-nodes-manager.css"); - -const gridId = "node"; - -const pageHtml = ` -
-
-
-
- -
-`; - -const ShowMode = { - NORMAL: "Normal", - UPDATE: "Update", - MISSING: "Missing", - FAVORITES: "Favorites", - ALTERNATIVES: "Alternatives", - IN_WORKFLOW: "In Workflow", -}; - -export class CustomNodesManager { - static instance = null; - static ShowMode = ShowMode; - - constructor(app, manager_dialog) { - this.app = app; - this.manager_dialog = manager_dialog; - this.id = "cn-manager"; - - app.registerExtension({ - name: "Comfy.CustomNodesManager", - afterConfigureGraph: (missingNodeTypes) => { - const item = this.getFilterItem(ShowMode.MISSING); - if (item) { - item.hasData = false; - item.hashMap = null; - } - } - }); - - this.filter = ''; - this.keywords = ''; - this.restartMap = {}; - - this.init(); - - api.addEventListener("cm-queue-status", this.onQueueStatus); - api.getNodeDefs().then(objs => { - this.nodeMap = objs; - }) - } - - init() { - const header = $el("div.cn-manager-header.px-2", {}, [ - // $el("label", {}, [ - // $el("span", { textContent: "Filter" }), - // $el("select.cn-manager-filter") - // ]), - createSettingsCombo("Filter", $el("select.cn-manager-filter")), - $el("input.cn-manager-keywords.p-inputtext.p-component", { type: "search", placeholder: "Search" }), - $el("div.cn-manager-status"), - $el("div.cn-flex-auto"), - $el("div.cn-manager-channel") - ]); - - const frame = buildGuiFrameCustomHeader( - 'cn-manager-dialog', // dialog id - header, // custom header element - pageHtml, // dialog content element - this - ); // send this so we can attach close functions - - this.element = frame; - this.element.setAttribute("tabindex", 0); - this.element.focus(); - - this.initFilter(); - this.bindEvents(); - this.initGrid(); - } - - showVersionSelectorDialog(versions, onSelect) { - const dialog = new ComfyDialog(); - dialog.element.style.zIndex = 1100; - dialog.element.style.width = "300px"; - dialog.element.style.padding = "0"; - dialog.element.style.backgroundColor = "#2a2a2a"; - dialog.element.style.border = "1px solid #3a3a3a"; - dialog.element.style.borderRadius = "8px"; - dialog.element.style.boxSizing = "border-box"; - dialog.element.style.overflow = "hidden"; - - const contentStyle = { - width: "300px", - display: "flex", - flexDirection: "column", - alignItems: "center", - padding: "20px", - boxSizing: "border-box", - gap: "15px" - }; - - let selectedVersion = versions[0]; - - const versionList = $el("select", { - multiple: true, - size: Math.min(10, versions.length), - style: { - width: "260px", - height: "auto", - backgroundColor: "#383838", - color: "#ffffff", - border: "1px solid #4a4a4a", - borderRadius: "4px", - padding: "5px", - boxSizing: "border-box" - } - }, - versions.map((v, index) => $el("option", { - value: v, - textContent: v, - selected: index === 0 - })) - ); - - versionList.addEventListener('change', (e) => { - selectedVersion = e.target.value; - Array.from(e.target.options).forEach(opt => { - opt.selected = opt.value === selectedVersion; - }); - }); - - const content = $el("div", { - style: contentStyle - }, [ - $el("h3", { - textContent: "Select Version", - style: { - color: "#ffffff", - backgroundColor: "#1a1a1a", - padding: "10px 15px", - margin: "0 0 10px 0", - width: "260px", - textAlign: "center", - borderRadius: "4px", - boxSizing: "border-box", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis" - } - }), - versionList, - $el("div", { - style: { - display: "flex", - justifyContent: "space-between", - width: "260px", - gap: "10px" - } - }, [ - $el("button", { - textContent: "Cancel", - onclick: () => dialog.close(), - style: { - flex: "1", - padding: "8px", - backgroundColor: "#4a4a4a", - color: "#ffffff", - border: "none", - borderRadius: "4px", - cursor: "pointer", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis" - } - }), - $el("button", { - textContent: "Select", - onclick: () => { - if (selectedVersion) { - onSelect(selectedVersion); - dialog.close(); - } else { - customAlert("Please select a version."); - } - }, - style: { - flex: "1", - padding: "8px", - backgroundColor: "#4CAF50", - color: "#ffffff", - border: "none", - borderRadius: "4px", - cursor: "pointer", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis" - } - }), - ]) - ]); - - dialog.show(content); - } - - initFilter() { - const $filter = this.element.querySelector(".cn-manager-filter"); - const filterList = [{ - label: "All", - value: "", - hasData: true - }, { - label: "Installed", - value: "installed", - hasData: true - }, { - label: "Enabled", - value: "enabled", - hasData: true - }, { - label: "Disabled", - value: "disabled", - hasData: true - }, { - label: "Import Failed", - value: "import-fail", - hasData: true - }, { - label: "Not Installed", - value: "not-installed", - hasData: true - }, { - label: "ComfyRegistry", - value: "cnr", - hasData: true - }, { - label: "Non-ComfyRegistry", - value: "unknown", - hasData: true - }, { - label: "Update", - value: ShowMode.UPDATE, - hasData: false - }, { - label: "In Workflow", - value: ShowMode.IN_WORKFLOW, - hasData: false - }, { - label: "Missing", - value: ShowMode.MISSING, - hasData: false - }, { - label: "Favorites", - value: ShowMode.FAVORITES, - hasData: false - }, { - label: "Alternatives of A1111", - value: ShowMode.ALTERNATIVES, - hasData: false - }]; - this.filterList = filterList; - $filter.innerHTML = filterList.map(item => { - return `` - }).join(""); - } - - getFilterItem(filter) { - return this.filterList.find(it => it.value === filter) - } - - getActionButtons(action, rowItem, is_selected_button) { - const buttons = { - "enable": { - label: "Enable", - mode: "enable" - }, - "disable": { - label: "Disable", - mode: "disable" - }, - - "update": { - label: "Update", - mode: "update" - }, - "try-update": { - label: "Try update", - mode: "update" - }, - - "try-fix": { - label: "Try fix", - mode: "fix" - }, - - "reinstall": { - label: "Reinstall", - mode: "reinstall" - }, - - "install": { - label: "Install", - mode: "install" - }, - - "try-install": { - label: "Try install", - mode: "install" - }, - - "uninstall": { - label: "Uninstall", - mode: "uninstall" - }, - - "switch": { - label: "Switch Ver", - mode: "switch" - } - } - - const installGroups = { - "disabled": ["enable", "switch", "uninstall"], - "updatable": ["update", "switch", "disable", "uninstall"], - "import-fail": ["try-fix", "switch", "disable", "uninstall"], - "enabled": ["try-update", "switch", "disable", "uninstall"], - "not-installed": ["install"], - 'unknown': ["try-install"], - "invalid-installation": ["reinstall"], - } - - if (!installGroups.updatable) { - installGroups.enabled = installGroups.enabled.filter(it => it !== "try-update"); - } - - if (rowItem?.title === "ComfyUI-Manager") { - installGroups.enabled = installGroups.enabled.filter(it => it !== "disable" && it !== "uninstall" && it !== "switch"); - } - - let list = installGroups[action]; - - if(is_selected_button || rowItem?.version === "unknown") { - list = list.filter(it => it !== "switch"); - } - - if (!list) { - return ""; - } - - return list.map(id => { - const bt = buttons[id]; - return ``; - }).join(""); - } - - getButton(target) { - if(!target) { - return; - } - const mode = target.getAttribute("mode"); - if (!mode) { - return; - } - const group = target.getAttribute("group"); - if (!group) { - return; - } - return { - group, - mode, - target, - label: target.innerText - } - } - - bindEvents() { - const eventsMap = { - ".cn-manager-filter": { - change: (e) => { - - if (this.grid) { - this.grid.selectAll(false); - } - - const value = e.target.value - this.filter = value; - const item = this.getFilterItem(value); - if (item && (!item.hasData)) { - this.loadData(value); - return; - } - this.updateGrid(); - } - }, - - ".cn-manager-keywords": { - input: (e) => { - const keywords = `${e.target.value}`.trim(); - if (keywords !== this.keywords) { - this.keywords = keywords; - this.updateGrid(); - } - }, - focus: (e) => e.target.select() - }, - - ".cn-manager-selection": { - click: (e) => { - const btn = this.getButton(e.target); - if (btn) { - const nodes = this.selectedMap[btn.group]; - if (nodes) { - this.installNodes(nodes, btn); - } - } - } - }, - - ".cn-manager-back": { - click: (e) => { - this.flyover.hide(true); - this.removeHighlight(); - hidePopover(); - this.close() - manager_instance.show(); - } - }, - - ".cn-manager-restart": { - click: () => { - this.close(); - this.manager_dialog.close(); - rebootAPI(); - } - }, - - ".cn-manager-stop": { - click: () => { - api.fetchApi('/manager/queue/reset'); - infoToast('Cancel', 'Remaining tasks will stop after completing the current task.'); - } - }, - - ".cn-manager-used-in-workflow": { - click: (e) => { - e.target.classList.add("cn-btn-loading"); - this.setFilter(ShowMode.IN_WORKFLOW); - this.loadData(ShowMode.IN_WORKFLOW); - } - }, - - ".cn-manager-check-update": { - click: (e) => { - e.target.classList.add("cn-btn-loading"); - this.setFilter(ShowMode.UPDATE); - this.loadData(ShowMode.UPDATE); - } - }, - - ".cn-manager-check-missing": { - click: (e) => { - e.target.classList.add("cn-btn-loading"); - this.setFilter(ShowMode.MISSING); - this.loadData(ShowMode.MISSING); - } - }, - - ".cn-manager-install-url": { - click: async (e) => { - const url = await customPrompt("Please enter the URL of the Git repository to install", ""); - if (url !== null) { - install_via_git_url(url, this.manager_dialog); - } - } - } - }; - Object.keys(eventsMap).forEach(selector => { - const target = this.element.querySelector(selector); - if (target) { - const events = eventsMap[selector]; - if (events) { - Object.keys(events).forEach(type => { - target.addEventListener(type, events[type]); - }); - } - } - }); - - } - - // =========================================================================================== - - initGrid() { - const container = this.element.querySelector(".cn-manager-grid"); - const grid = new TG.Grid(container); - this.grid = grid; - - this.flyover = this.createFlyover(container); - - let prevViewRowsLength = -1; - grid.bind('onUpdated', (e, d) => { - const viewRows = grid.viewRows; - prevViewRowsLength = viewRows.length; - this.showStatus(`${prevViewRowsLength.toLocaleString()} custom nodes`); - }); - - grid.bind('onSelectChanged', (e, changes) => { - this.renderSelected(); - }); - - grid.bind("onColumnWidthChanged", (e, columnItem) => { - storeColumnWidth(gridId, columnItem) - }); - - grid.bind('onClick', (e, d) => { - - this.addHighlight(d.rowItem); - - if (d.columnItem.id === "nodes") { - this.showNodes(d); - return; - } - - const btn = this.getButton(d.e.target); - if (btn) { - const item = this.grid.getRowItemBy("hash", d.rowItem.hash); - - const { target, label, mode} = btn; - if((mode === "install" || mode === "switch" || mode == "enable") && item.originalData.version != 'unknown') { - // install after select version via dialog if item is cnr node - this.installNodeWithVersion(d.rowItem, btn, mode == 'enable'); - } else { - this.installNodes([d.rowItem.hash], btn, d.rowItem.title); - } - return; - } - - }); - - // iteration events - this.element.addEventListener("click", (e) => { - if (container === e.target || container.contains(e.target)) { - return; - } - this.removeHighlight(); - }); - // proxy keyboard events - this.element.addEventListener("keydown", (e) => { - if (e.target === this.element) { - grid.containerKeyDownHandler(e); - } - }, true); - - - grid.setOption({ - theme: 'dark', - selectVisible: true, - selectMultiple: true, - selectAllVisible: true, - - textSelectable: true, - scrollbarRound: true, - - frozenColumn: 1, - rowNotFound: "No Results", - - rowHeight: 40, - bindWindowResize: true, - bindContainerResize: true, - - cellResizeObserver: (rowItem, columnItem) => { - const autoHeightColumns = ['title', 'action', 'description', "alternatives"]; - return autoHeightColumns.includes(columnItem.id) - }, - - // updateGrid handler for filter and keywords - rowFilter: (rowItem) => { - - const searchableColumns = ["title", "author", "description"]; - if (this.hasAlternatives()) { - searchableColumns.push("alternatives"); - } - - let shouldShown = grid.highlightKeywordsFilter(rowItem, searchableColumns, this.keywords); - - if (shouldShown) { - if(this.filter && rowItem.filterTypes) { - shouldShown = rowItem.filterTypes.includes(this.filter); - } - } - - return shouldShown; - } - }); - - } - - hasAlternatives() { - return this.filter === ShowMode.ALTERNATIVES - } - - async handleImportFail(rowItem) { - var info; - if(rowItem.version == 'unknown'){ - info = { - 'url': rowItem.originalData.files[0] - }; - } - else{ - info = { - 'cnr_id': rowItem.originalData.id - }; - } - - const response = await api.fetchApi(`/customnode/import_fail_info`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(info) - }); - - let res = await response.json(); - - let title = `Error message occurred while importing the '${rowItem.title}' module.


` - - if(res.code == 400) - { - show_message(title+'The information is not available.') - } - else { - show_message(title+sanitizeHTML(res['msg']).replace(/ /g, ' ').replace(/\n/g, '
')); - } - } - - renderGrid() { - // update theme - const globalStyle = window.getComputedStyle(document.body); - this.colorVars = { - bgColor: globalStyle.getPropertyValue('--comfy-menu-bg'), - borderColor: globalStyle.getPropertyValue('--border-color') - } - - const colorPalette = this.app.ui.settings.settingsValues['Comfy.ColorPalette']; - this.colorPalette = colorPalette; - Array.from(this.element.classList).forEach(cn => { - if (cn.startsWith("cn-manager-")) { - this.element.classList.remove(cn); - } - }); - this.element.classList.add(`cn-manager-${colorPalette}`); - - const options = { - theme: colorPalette === "light" ? "" : "dark" - }; - - - let self = this; - const columns = [{ - id: 'id', - name: 'ID', - width: 50, - align: 'center' - }, { - id: 'title', - name: 'Title', - width: 200, - minWidth: 100, - maxWidth: 500, - classMap: 'cn-pack-name', - formatter: (title, rowItem, columnItem) => { - const container = document.createElement('div'); - - if (rowItem.action === 'invalid-installation') { - const invalidTag = document.createElement('span'); - invalidTag.style.color = 'red'; - invalidTag.innerHTML = '(INVALID)'; - container.appendChild(invalidTag); - } else if (rowItem.action === 'import-fail') { - const button = document.createElement('button'); - button.className = 'cn-btn-import-failed'; - button.innerText = 'IMPORT FAILED ↗'; - button.onclick = () => self.handleImportFail(rowItem); - container.appendChild(button); - container.appendChild(document.createElement('br')); - } - - const link = document.createElement('a'); - if(rowItem.originalData.repository) - link.href = rowItem.originalData.repository; - else - link.href = rowItem.reference; - link.target = '_blank'; - link.innerHTML = `${title}`; - link.title = rowItem.originalData.id; - container.appendChild(link); - - return container; - } - }, { - id: 'version', - name: 'Version', - width: 100, - minWidth: 80, - maxWidth: 300, - classMap: 'cn-pack-version', - formatter: (version, rowItem, columnItem) => { - if(!version) { - return; - } - if(rowItem.cnr_latest && version != rowItem.cnr_latest) { - if(version == 'nightly') { - return `
${version}
[${rowItem.cnr_latest}]
`; - } - return `
${version}
[↑${rowItem.cnr_latest}]
`; - } - return version; - } - }, { - id: 'action', - name: 'Action', - width: 130, - minWidth: 110, - maxWidth: 200, - sortable: false, - align: 'center', - formatter: (action, rowItem, columnItem) => { - if (rowItem.restart) { - return `Restart Required`; - } - const buttons = this.getActionButtons(action, rowItem); - return `
${buttons}
`; - } - }, { - id: "nodes", - name: "Nodes", - width: 100, - formatter: (v, rowItem, columnItem) => { - if (!rowItem.nodes) { - return ''; - } - const list = [`
`]; - list.push(`
${rowItem.nodes} node${(rowItem.nodes>1?'s':'')}
`); - if (rowItem.conflicts) { - list.push(`
${rowItem.conflicts} conflict${(rowItem.conflicts>1?'s':'')}
`); - } - list.push('
'); - return list.join(""); - } - }, { - id: "alternatives", - name: "Alternatives", - width: 400, - maxWidth: 5000, - invisible: !this.hasAlternatives(), - classMap: 'cn-pack-desc' - }, { - id: 'description', - name: 'Description', - width: 400, - maxWidth: 5000, - classMap: 'cn-pack-desc' - }, { - id: 'author', - name: 'Author', - width: 120, - classMap: "cn-pack-author", - formatter: (author, rowItem, columnItem) => { - if (rowItem.trust) { - return `✅ ${author}`; - } - return author; - } - }, { - id: 'stars', - name: '★', - align: 'center', - classMap: "cn-pack-stars", - formatter: (stars) => { - if (stars < 0) { - return 'N/A'; - } - if (typeof stars === 'number') { - return stars.toLocaleString(); - } - return stars; - } - }, { - id: 'last_update', - name: 'Last Update', - align: 'center', - type: 'date', - width: 100, - classMap: "cn-pack-last-update", - formatter: (last_update) => { - if (last_update < 0) { - return 'N/A'; - } - const ago = getTimeAgo(last_update); - const short = `${last_update}`.split(' ')[0]; - return `${short}`; - } - }]; - - restoreColumnWidth(gridId, columns); - - const rows_values = Object.values(this.custom_nodes); - rows_values.sort((a, b) => { - if (a.version == 'unknown' && b.version != 'unknown') return 1; - if (a.version != 'unknown' && b.version == 'unknown') return -1; - - if (a.stars !== b.stars) { - return b.stars - a.stars; - } - - if (a.last_update !== b.last_update) { - return new Date(b.last_update) - new Date(a.last_update); - } - - return 0; - }); - - rows_values.forEach((it, i) => { - it.id = i + 1; - }); - - this.grid.setData({ - options: options, - rows: rows_values, - columns: columns - }); - - this.grid.render(); - } - - updateGrid() { - if (this.grid) { - this.grid.update(); - if (this.hasAlternatives()) { - this.grid.showColumn("alternatives"); - } else { - this.grid.hideColumn("alternatives"); - } - } - } - - addHighlight(rowItem) { - this.removeHighlight(); - if (this.grid && rowItem) { - this.grid.setRowState(rowItem, 'highlight', true); - this.highlightRow = rowItem; - } - } - - removeHighlight() { - if (this.grid && this.highlightRow) { - this.grid.setRowState(this.highlightRow, 'highlight', false); - this.highlightRow = null; - } - } - - // =========================================================================================== - - getWidgetType(type, inputName) { - if (type === 'COMBO') { - return 'COMBO' - } - const widgets = app.widgets; - if (`${type}:${inputName}` in widgets) { - return `${type}:${inputName}` - } - if (type in widgets) { - return type - } - } - - createNodePreview(nodeItem) { - // console.log(nodeItem); - const list = [`
-
-
${nodeItem.name}
-
Preview
-
`]; - - // Node slot I/O - const inputList = []; - nodeItem.input_order.required?.map(name => { - inputList.push({ - name - }); - }) - nodeItem.input_order.optional?.map(name => { - inputList.push({ - name, - optional: true - }); - }); - - const slotInputList = []; - const widgetInputList = []; - const inputMap = Object.assign({}, nodeItem.input.optional, nodeItem.input.required); - inputList.forEach(it => { - const inputName = it.name; - const _inputData = inputMap[inputName]; - let type = _inputData[0]; - let options = _inputData[1] || {}; - if (Array.isArray(type)) { - options.default = type[0]; - type = 'COMBO'; - } - it.type = type; - it.options = options; - - // convert force/default inputs - if (options.forceInput || options.defaultInput) { - slotInputList.push(it); - return; - } - - const widgetType = this.getWidgetType(type, inputName); - if (widgetType) { - it.default = options.default; - widgetInputList.push(it); - } else { - slotInputList.push(it); - } - }); - - const outputList = nodeItem.output.map((type, i) => { - return { - type, - name: nodeItem.output_name[i], - list: nodeItem.output_is_list[i] - } - }); - - // dark - const colorMap = { - "CLIP": "#FFD500", - "CLIP_VISION": "#A8DADC", - "CLIP_VISION_OUTPUT": "#ad7452", - "CONDITIONING": "#FFA931", - "CONTROL_NET": "#6EE7B7", - "IMAGE": "#64B5F6", - "LATENT": "#FF9CF9", - "MASK": "#81C784", - "MODEL": "#B39DDB", - "STYLE_MODEL": "#C2FFAE", - "VAE": "#FF6E6E", - "NOISE": "#B0B0B0", - "GUIDER": "#66FFFF", - "SAMPLER": "#ECB4B4", - "SIGMAS": "#CDFFCD", - "TAESD": "#DCC274" - } - - const inputHtml = slotInputList.map(it => { - const color = colorMap[it.type] || "gray"; - const optional = it.optional ? " cn-preview-optional" : "" - return `
-
- ${it.name} -
`; - }).join(""); - - const outputHtml = outputList.map(it => { - const color = colorMap[it.type] || "gray"; - const grid = it.list ? " cn-preview-grid" : ""; - return `
- ${it.name} -
-
`; - }).join(""); - - list.push(`
-
${inputHtml}
-
${outputHtml}
-
`); - - // Node widget inputs - if (widgetInputList.length) { - list.push(`
`); - - // console.log(widgetInputList); - widgetInputList.forEach(it => { - - let value = it.default; - if (typeof value === "object" && value && Object.prototype.hasOwnProperty.call(value, "content")) { - value = value.content; - } - if (typeof value === "undefined" || value === null) { - value = ""; - } else { - value = `${value}`; - } - - if ( - (it.type === "STRING" && (value || it.options.multiline)) - || it.type === "MARKDOWN" - ) { - if (value) { - value = value.replace(/\r?\n/g, "
") - } - list.push(`
${value || it.name}
`); - return; - } - - list.push(`
-
${it.name}
-
${value}
-
`); - }); - list.push(`
`); - } - - if (nodeItem.description) { - list.push(`
${nodeItem.description}
`) - } - - return list.join(""); - } - - showNodePreview(target) { - const nodeName = target.innerText; - const nodeItem = this.nodeMap[nodeName]; - if (!nodeItem) { - this.hideNodePreview(); - return; - } - const html = this.createNodePreview(nodeItem); - showPopover(target, html, "cn-preview cn-preview-"+this.colorPalette, { - positions: ['left'], - bgColor: this.colorVars.bgColor, - borderColor: this.colorVars.borderColor - }) - } - - hideNodePreview() { - hidePopover(); - } - - createFlyover(container) { - const $flyover = document.createElement("div"); - $flyover.className = "cn-flyover"; - $flyover.innerHTML = `
-
${icons.arrowRight}
-
-
${icons.close}
-
-
` - container.appendChild($flyover); - - const $flyoverTitle = $flyover.querySelector(".cn-flyover-title"); - const $flyoverBody = $flyover.querySelector(".cn-flyover-body"); - - let width = '50%'; - let visible = false; - - let timeHide; - const closeHandler = (e) => { - if ($flyover === e.target || $flyover.contains(e.target)) { - return; - } - clearTimeout(timeHide); - timeHide = setTimeout(() => { - flyover.hide(); - }, 100); - } - - const hoverHandler = (e) => { - if(e.type === "mouseenter") { - if(e.target.classList.contains("cn-nodes-name")) { - this.showNodePreview(e.target); - } - return; - } - this.hideNodePreview(); - } - - const displayHandler = () => { - if (visible) { - $flyover.classList.remove("cn-slide-in-right"); - } else { - $flyover.classList.remove("cn-slide-out-right"); - $flyover.style.width = '0px'; - $flyover.style.display = "none"; - } - } - - const flyover = { - show: (titleHtml, bodyHtml) => { - clearTimeout(timeHide); - this.element.removeEventListener("click", closeHandler); - $flyoverTitle.innerHTML = titleHtml; - $flyoverBody.innerHTML = bodyHtml; - $flyover.style.display = "block"; - $flyover.style.width = width; - if(!visible) { - $flyover.classList.add("cn-slide-in-right"); - } - visible = true; - setTimeout(() => { - this.element.addEventListener("click", closeHandler); - }, 100); - }, - hide: (now) => { - visible = false; - this.element.removeEventListener("click", closeHandler); - if(now) { - displayHandler(); - return; - } - $flyover.classList.add("cn-slide-out-right"); - } - } - - $flyover.addEventListener("animationend", (e) => { - displayHandler(); - }); - - $flyover.addEventListener("mouseenter", hoverHandler, true); - $flyover.addEventListener("mouseleave", hoverHandler, true); - - $flyover.addEventListener("click", (e) => { - - if(e.target.classList.contains("cn-nodes-name")) { - const nodeName = e.target.innerText; - const nodeItem = this.nodeMap[nodeName]; - if (!nodeItem) { - copyText(nodeName).then((res) => { - if (res) { - e.target.setAttribute("action", "Copied"); - e.target.classList.add("action"); - setTimeout(() => { - e.target.classList.remove("action"); - e.target.removeAttribute("action"); - }, 1000); - } - }); - return; - } - - const [x, y, w, h] = app.canvas.ds.visible_area; - const dpi = Math.max(window.devicePixelRatio ?? 1, 1); - const node = window.LiteGraph?.createNode( - nodeItem.name, - nodeItem.display_name, - { - pos: [x + (w-300) / dpi / 2, y] - } - ); - if (node) { - app.graph.add(node); - e.target.setAttribute("action", "Added to Workflow"); - e.target.classList.add("action"); - setTimeout(() => { - e.target.classList.remove("action"); - e.target.removeAttribute("action"); - }, 1000); - } - - return; - } - if(e.target.classList.contains("cn-nodes-pack")) { - const hash = e.target.getAttribute("hash"); - const rowItem = this.grid.getRowItemBy("hash", hash); - //console.log(rowItem); - this.grid.scrollToRow(rowItem); - this.addHighlight(rowItem); - return; - } - if(e.target.classList.contains("cn-flyover-close")) { - flyover.hide(); - return; - } - }); - - return flyover; - } - - showNodes(d) { - const nodesList = d.rowItem.nodesList; - if (!nodesList) { - return; - } - - const rowItem = d.rowItem; - const isNotInstalled = rowItem.action == "not-installed"; - - let titleHtml = `
${rowItem.title}
`; - if (isNotInstalled) { - titleHtml += '
Not Installed
' - } - - const list = []; - list.push(`
`); - - nodesList.forEach((it, i) => { - let rowClass = 'cn-nodes-row' - if (it.conflicts) { - rowClass += ' cn-nodes-conflict'; - } - - list.push(`
`); - list.push(`
${i+1}
`); - list.push(`
${it.name}
`); - - if (it.conflicts) { - list.push(`
${icons.conflicts}
Conflict with${it.conflicts.map(c => { - return `
${c.title}
`; - }).join(",")}
`); - } - list.push(`
`); - }); - - list.push("
"); - const bodyHtml = list.join(""); - - this.flyover.show(titleHtml, bodyHtml); - } - - async loadNodes(node_packs) { - const mode = manager_instance.datasrc_combo.value; - this.showStatus(`Loading node mappings (${mode}) ...`); - const res = await fetchData(`/customnode/getmappings?mode=${mode}`); - if (res.error) { - console.log(res.error); - return; - } - - const data = res.data; - - const findNode = (k, title) => { - let item = node_packs[k]; - if (item) { - return item; - } - - // git url - if (k.includes("/")) { - const gitName = k.split("/").pop(); - item = node_packs[gitName]; - if (item) { - return item; - } - } - - return node_packs[title]; - } - - const conflictsMap = {}; - - // add nodes data - Object.keys(data).forEach(k => { - const [nodes, metadata] = data[k]; - if (nodes?.length) { - const title = metadata?.title_aux; - const nodeItem = findNode(k, title); - if (nodeItem) { - - // deduped - const eList = Array.from(new Set(nodes)); - - nodeItem.nodes = eList.length; - const nodesMap = {}; - eList.forEach(extName => { - nodesMap[extName] = { - name: extName - }; - let cList = conflictsMap[extName]; - if(!cList) { - cList = []; - conflictsMap[extName] = cList; - } - cList.push(nodeItem.key); - }); - nodeItem.nodesMap = nodesMap; - } else { - // should be removed - // console.log("not found", k, title, nodes) - } - } - }); - - // calculate conflicts data - Object.keys(conflictsMap).forEach(extName => { - const cList = conflictsMap[extName]; - if(cList.length <= 1) { - return; - } - cList.forEach(key => { - const nodeItem = node_packs[key]; - const extItem = nodeItem.nodesMap[extName]; - if(!extItem.conflicts) { - extItem.conflicts = [] - } - const conflictsList = cList.filter(k => k !== key); - conflictsList.forEach(k => { - const nItem = node_packs[k]; - extItem.conflicts.push({ - key: k, - title: nItem.title, - hash: nItem.hash - }) - - }) - }) - }) - - Object.values(node_packs).forEach(nodeItem => { - if (nodeItem.nodesMap) { - nodeItem.nodesList = Object.values(nodeItem.nodesMap); - nodeItem.conflicts = nodeItem.nodesList.filter(it => it.conflicts).length; - } - }) - - } - - // =========================================================================================== - - renderSelected() { - const selectedList = this.grid.getSelectedRows(); - if (!selectedList.length) { - this.showSelection(""); - return; - } - - const selectedMap = {}; - selectedList.forEach(item => { - let type = item.action; - if (item.restart) { - type = "Restart Required"; - } - if (selectedMap[type]) { - selectedMap[type].push(item.hash); - } else { - selectedMap[type] = [item.hash]; - } - }); - - this.selectedMap = selectedMap; - - const list = []; - Object.keys(selectedMap).forEach(v => { - const filterItem = this.getFilterItem(v); - list.push(`
- Selected ${selectedMap[v].length} ${filterItem ? filterItem.label : v} - ${this.grid.hasMask ? "" : this.getActionButtons(v, null, true)} -
`); - }); - - this.showSelection(list.join("")); - } - - focusInstall(item, mode) { - const cellNode = this.grid.getCellNode(item, "action"); - if (cellNode) { - const cellBtn = cellNode.querySelector(`button[mode="${mode}"]`); - if (cellBtn) { - cellBtn.classList.add("cn-btn-loading"); - return true - } - } - } - - async installNodeWithVersion(rowItem, btn, is_enable) { - let hash = rowItem.hash; - let title = rowItem.title; - - const item = this.grid.getRowItemBy("hash", hash); - - let node_id = item.originalData.id; - - this.showLoading(); - let res; - if(is_enable) { - res = await api.fetchApi(`/customnode/disabled_versions/${node_id}`, { cache: "no-store" }); - } - else { - res = await api.fetchApi(`/customnode/versions/${node_id}`, { cache: "no-store" }); - } - this.hideLoading(); - - if(res.status == 200) { - let obj = await res.json(); - - let versions = []; - let default_version; - let version_cnt = 0; - - if(!is_enable) { - - if(rowItem.cnr_latest != rowItem.originalData.active_version && obj.length > 0) { - versions.push('latest'); - } - - if(rowItem.originalData.active_version != 'nightly') { - versions.push('nightly'); - default_version = 'nightly'; - version_cnt++; - } - } - - for(let v of obj) { - if(rowItem.originalData.active_version != v.version) { - default_version = v.version; - versions.push(v.version); - version_cnt++; - } - } - - this.showVersionSelectorDialog(versions, (selected_version) => { - this.installNodes([hash], btn, title, selected_version); - }); - } - else { - show_message('Failed to fetch versions from ComfyRegistry.'); - } - } - - async installNodes(list, btn, title, selected_version) { - let stats = await api.fetchApi('/manager/queue/status'); - stats = await stats.json(); - if(stats.is_processing) { - customAlert(`[ComfyUI-Manager] There are already tasks in progress. Please try again after it is completed. (${stats.done_count}/${stats.total_count})`); - return; - } - - const { target, label, mode} = btn; - - if(mode === "uninstall") { - title = title || `${list.length} custom nodes`; - - const confirmed = await customConfirm(`Are you sure uninstall ${title}?`); - if (!confirmed) { - return; - } - } - - if(mode === "reinstall") { - title = title || `${list.length} custom nodes`; - - const confirmed = await customConfirm(`Are you sure reinstall ${title}?`); - if (!confirmed) { - return; - } - } - - target.classList.add("cn-btn-loading"); - this.showError(""); - - let needRestart = false; - let errorMsg = ""; - - await api.fetchApi('/manager/queue/reset'); - - let target_items = []; - - for (const hash of list) { - const item = this.grid.getRowItemBy("hash", hash); - target_items.push(item); - - if (!item) { - errorMsg = `Not found custom node: ${hash}`; - break; - } - - this.grid.scrollRowIntoView(item); - - if (!this.focusInstall(item, mode)) { - this.grid.onNextUpdated(() => { - this.focusInstall(item, mode); - }); - } - - this.showStatus(`${label} ${item.title} ...`); - - const data = item.originalData; - data.selected_version = selected_version; - data.channel = this.channel; - data.mode = this.mode; - data.ui_id = hash; - - let install_mode = mode; - if(mode == 'switch') { - install_mode = 'install'; - } - - // don't post install if install_mode == 'enable' - data.skip_post_install = install_mode == 'enable'; - let api_mode = install_mode; - if(install_mode == 'enable') { - api_mode = 'install'; - } - - if(install_mode == 'reinstall') { - api_mode = 'reinstall'; - } - - const res = await api.fetchApi(`/manager/queue/${api_mode}`, { - method: 'POST', - body: JSON.stringify(data) - }); - - if (res.status != 200) { - errorMsg = `'${item.title}': `; - - if(res.status == 403) { - try { - const data = await res.json(); - if(data.error === 'comfyui_outdated') { - errorMsg += `ComfyUI version is outdated. Please update ComfyUI to use Manager normally.\n`; - } else { - errorMsg += `This action is not allowed with this security level configuration.\n`; - } - } catch { - errorMsg += `This action is not allowed with this security level configuration.\n`; - } - } else if(res.status == 404) { - errorMsg += `With the current security level configuration, only custom nodes from the "default channel" can be installed.\n`; - } else { - errorMsg += await res.text() + '\n'; - } - - break; - } - } - - this.install_context = {btn: btn, targets: target_items}; - - if(errorMsg) { - this.showError(errorMsg); - show_message("[Installation Errors]\n"+errorMsg); - - // reset - for(let k in target_items) { - const item = target_items[k]; - this.grid.updateCell(item, "action"); - } - } - else { - await api.fetchApi('/manager/queue/start'); - this.showStop(); - showTerminal(); - } - } - - async onQueueStatus(event) { - let self = CustomNodesManager.instance; - if(event.detail.status == 'in_progress' && event.detail.ui_target == 'nodepack_manager') { - const hash = event.detail.target; - - const item = self.grid.getRowItemBy("hash", hash); - - item.restart = true; - self.restartMap[item.hash] = true; - self.grid.updateCell(item, "action"); - self.grid.setRowSelected(item, false); - } - else if(event.detail.status == 'done') { - self.hideStop(); - self.onQueueCompleted(event.detail); - } - } - - async onQueueCompleted(info) { - let result = info.nodepack_result; - - if(result.length == 0) { - return; - } - - let self = CustomNodesManager.instance; - - if(!self.install_context) { - return; - } - - const { target, label, mode } = self.install_context.btn; - target.classList.remove("cn-btn-loading"); - - let errorMsg = ""; - - for(let hash in result){ - let v = result[hash]; - - if(v != 'success' && v != 'skip') - errorMsg += v+'\n'; - } - - for(let k in self.install_context.targets) { - let item = self.install_context.targets[k]; - self.grid.updateCell(item, "action"); - } - - if (errorMsg) { - self.showError(errorMsg); - show_message("Installation Error:\n"+errorMsg); - } else { - self.showStatus(`${label} ${result.length} custom node(s) successfully`); - } - - self.showRestart(); - self.showMessage(`To apply the installed/updated/disabled/enabled custom node, please restart ComfyUI. And refresh browser.`, "red"); - - infoToast(`[ComfyUI-Manager] All node pack tasks in the queue have been completed.\n${info.done_count}/${info.total_count}`); - self.install_context = undefined; - } - - // =========================================================================================== - - getNodesInWorkflow() { - let usedGroupNodes = new Set(); - let allUsedNodes = {}; - const visitedGraphs = new Set(); - - const visitGraph = (graph) => { - if (!graph || visitedGraphs.has(graph)) return; - visitedGraphs.add(graph); - - 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; - } - }; - - visitGraph(app.graph); - - for(let k of usedGroupNodes) { - let subnodes = app.graph.extra.groupNodes[k]?.nodes; - - if(subnodes) { - for(let k2 in subnodes) { - let node = subnodes[k2]; - allUsedNodes[node.type] = node; - } - } - } - - return allUsedNodes; - } - - async getMissingNodes() { - let unresolved_missing_nodes = new Set(); - let hashMap = {}; - let allUsedNodes = this.getNodesInWorkflow(); - - const registered_nodes = new Set(); - for (let i in LiteGraph.registered_node_types) { - registered_nodes.add(LiteGraph.registered_node_types[i].type); - } - - let unresolved_aux_ids = {}; - let outdated_comfyui = false; - let unresolved_cnr_list = []; - - for(let k in allUsedNodes) { - let node = allUsedNodes[k]; - - if(!registered_nodes.has(node.type)) { - // missing node - if(node.properties.cnr_id) { - if(node.properties.cnr_id == 'comfy-core') { - outdated_comfyui = true; - } - - let item = this.custom_nodes[node.properties.cnr_id]; - if(item) { - hashMap[item.hash] = true; - } - else { - console.log(`CM: cannot find '${node.properties.cnr_id}' from cnr list.`); - unresolved_aux_ids[node.properties.cnr_id] = node.type; - unresolved_cnr_list.push(node.properties.cnr_id); - } - } - else if(node.properties.aux_id) { - unresolved_aux_ids[node.properties.aux_id] = node.type; - } - else { - unresolved_missing_nodes.add(node.type); - } - } - } - - - if(unresolved_cnr_list.length > 0) { - let error_msg = "Failed to find the following ComfyRegistry list.\nThe cache may be outdated, or the nodes may have been removed from ComfyRegistry.
"; - for(let i in unresolved_cnr_list) { - error_msg += '
  • '+unresolved_cnr_list[i]+'
  • '; - } - - show_message(error_msg); - } - - if(outdated_comfyui) { - customAlert('ComfyUI is outdated, so some built-in nodes cannot be used.'); - } - - if(Object.keys(unresolved_aux_ids).length > 0) { - // building aux_id to nodepack map - let aux_id_to_pack = {}; - for(let k in this.custom_nodes) { - let nodepack = this.custom_nodes[k]; - let aux_id; - if(nodepack.repository?.startsWith('https://github.com')) { - aux_id = nodepack.repository.split('/').slice(-2).join('/'); - aux_id_to_pack[aux_id] = nodepack; - } - else if(nodepack.repository) { - aux_id = nodepack.repository.split('/').slice(-1); - aux_id_to_pack[aux_id] = nodepack; - } - } - - // resolving aux_id - for(let k in unresolved_aux_ids) { - let nodepack = aux_id_to_pack[k]; - if(nodepack) { - hashMap[nodepack.hash] = true; - } - else { - unresolved_missing_nodes.add(unresolved_aux_ids[k]); - } - } - } - - if(unresolved_missing_nodes.size > 0) { - await this.getMissingNodesLegacy(hashMap, unresolved_missing_nodes); - } - - return hashMap; - } - - async getMissingNodesLegacy(hashMap, missing_nodes) { - const mode = manager_instance.datasrc_combo.value; - this.showStatus(`Loading missing nodes (${mode}) ...`); - const res = await fetchData(`/customnode/getmappings?mode=${mode}`); - if (res.error) { - this.showError(`Failed to get custom node mappings: ${res.error}`); - return; - } - - const mappings = res.data; - - // build regex->url map - const regex_to_pack = []; - for(let k in this.custom_nodes) { - let node = this.custom_nodes[k]; - - if(node.nodename_pattern) { - regex_to_pack.push({ - regex: new RegExp(node.nodename_pattern), - url: node.files[0] - }); - } - } - - // build name->url map - const name_to_packs = {}; - for (const url in mappings) { - const names = mappings[url]; - - for(const name in names[0]) { - let v = name_to_packs[names[0][name]]; - if(v == undefined) { - v = []; - name_to_packs[names[0][name]] = v; - } - v.push(url); - } - } - - let unresolved_missing_nodes = new Set(); - for (let node_type of missing_nodes) { - const packs = name_to_packs[node_type.trim()]; - if(packs) - packs.forEach(url => { - unresolved_missing_nodes.add(url); - }); - else { - for(let j in regex_to_pack) { - if(regex_to_pack[j].regex.test(node_type)) { - unresolved_missing_nodes.add(regex_to_pack[j].url); - } - } - } - } - - for(let k in this.custom_nodes) { - let item = this.custom_nodes[k]; - - if(unresolved_missing_nodes.has(item.id)) { - hashMap[item.hash] = true; - } - else if (item.files?.some(file => unresolved_missing_nodes.has(file))) { - hashMap[item.hash] = true; - } - } - - return hashMap; - } - - async getFavorites() { - const hashMap = {}; - for(let k in this.custom_nodes) { - let item = this.custom_nodes[k]; - if(item.is_favorite) - hashMap[item.hash] = true; - } - - return hashMap; - } - - async getNodepackInWorkflow() { - let allUsedNodes = this.getNodesInWorkflow(); - - // building aux_id to nodepack map - let aux_id_to_pack = {}; - for(let k in this.custom_nodes) { - let nodepack = this.custom_nodes[k]; - let aux_id; - if(nodepack.repository?.startsWith('https://github.com')) { - aux_id = nodepack.repository.split('/').slice(-2).join('/'); - aux_id_to_pack[aux_id] = nodepack; - } - else if(nodepack.repository) { - aux_id = nodepack.repository.split('/').slice(-1); - aux_id_to_pack[aux_id] = nodepack; - } - } - - const hashMap = {}; - for(let k in allUsedNodes) { - var item; - if(allUsedNodes[k].properties.cnr_id) { - item = this.custom_nodes[allUsedNodes[k].properties.cnr_id]; - } - else if(allUsedNodes[k].properties.aux_id) { - item = aux_id_to_pack[allUsedNodes[k].properties.aux_id]; - } - - if(item) - hashMap[item.hash] = true; - } - - return hashMap; - } - - async getAlternatives() { - const mode = manager_instance.datasrc_combo.value; - this.showStatus(`Loading alternatives (${mode}) ...`); - const res = await fetchData(`/customnode/alternatives?mode=${mode}`); - if (res.error) { - this.showError(`Failed to get alternatives: ${res.error}`); - return []; - } - - const hashMap = {}; - const items = res.data; - - for(let i in items) { - let item = items[i]; - let custom_node = this.custom_nodes[i]; - - if (!custom_node) { - console.log(`Not found custom node: ${item.id}`); - continue; - } - - const tags = `${item.tags}`.split(",").map(tag => { - return `
    ${tag.trim()}
    `; - }).join(""); - - hashMap[custom_node.hash] = { - alternatives: `
    ${tags}
    ${item.description}` - } - - } - - return hashMap; - } - - async loadData(show_mode = ShowMode.NORMAL) { - const isElectron = 'electronAPI' in window; - - this.show_mode = show_mode; - console.log("Show mode:", show_mode); - - this.showLoading(); - - const mode = manager_instance.datasrc_combo.value; - this.showStatus(`Loading custom nodes (${mode}) ...`); - - const skip_update = this.show_mode === ShowMode.UPDATE ? "" : "&skip_update=true"; - - if(this.show_mode === ShowMode.UPDATE) { - infoToast('Fetching updated information. This may take some time if many custom nodes are installed.'); - } - - const res = await fetchData(`/customnode/getlist?mode=${mode}${skip_update}`); - if (res.error) { - this.showError("Failed to get custom node list."); - this.hideLoading(); - return; - } - - const { channel, node_packs } = res.data; - - if(isElectron) { - delete node_packs['comfyui-manager']; - } - - this.channel = channel; - this.mode = mode; - this.custom_nodes = node_packs; - - if(this.channel !== 'default') { - this.element.querySelector(".cn-manager-channel").innerHTML = `Channel: ${this.channel} (Incomplete list)`; - } - - for (const k in node_packs) { - let item = node_packs[k]; - item.originalData = JSON.parse(JSON.stringify(item)); - if(item.originalData.id == undefined) { - item.originalData.id = k; - } - item.key = k; - item.hash = md5(k); - } - - await this.loadNodes(node_packs); - - const filterItem = this.getFilterItem(this.show_mode); - if(filterItem) { - let hashMap; - if(this.show_mode == ShowMode.UPDATE) { - hashMap = {}; - for (const k in node_packs) { - let it = node_packs[k]; - if (it['update-state'] === "true") { - hashMap[it.hash] = true; - } - } - } else if(this.show_mode == ShowMode.MISSING) { - hashMap = await this.getMissingNodes(); - } else if(this.show_mode == ShowMode.ALTERNATIVES) { - hashMap = await this.getAlternatives(); - } else if(this.show_mode == ShowMode.FAVORITES) { - hashMap = await this.getFavorites(); - } else if(this.show_mode == ShowMode.IN_WORKFLOW) { - hashMap = await this.getNodepackInWorkflow(); - } - filterItem.hashMap = hashMap; - - if(this.show_mode != ShowMode.IN_WORKFLOW) { - filterItem.hasData = true; - } - } - - for(let k in node_packs) { - let nodeItem = node_packs[k]; - - if (this.restartMap[nodeItem.hash]) { - nodeItem.restart = true; - } - - if(nodeItem['update-state'] == "true") { - nodeItem.action = 'updatable'; - } - else if(nodeItem['import-fail']) { - nodeItem.action = 'import-fail'; - } - else { - nodeItem.action = nodeItem.state; - } - - if(nodeItem['invalid-installation']) { - nodeItem.action = 'invalid-installation'; - } - - const filterTypes = new Set(); - this.filterList.forEach(filterItem => { - const { value, hashMap } = filterItem; - if (hashMap) { - const hashData = hashMap[nodeItem.hash] - if (hashData) { - filterTypes.add(value); - if (value === ShowMode.UPDATE) { - nodeItem['update-state'] = "true"; - } - if (value === ShowMode.MISSING) { - nodeItem['missing-node'] = "true"; - } - if (typeof hashData === "object") { - Object.assign(nodeItem, hashData); - } - } - } else { - if (nodeItem.state === value) { - filterTypes.add(value); - } - - switch(nodeItem.state) { - case "enabled": - filterTypes.add("enabled"); - case "disabled": - filterTypes.add("installed"); - break; - case "not-installed": - filterTypes.add("not-installed"); - break; - } - - if(nodeItem.version != 'unknown') { - filterTypes.add("cnr"); - } - else { - filterTypes.add("unknown"); - } - - if(nodeItem['update-state'] == 'true') { - filterTypes.add("updatable"); - } - - if(nodeItem['import-fail']) { - filterTypes.add("import-fail"); - } - - if(nodeItem['invalid-installation']) { - filterTypes.add("invalid-installation"); - } - } - }); - - nodeItem.filterTypes = Array.from(filterTypes); - } - - this.renderGrid(); - - this.hideLoading(); - - } - - // =========================================================================================== - - showSelection(msg) { - this.element.querySelector(".cn-manager-selection").innerHTML = msg; - } - - showError(err) { - this.showMessage(err, "red"); - } - - showMessage(msg, color) { - if (color) { - msg = `${msg}`; - } - this.element.querySelector(".cn-manager-message").innerHTML = msg; - } - - showStatus(msg, color) { - if (color) { - msg = `${msg}`; - } - this.element.querySelector(".cn-manager-status").innerHTML = msg; - } - - showLoading() { - this.setDisabled(true); - if (this.grid) { - this.grid.showLoading(); - this.grid.showMask({ - opacity: 0.05 - }); - } - } - - hideLoading() { - this.setDisabled(false); - if (this.grid) { - this.grid.hideLoading(); - this.grid.hideMask(); - } - } - - setDisabled(disabled) { - const $close = this.element.querySelector(".cn-manager-close"); - const $restart = this.element.querySelector(".cn-manager-restart"); - const $stop = this.element.querySelector(".cn-manager-stop"); - - const list = [ - ".cn-manager-header input", - ".cn-manager-header select", - ".cn-manager-footer button", - ".cn-manager-selection button" - ].map(s => { - return Array.from(this.element.querySelectorAll(s)); - }) - .flat() - .filter(it => { - return it !== $close && it !== $restart && it !== $stop; - }); - - list.forEach($elem => { - if (disabled) { - $elem.setAttribute("disabled", "disabled"); - } else { - $elem.removeAttribute("disabled"); - } - }); - - Array.from(this.element.querySelectorAll(".cn-btn-loading")).forEach($elem => { - $elem.classList.remove("cn-btn-loading"); - }); - - } - - showRestart() { - this.element.querySelector(".cn-manager-restart").style.display = "block"; - setNeedRestart(true); - } - - showStop() { - this.element.querySelector(".cn-manager-stop").style.display = "block"; - } - - hideStop() { - this.element.querySelector(".cn-manager-stop").style.display = "none"; - } - - setFilter(filterValue) { - let filter = ""; - const filterItem = this.getFilterItem(filterValue); - if(filterItem) { - filter = filterItem.value; - } - this.filter = filter; - this.element.querySelector(".cn-manager-filter").value = filter; - } - - setKeywords(keywords = "") { - this.keywords = keywords; - this.element.querySelector(".cn-manager-keywords").value = keywords; - } - - show(show_mode) { - this.element.style.display = "flex"; - this.element.focus(); - this.setFilter(show_mode); - this.setKeywords(""); - this.showSelection(""); - this.showMessage(""); - this.loadData(show_mode); - } - - close() { - this.element.style.display = "none"; - } - - get isVisible() { - return this.element?.style?.display !== "none"; - } -} \ No newline at end of file diff --git a/js/model-manager.css b/js/model-manager.css deleted file mode 100644 index 6c9ab04a..00000000 --- a/js/model-manager.css +++ /dev/null @@ -1,237 +0,0 @@ -.cmm-manager { - --grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; - z-index: 1099; - width: 80vw; - height: 75vh; - min-height: 30em; - display: flex; - flex-direction: column; - gap: 10px; - color: var(--fg-color); - font-family: arial, sans-serif; - margin: calc(var(--spacing)*2); -} - -.cmm-manager .cmm-flex-auto { - flex: auto; -} - -.cmm-manager button { - font-size: 16px; - color: var(--input-text); - background-color: var(--comfy-input-bg); - border-color: var(--border-color); - margin: 0; - min-width: 100px; -} - -.cmm-manager button:hover { - filter: brightness(125%); -} - -.cmm-manager button:disabled, -.cmm-manager input:disabled, -.cmm-manager select:disabled { - color: gray; -} - -.cmm-manager button:disabled { - background-color: var(--comfy-input-bg); -} - -.cmm-manager .cmm-manager-refresh { - display: none; - background-color: #000080 !important; - color: white; -} - -.cmm-manager .cmm-manager-stop { - display: none; - background-color: #500000; - color: white; -} - -.cmm-manager-header { - display: flex; - flex-wrap: wrap; - gap: 5px; - align-items: center; -} - -.cmm-manager-header label { - display: flex; - gap: 5px; - align-items: center; -} - -.cmm-manager-type, -.cmm-manager-base, -.cmm-manager-filter { - height: 28px; - line-height: 28px; - - cursor: pointer; - padding: 0.5em 0.5em; - border: 1px solid var(--border-color); - border-radius: 6px; - background: var(--comfy-input-bg); -} - -.cmm-manager-type:hover, -.cmm-manager-base:hover, -.cmm-manager-filter:hover { - filter: brightness(125%); -} - -.cmm-manager-keywords { - height: 28px; - line-height: 28px; - padding: 0 5px 0 26px; - background: var(--comfy-input-bg); - background-size: 16px; - background-position: 5px center; - background-repeat: no-repeat; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E"); - - border: 1px solid var(--border-color); - border-radius: 6px; - - outline-color: transparent; -} - -.cmm-manager-status { - padding-left: 10px; -} - -.cmm-manager-grid { - flex: auto; - border: 1px solid var(--border-color); - overflow: hidden; -} - -.cmm-manager-selection { - display: flex; - flex-wrap: wrap; - gap: 10px; - align-items: center; -} - -.cmm-manager-footer { - display: flex; - flex-wrap: wrap; - gap: 10px; - align-items: center; -} - -.cmm-manager-grid .tg-turbogrid { - font-family: var(--grid-font); - font-size: 15px; - background: var(--bg-color); -} - -.cmm-manager-grid .cmm-node-name a { - color: skyblue; - text-decoration: none; - word-break: break-word; -} - -.cmm-manager-grid .cmm-node-desc a { - color: #5555FF; - font-weight: bold; - text-decoration: none; -} - -.cmm-manager-grid .tg-cell a:hover { - text-decoration: underline; -} - -.cmm-icon-passed { - width: 20px; - height: 20px; - position: absolute; - left: calc(50% - 10px); - top: calc(50% - 10px); -} - -.cmm-manager .cmm-btn-enable { - background-color: blue; - color: white; -} - -.cmm-manager .cmm-btn-disable { - background-color: MediumSlateBlue; - color: white; -} - -.cmm-manager .cmm-btn-install { - background-color: black; - color: white; -} - -.cmm-btn-install { - padding: 4px 8px; -} - -.cmm-btn-download { - width: 18px; - height: 18px; - position: absolute; - left: calc(50% - 10px); - top: calc(50% - 10px); - cursor: pointer; - opacity: 0.8; - color: #fff; -} - -.cmm-btn-download:hover { - opacity: 1; -} - -.cmm-manager-light .cmm-btn-download { - color: #000; -} - -@keyframes cmm-btn-loading-bg { - 0% { - left: 0; - } - 100% { - left: -105px; - } -} - -.cmm-manager button.cmm-btn-loading { - position: relative; - overflow: hidden; - border-color: rgb(0 119 207 / 80%); - background-color: var(--comfy-input-bg); -} - -.cmm-manager button.cmm-btn-loading::after { - position: absolute; - top: 0; - left: 0; - content: ""; - width: 500px; - height: 100%; - background-image: repeating-linear-gradient( - -45deg, - rgb(0 119 207 / 30%), - rgb(0 119 207 / 30%) 10px, - transparent 10px, - transparent 15px - ); - animation: cmm-btn-loading-bg 2s linear infinite; -} - -.cmm-manager-light .cmm-node-name a { - color: blue; -} - -.cmm-manager-light .cm-warn-note { - background-color: #ccc !important; -} - -.cmm-manager-light .cmm-btn-install { - background-color: #333; -} \ No newline at end of file diff --git a/js/model-manager.js b/js/model-manager.js deleted file mode 100644 index 0f0de552..00000000 --- a/js/model-manager.js +++ /dev/null @@ -1,824 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { $el } from "../../scripts/ui.js"; -import { - manager_instance, rebootAPI, - fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal, - storeColumnWidth, restoreColumnWidth, loadCss, handle403Response -} from "./common.js"; -import { api } from "../../scripts/api.js"; - -// https://cenfun.github.io/turbogrid/api.html -import TG from "./turbogrid.esm.js"; -import { buildGuiFrameCustomHeader, createSettingsCombo } from "./comfyui-gui-builder.js"; - -loadCss("./model-manager.css"); - -const gridId = "model"; - -const pageHtml = ` -
    -
    -
    -
    - -
    -`; - -export class ModelManager { - static instance = null; - - constructor(app, manager_dialog) { - this.app = app; - this.manager_dialog = manager_dialog; - this.id = "cmm-manager"; - - this.filter = ''; - this.type = ''; - this.base = ''; - this.keywords = ''; - - this.init(); - - api.addEventListener("cm-queue-status", this.onQueueStatus); - } - - init() { - const header = $el("div.cmm-manager-header", {}, [ - createSettingsCombo("Filter", $el("select.cmm-manager-filter")), - createSettingsCombo("Type", $el("select.cmm-manager-type")), - createSettingsCombo("Base", $el("select.cmm-manager-base")), - $el("input.cmm-manager-keywords.p-inputtext.p-component", { type: "search", placeholder: "Search" }), - $el("div.cmm-manager-status"), - $el("div.cmm-flex-auto") - ]); - - const frame = buildGuiFrameCustomHeader( - 'cmm-manager-dialog', // dialog id - header, // custom header element - pageHtml, // dialog content element - this - ); // send this so we can attach close functions - - this.element = frame; - this.initFilter(); - this.bindEvents(); - this.initGrid(); - } - - initFilter() { - - this.filterList = [{ - label: "All", - value: "" - }, { - label: "Installed", - value: "installed" - }, { - label: "Not Installed", - value: "not_installed" - }, { - label: "In Workflow", - value: "in_workflow" - }]; - - this.typeList = [{ - label: "All", - value: "" - }]; - - this.baseList = [{ - label: "All", - value: "" - }]; - - this.updateFilter(); - - } - - updateFilter() { - const $filter = this.element.querySelector(".cmm-manager-filter"); - $filter.innerHTML = this.filterList.map(item => { - const selected = item.value === this.filter ? " selected" : ""; - return `` - }).join(""); - - const $type = this.element.querySelector(".cmm-manager-type"); - $type.innerHTML = this.typeList.map(item => { - const selected = item.value === this.type ? " selected" : ""; - return `` - }).join(""); - - const $base = this.element.querySelector(".cmm-manager-base"); - $base.innerHTML = this.baseList.map(item => { - const selected = item.value === this.base ? " selected" : ""; - return `` - }).join(""); - - } - - bindEvents() { - const eventsMap = { - ".cmm-manager-filter": { - change: (e) => { - this.filter = e.target.value; - this.updateGrid(); - } - }, - ".cmm-manager-type": { - change: (e) => { - this.type = e.target.value; - this.updateGrid(); - } - }, - ".cmm-manager-base": { - change: (e) => { - this.base = e.target.value; - this.updateGrid(); - } - }, - - ".cmm-manager-keywords": { - input: (e) => { - const keywords = `${e.target.value}`.trim(); - if (keywords !== this.keywords) { - this.keywords = keywords; - this.updateGrid(); - } - }, - focus: (e) => e.target.select() - }, - - ".cmm-manager-selection": { - click: (e) => { - const target = e.target; - const mode = target.getAttribute("mode"); - if (mode === "install") { - this.installModels(this.selectedModels, target); - } - } - }, - - ".cmm-manager-refresh": { - click: () => { - app.refreshComboInNodes(); - } - }, - - ".cmm-manager-stop": { - click: () => { - api.fetchApi('/manager/queue/reset'); - infoToast('Cancel', 'Remaining tasks will stop after completing the current task.'); - } - }, - - ".cmm-manager-back": { - click: (e) => { - this.close() - manager_instance.show(); - } - } - }; - Object.keys(eventsMap).forEach(selector => { - const target = this.element.querySelector(selector); - if (target) { - const events = eventsMap[selector]; - if (events) { - Object.keys(events).forEach(type => { - target.addEventListener(type, events[type]); - }); - } - } - }); - } - - // =========================================================================================== - - initGrid() { - const container = this.element.querySelector(".cmm-manager-grid"); - const grid = new TG.Grid(container); - this.grid = grid; - - grid.bind('onUpdated', (e, d) => { - - this.showStatus(`${grid.viewRows.length.toLocaleString()} external models`); - - }); - - grid.bind('onSelectChanged', (e, changes) => { - this.renderSelected(); - }); - - grid.bind("onColumnWidthChanged", (e, columnItem) => { - storeColumnWidth(gridId, columnItem) - }); - - grid.bind('onClick', (e, d) => { - const { rowItem } = d; - const target = d.e.target; - const mode = target.getAttribute("mode"); - if (mode === "install") { - this.installModels([rowItem], target); - } - - }); - - grid.setOption({ - theme: 'dark', - - selectVisible: true, - selectMultiple: true, - selectAllVisible: true, - - textSelectable: true, - scrollbarRound: true, - - frozenColumn: 1, - rowNotFound: "No Results", - - rowHeight: 40, - bindWindowResize: true, - bindContainerResize: true, - - cellResizeObserver: (rowItem, columnItem) => { - const autoHeightColumns = ['name', 'description']; - return autoHeightColumns.includes(columnItem.id) - }, - - // updateGrid handler for filter and keywords - rowFilter: (rowItem) => { - - const searchableColumns = ["name", "type", "base", "description", "filename", "save_path"]; - const models_extensions = ['.ckpt', '.pt', '.pt2', '.bin', '.pth', '.safetensors', '.pkl', '.sft']; - - let shouldShown = grid.highlightKeywordsFilter(rowItem, searchableColumns, this.keywords); - - if (shouldShown) { - if(this.filter) { - if (this.filter == "in_workflow") { - rowItem.in_workflow = null; - if (Array.isArray(app.graph._nodes)) { - app.graph._nodes.forEach((item, i) => { - if (Array.isArray(item.widgets_values)) { - item.widgets_values.forEach((_item, i) => { - if (rowItem.in_workflow === null && _item !== null && models_extensions.includes("." + _item.toString().split('.').pop())) { - let filename = _item.match(/([^\/]+)(?=\.\w+$)/)[0]; - if (grid.highlightKeywordsFilter(rowItem, searchableColumns, filename)) { - rowItem.in_workflow = "True"; - grid.highlightKeywordsFilter(rowItem, searchableColumns, ""); - } - } - }); - } - }); - } - } - return ((this.filter == "installed" && rowItem.installed == "True") || (this.filter == "not_installed" && rowItem.installed == "False") || (this.filter == "in_workflow" && rowItem.in_workflow == "True")); - } - - if(this.type && rowItem.type !== this.type) { - return false; - } - - if(this.base && rowItem.base !== this.base) { - return false; - } - - } - - return shouldShown; - } - }); - - } - - renderGrid() { - - // update theme - const colorPalette = this.app.ui.settings.settingsValues['Comfy.ColorPalette']; - Array.from(this.element.classList).forEach(cn => { - if (cn.startsWith("cmm-manager-")) { - this.element.classList.remove(cn); - } - }); - this.element.classList.add(`cmm-manager-${colorPalette}`); - - const options = { - theme: colorPalette === "light" ? "" : "dark" - }; - - const rows = this.modelList || []; - - const columns = [{ - id: 'id', - name: 'ID', - width: 50, - align: 'center' - }, { - id: 'name', - name: 'Name', - width: 200, - minWidth: 100, - maxWidth: 500, - classMap: 'cmm-node-name', - formatter: function(name, rowItem, columnItem, cellNode) { - return `${name}`; - } - }, { - id: 'installed', - name: 'Install', - width: 130, - minWidth: 110, - maxWidth: 200, - sortable: false, - align: 'center', - formatter: (installed, rowItem, columnItem) => { - if (rowItem.refresh) { - return `Refresh Required`; - } - if (installed === "True") { - return `
    ${icons.passed}
    `; - } - return ``; - } - }, { - id: 'url', - name: '', - width: 50, - sortable: false, - align: 'center', - formatter: (url, rowItem, columnItem) => { - return `${icons.download}`; - } - }, { - id: 'size', - name: 'Size', - width: 100, - formatter: (size) => { - if (typeof size === "number") { - return this.formatSize(size); - } - return size; - } - }, { - id: 'type', - name: 'Type', - width: 100 - }, { - id: 'base', - name: 'Base' - }, { - id: 'description', - name: 'Description', - width: 400, - maxWidth: 5000, - classMap: 'cmm-node-desc' - }, { - id: "save_path", - name: 'Save Path', - width: 200 - }, { - id: 'filename', - name: 'Filename', - width: 200 - }]; - - restoreColumnWidth(gridId, columns); - - this.grid.setData({ - options, - rows, - columns - }); - - this.grid.render(); - - } - - updateGrid() { - if (this.grid) { - this.grid.update(); - } - } - - // =========================================================================================== - - renderSelected() { - const selectedList = this.grid.getSelectedRows(); - if (!selectedList.length) { - this.showSelection(""); - this.selectedModels = []; - return; - } - - this.selectedModels = selectedList; - this.showSelection(`Selected ${selectedList.length} models `); - } - - focusInstall(item) { - const cellNode = this.grid.getCellNode(item, "installed"); - if (cellNode) { - const cellBtn = cellNode.querySelector(`button[mode="install"]`); - if (cellBtn) { - cellBtn.classList.add("cmm-btn-loading"); - return true - } - } - } - - async installModels(list, btn) { - let stats = await api.fetchApi('/manager/queue/status'); - - stats = await stats.json(); - if(stats.is_processing) { - customAlert(`[ComfyUI-Manager] There are already tasks in progress. Please try again after it is completed. (${stats.done_count}/${stats.total_count})`); - return; - } - - btn.classList.add("cmm-btn-loading"); - this.showError(""); - - let needRefresh = false; - let errorMsg = ""; - - await api.fetchApi('/manager/queue/reset'); - - let target_items = []; - - for (const item of list) { - this.grid.scrollRowIntoView(item); - target_items.push(item); - - if (!this.focusInstall(item)) { - this.grid.onNextUpdated(() => { - this.focusInstall(item); - }); - } - - this.showStatus(`Install ${item.name} ...`); - - const data = item.originalData; - data.ui_id = item.hash; - - const res = await api.fetchApi(`/manager/queue/install_model`, { - method: 'POST', - body: JSON.stringify(data) - }); - - if (res.status != 200) { - errorMsg = `'${item.name}': `; - - if(res.status == 403) { - try { - const data = await res.json(); - if(data.error === 'comfyui_outdated') { - errorMsg += `ComfyUI version is outdated. Please update ComfyUI to use Manager normally.\n`; - } else { - errorMsg += `This action is not allowed with this security level configuration.\n`; - } - } catch { - errorMsg += `This action is not allowed with this security level configuration.\n`; - } - } else { - errorMsg += await res.text() + '\n'; - } - - break; - } - } - - this.install_context = {btn: btn, targets: target_items}; - - if(errorMsg) { - this.showError(errorMsg); - show_message("[Installation Errors]\n"+errorMsg); - - // reset - for(let k in target_items) { - const item = target_items[k]; - this.grid.updateCell(item, "installed"); - } - } - else { - await api.fetchApi('/manager/queue/start'); - this.showStop(); - showTerminal(); - } - } - - async onQueueStatus(event) { - let self = ModelManager.instance; - - if(event.detail.status == 'in_progress' && event.detail.ui_target == 'model_manager') { - const hash = event.detail.target; - - const item = self.grid.getRowItemBy("hash", hash); - - item.refresh = true; - self.grid.setRowSelected(item, false); - item.selectable = false; -// self.grid.updateCell(item, "tg-column-select"); - self.grid.updateRow(item); - } - else if(event.detail.status == 'done') { - self.hideStop(); - self.onQueueCompleted(event.detail); - } - } - - async onQueueCompleted(info) { - let result = info.model_result; - - if(result.length == 0) { - return; - } - - let self = ModelManager.instance; - - if(!self.install_context) { - return; - } - - let btn = self.install_context.btn; - - self.hideLoading(); - btn.classList.remove("cmm-btn-loading"); - - let errorMsg = ""; - - for(let hash in result){ - let v = result[hash]; - - if(v != 'success') - errorMsg += v + '\n'; - } - - for(let k in self.install_context.targets) { - let item = self.install_context.targets[k]; - self.grid.updateCell(item, "installed"); - } - - if (errorMsg) { - self.showError(errorMsg); - show_message("Installation Error:\n"+errorMsg); - } else { - self.showStatus(`Install ${result.length} models successfully`); - } - - self.showRefresh(); - self.showMessage(`To apply the installed model, please click the 'Refresh' button.`, "red") - - infoToast('Tasks done', `[ComfyUI-Manager] All model downloading tasks in the queue have been completed.\n${info.done_count}/${info.total_count}`); - self.install_context = undefined; - } - - getModelList(models) { - const typeMap = new Map(); - const baseMap = new Map(); - - models.forEach((item, i) => { - const { type, base, name, reference, installed } = item; - item.originalData = JSON.parse(JSON.stringify(item)); - item.size = this.sizeToBytes(item.size); - item.hash = md5(name + reference); - item.id = i + 1; - - if (installed === "True") { - item.selectable = false; - } - - typeMap.set(type, type); - baseMap.set(base, base); - - }); - - const typeList = []; - typeMap.forEach(type => { - typeList.push({ - label: type, - value: type - }); - }); - typeList.sort((a,b)=> { - const au = a.label.toUpperCase(); - const bu = b.label.toUpperCase(); - if (au !== bu) { - return au > bu ? 1 : -1; - } - return 0; - }); - this.typeList = [{ - label: "All", - value: "" - }].concat(typeList); - - - const baseList = []; - baseMap.forEach(base => { - baseList.push({ - label: base, - value: base - }); - }); - baseList.sort((a,b)=> { - const au = a.label.toUpperCase(); - const bu = b.label.toUpperCase(); - if (au !== bu) { - return au > bu ? 1 : -1; - } - return 0; - }); - this.baseList = [{ - label: "All", - value: "" - }].concat(baseList); - - return models; - } - - // =========================================================================================== - - async loadData() { - - this.showLoading(); - - this.showStatus(`Loading external model list ...`); - - const mode = manager_instance.datasrc_combo.value; - - const res = await fetchData(`/externalmodel/getlist?mode=${mode}`); - if (res.error) { - this.showError("Failed to get external model list."); - this.hideLoading(); - return - } - - const { models } = res.data; - - this.modelList = this.getModelList(models); - // console.log("models", this.modelList); - - this.updateFilter(); - - this.renderGrid(); - - this.hideLoading(); - - } - - // =========================================================================================== - - formatSize(v) { - const base = 1000; - const units = ['', 'K', 'M', 'G', 'T', 'P']; - const space = ''; - const postfix = 'B'; - if (v <= 0) { - return `0${space}${postfix}`; - } - for (let i = 0, l = units.length; i < l; i++) { - const min = Math.pow(base, i); - const max = Math.pow(base, i + 1); - if (v > min && v <= max) { - const unit = units[i]; - if (unit) { - const n = v / min; - const nl = n.toString().split('.')[0].length; - const fl = Math.max(3 - nl, 1); - v = n.toFixed(fl); - } - v = v + space + unit + postfix; - break; - } - } - return v; - } - - // for size sort - sizeToBytes(v) { - if (typeof v === "number") { - return v; - } - if (typeof v === "string") { - const n = parseFloat(v); - const unit = v.replace(/[0-9.B]+/g, "").trim().toUpperCase(); - if (unit === "K") { - return n * 1000; - } - if (unit === "M") { - return n * 1000 * 1000; - } - if (unit === "G") { - return n * 1000 * 1000 * 1000; - } - if (unit === "T") { - return n * 1000 * 1000 * 1000 * 1000; - } - } - return v; - } - - showSelection(msg) { - this.element.querySelector(".cmm-manager-selection").innerHTML = msg; - } - - showError(err) { - this.showMessage(err, "red"); - } - - showMessage(msg, color) { - if (color) { - msg = `${msg}`; - } - this.element.querySelector(".cmm-manager-message").innerHTML = msg; - } - - showStatus(msg, color) { - if (color) { - msg = `${msg}`; - } - this.element.querySelector(".cmm-manager-status").innerHTML = msg; - } - - showLoading() { -// this.setDisabled(true); - if (this.grid) { - this.grid.showLoading(); - this.grid.showMask({ - opacity: 0.05 - }); - } - } - - hideLoading() { -// this.setDisabled(false); - if (this.grid) { - this.grid.hideLoading(); - this.grid.hideMask(); - } - } - - setDisabled(disabled) { - const $close = this.element.querySelector(".cmm-manager-close"); - const $refresh = this.element.querySelector(".cmm-manager-refresh"); - const $stop = this.element.querySelector(".cmm-manager-stop"); - - const list = [ - ".cmm-manager-header input", - ".cmm-manager-header select", - ".cmm-manager-footer button", - ".cmm-manager-selection button" - ].map(s => { - return Array.from(this.element.querySelectorAll(s)); - }) - .flat() - .filter(it => { - return it !== $close && it !== $refresh && it !== $stop; - }); - - list.forEach($elem => { - if (disabled) { - $elem.setAttribute("disabled", "disabled"); - } else { - $elem.removeAttribute("disabled"); - } - }); - - Array.from(this.element.querySelectorAll(".cmm-btn-loading")).forEach($elem => { - $elem.classList.remove("cmm-btn-loading"); - }); - - } - - showRefresh() { - this.element.querySelector(".cmm-manager-refresh").style.display = "block"; - } - - showStop() { - this.element.querySelector(".cmm-manager-stop").style.display = "block"; - } - - hideStop() { - this.element.querySelector(".cmm-manager-stop").style.display = "none"; - } - - setKeywords(keywords = "") { - this.keywords = keywords; - this.element.querySelector(".cmm-manager-keywords").value = keywords; - } - - show() { - this.element.style.display = "flex"; - this.setKeywords(""); - this.showSelection(""); - this.showMessage(""); - this.loadData(); - } - - close() { - this.element.style.display = "none"; - } -} diff --git a/js/node_fixer.js b/js/node_fixer.js deleted file mode 100644 index 867a7b81..00000000 --- a/js/node_fixer.js +++ /dev/null @@ -1,161 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; - -function addMenuHandler(nodeType, cb) { - const getOpts = nodeType.prototype.getExtraMenuOptions; - nodeType.prototype.getExtraMenuOptions = function () { - const r = getOpts.apply(this, arguments); - cb.apply(this, arguments); - return r; - }; -} - -function distance(node1, node2) { - let dx = (node1.pos[0] + node1.size[0]/2) - (node2.pos[0] + node2.size[0]/2); - let dy = (node1.pos[1] + node1.size[1]/2) - (node2.pos[1] + node2.size[1]/2); - return Math.sqrt(dx * dx + dy * dy); -} - -function lookup_nearest_nodes(node) { - let nearest_distance = Infinity; - let nearest_node = null; - for(let other of app.graph._nodes) { - if(other === node) - continue; - - let dist = distance(node, other); - if (dist < nearest_distance && dist < 1000) { - nearest_distance = dist; - nearest_node = other; - } - } - - return nearest_node; -} - -function lookup_nearest_inputs(node) { - let input_map = {}; - - for(let i in node.inputs) { - let input = node.inputs[i]; - - if(input.link || input_map[input.type]) - continue; - - input_map[input.type] = {distance: Infinity, input_name: input.name, node: null, slot: null}; - } - - let x = node.pos[0]; - let y = node.pos[1] + node.size[1]/2; - - for(let other of app.graph._nodes) { - if(other === node || !other.outputs) - continue; - - let dx = x - (other.pos[0] + other.size[0]); - let dy = y - (other.pos[1] + other.size[1]/2); - - if(dx < 0) - continue; - - let dist = Math.sqrt(dx * dx + dy * dy); - - for(let input_type in input_map) { - for(let j in other.outputs) { - let output = other.outputs[j]; - if(output.type == input_type) { - if(input_map[input_type].distance > dist) { - input_map[input_type].distance = dist; - input_map[input_type].node = other; - input_map[input_type].slot = parseInt(j); - } - } - } - } - } - - let res = {}; - for (let i in input_map) { - if (input_map[i].node) { - res[i] = input_map[i]; - } - } - - return res; -} - -function connect_inputs(nearest_inputs, node) { - for(let i in nearest_inputs) { - let info = nearest_inputs[i]; - info.node.connect(info.slot, node.id, info.input_name); - } -} - -function node_info_copy(src, dest, connect_both, copy_shape) { - // copy input connections - for(let i in src.inputs) { - let input = src.inputs[i]; - if (input.widget !== undefined) { - const destWidget = dest.widgets.find(x => x.name === input.widget.name); - dest.convertWidgetToInput(destWidget); - } - if(input.link) { - let link = app.graph.links[input.link]; - let src_node = app.graph.getNodeById(link.origin_id); - src_node.connect(link.origin_slot, dest.id, input.name); - } - } - - // copy output connections - if(connect_both) { - let output_links = {}; - for(let i in src.outputs) { - let output = src.outputs[i]; - if(output.links) { - let links = []; - for(let j in output.links) { - links.push(app.graph.links[output.links[j]]); - } - output_links[output.name] = links; - } - } - - for(let i in dest.outputs) { - let links = output_links[dest.outputs[i].name]; - if(links) { - for(let j in links) { - let link = links[j]; - let target_node = app.graph.getNodeById(link.target_id); - dest.connect(parseInt(i), target_node, link.target_slot); - } - } - } - } - - if(copy_shape) { - dest.color = src.color; - dest.bgcolor = src.bgcolor; - dest.size = max(src.size, dest.size); - } - - app.graph.afterChange(); -} - -app.registerExtension({ - name: "Comfy.Manager.NodeFixer", - beforeRegisterNodeDef(nodeType, nodeData, app) { - addMenuHandler(nodeType, function (_, options) { - options.push({ - content: "Fix node (recreate)", - callback: () => { - let new_node = LiteGraph.createNode(nodeType.comfyClass); - new_node.pos = [this.pos[0], this.pos[1]]; - app.canvas.graph.add(new_node, false); - node_info_copy(this, new_node, true); - app.canvas.graph.remove(this); - requestAnimationFrame(() => app.canvas.setDirty(true, true)) - }, - }); - }); - } -}); diff --git a/js/popover-helper.js b/js/popover-helper.js deleted file mode 100644 index 8c214b8c..00000000 --- a/js/popover-helper.js +++ /dev/null @@ -1,619 +0,0 @@ -const hasOwn = function(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); -}; - -const isNum = function(num) { - if (typeof num !== 'number' || isNaN(num)) { - return false; - } - const isInvalid = function(n) { - if (n === Number.MAX_VALUE || n === Number.MIN_VALUE || n === Number.NEGATIVE_INFINITY || n === Number.POSITIVE_INFINITY) { - return true; - } - return false; - }; - if (isInvalid(num)) { - return false; - } - return true; -}; - -const toNum = (num) => { - if (typeof (num) !== 'number') { - num = parseFloat(num); - } - if (isNaN(num)) { - num = 0; - } - num = Math.round(num); - return num; -}; - -const clamp = function(value, min, max) { - return Math.max(min, Math.min(max, value)); -}; - -const isWindow = (obj) => { - return Boolean(obj && obj === obj.window); -}; - -const isDocument = (obj) => { - return Boolean(obj && obj.nodeType === 9); -}; - -const isElement = (obj) => { - return Boolean(obj && obj.nodeType === 1); -}; - -// =========================================================================================== - -export const toRect = (obj) => { - if (obj) { - return { - left: toNum(obj.left || obj.x), - top: toNum(obj.top || obj.y), - width: toNum(obj.width), - height: toNum(obj.height) - }; - } - return { - left: 0, - top: 0, - width: 0, - height: 0 - }; -}; - -export const getElement = (selector) => { - if (typeof selector === 'string' && selector) { - if (selector.startsWith('#')) { - return document.getElementById(selector.slice(1)); - } - return document.querySelector(selector); - } - - if (isDocument(selector)) { - return selector.body; - } - if (isElement(selector)) { - return selector; - } -}; - -export const getRect = (target, fixed) => { - if (!target) { - return toRect(); - } - - if (isWindow(target)) { - return { - left: 0, - top: 0, - width: window.innerWidth, - height: window.innerHeight - }; - } - - const elem = getElement(target); - if (!elem) { - return toRect(target); - } - - const br = elem.getBoundingClientRect(); - const rect = toRect(br); - - // fix offset - if (!fixed) { - rect.left += window.scrollX; - rect.top += window.scrollY; - } - - rect.width = elem.offsetWidth; - rect.height = elem.offsetHeight; - - return rect; -}; - -// =========================================================================================== - -const calculators = { - - bottom: (info, containerRect, targetRect) => { - info.space = containerRect.top + containerRect.height - targetRect.top - targetRect.height - info.height; - info.top = targetRect.top + targetRect.height; - info.left = Math.round(targetRect.left + targetRect.width * 0.5 - info.width * 0.5); - }, - - top: (info, containerRect, targetRect) => { - info.space = targetRect.top - info.height - containerRect.top; - info.top = targetRect.top - info.height; - info.left = Math.round(targetRect.left + targetRect.width * 0.5 - info.width * 0.5); - }, - - right: (info, containerRect, targetRect) => { - info.space = containerRect.left + containerRect.width - targetRect.left - targetRect.width - info.width; - info.top = Math.round(targetRect.top + targetRect.height * 0.5 - info.height * 0.5); - info.left = targetRect.left + targetRect.width; - }, - - left: (info, containerRect, targetRect) => { - info.space = targetRect.left - info.width - containerRect.left; - info.top = Math.round(targetRect.top + targetRect.height * 0.5 - info.height * 0.5); - info.left = targetRect.left - info.width; - } -}; - -// with order -export const getDefaultPositions = () => { - return Object.keys(calculators); -}; - -const calculateSpace = (info, containerRect, targetRect) => { - const calculator = calculators[info.position]; - calculator(info, containerRect, targetRect); - if (info.space >= 0) { - info.passed += 1; - } -}; - -// =========================================================================================== - -const calculateAlignOffset = (info, containerRect, targetRect, alignType, sizeType) => { - - const popoverStart = info[alignType]; - const popoverSize = info[sizeType]; - - const containerStart = containerRect[alignType]; - const containerSize = containerRect[sizeType]; - - const targetStart = targetRect[alignType]; - const targetSize = targetRect[sizeType]; - - const targetCenter = targetStart + targetSize * 0.5; - - // size overflow - if (popoverSize > containerSize) { - const overflow = (popoverSize - containerSize) * 0.5; - info[alignType] = containerStart - overflow; - info.offset = targetCenter - containerStart + overflow; - return; - } - - const space1 = popoverStart - containerStart; - const space2 = (containerStart + containerSize) - (popoverStart + popoverSize); - - // both side passed, default to center - if (space1 >= 0 && space2 >= 0) { - if (info.passed) { - info.passed += 2; - } - info.offset = popoverSize * 0.5; - return; - } - - // one side passed - if (info.passed) { - info.passed += 1; - } - - if (space1 < 0) { - const min = containerStart; - info[alignType] = min; - info.offset = targetCenter - min; - return; - } - - // space2 < 0 - const max = containerStart + containerSize - popoverSize; - info[alignType] = max; - info.offset = targetCenter - max; - -}; - -const calculateHV = (info, containerRect) => { - if (['top', 'bottom'].includes(info.position)) { - info.top = clamp(info.top, containerRect.top, containerRect.top + containerRect.height - info.height); - return ['left', 'width']; - } - info.left = clamp(info.left, containerRect.left, containerRect.left + containerRect.width - info.width); - return ['top', 'height']; -}; - -const calculateOffset = (info, containerRect, targetRect) => { - - const [alignType, sizeType] = calculateHV(info, containerRect); - - calculateAlignOffset(info, containerRect, targetRect, alignType, sizeType); - - info.offset = clamp(info.offset, 0, info[sizeType]); - -}; - -// =========================================================================================== - -const calculateDistance = (info, previousPositionInfo) => { - if (!previousPositionInfo) { - return; - } - // no change if position no change with previous - if (info.position === previousPositionInfo.position) { - return; - } - const ax = info.left + info.width * 0.5; - const ay = info.top + info.height * 0.5; - const bx = previousPositionInfo.left + previousPositionInfo.width * 0.5; - const by = previousPositionInfo.top + previousPositionInfo.height * 0.5; - const dx = Math.abs(ax - bx); - const dy = Math.abs(ay - by); - info.distance = Math.round(Math.sqrt(dx * dx + dy * dy)); -}; - -// =========================================================================================== - -const calculatePositionInfo = (info, containerRect, targetRect, previousPositionInfo) => { - calculateSpace(info, containerRect, targetRect); - calculateOffset(info, containerRect, targetRect); - calculateDistance(info, previousPositionInfo); -}; - -// =========================================================================================== - -const calculateBestPosition = (containerRect, targetRect, infoMap, withOrder, previousPositionInfo) => { - - // position space: +1 - // align space: - // two side passed: +2 - // one side passed: +1 - - const safePassed = 3; - - if (previousPositionInfo) { - const prevInfo = infoMap[previousPositionInfo.position]; - if (prevInfo) { - calculatePositionInfo(prevInfo, containerRect, targetRect); - if (prevInfo.passed >= safePassed) { - return prevInfo; - } - prevInfo.calculated = true; - } - } - - const positionList = []; - Object.values(infoMap).forEach((info) => { - if (!info.calculated) { - calculatePositionInfo(info, containerRect, targetRect, previousPositionInfo); - } - positionList.push(info); - }); - - positionList.sort((a, b) => { - if (a.passed !== b.passed) { - return b.passed - a.passed; - } - - if (withOrder && a.passed >= safePassed && b.passed >= safePassed) { - return a.index - b.index; - } - - if (a.space !== b.space) { - return b.space - a.space; - } - - return a.index - b.index; - }); - - // logTable(positionList); - - return positionList[0]; -}; - -// const logTable = (() => { -// let time_id; -// return (info) => { -// clearTimeout(time_id); -// time_id = setTimeout(() => { -// console.table(info); -// }, 10); -// }; -// })(); - -// =========================================================================================== - -const getAllowPositions = (positions, defaultAllowPositions) => { - if (!positions) { - return; - } - if (Array.isArray(positions)) { - positions = positions.join(','); - } - positions = String(positions).split(',').map((it) => it.trim().toLowerCase()).filter((it) => it); - positions = positions.filter((it) => defaultAllowPositions.includes(it)); - if (!positions.length) { - return; - } - return positions; -}; - -const isPositionChanged = (info, previousPositionInfo) => { - if (!previousPositionInfo) { - return true; - } - - if (info.left !== previousPositionInfo.left) { - return true; - } - - if (info.top !== previousPositionInfo.top) { - return true; - } - - return false; -}; - -// =========================================================================================== - -// const log = (name, time) => { -// if (time > 0.1) { -// console.log(name, time); -// } -// }; - -export const getBestPosition = (containerRect, targetRect, popoverRect, positions, previousPositionInfo) => { - - const defaultAllowPositions = getDefaultPositions(); - let withOrder = true; - let allowPositions = getAllowPositions(positions, defaultAllowPositions); - if (!allowPositions) { - allowPositions = defaultAllowPositions; - withOrder = false; - } - - // console.log('withOrder', withOrder); - - // const start_time = performance.now(); - - const infoMap = {}; - allowPositions.forEach((k, i) => { - infoMap[k] = { - position: k, - index: i, - - top: 0, - left: 0, - width: popoverRect.width, - height: popoverRect.height, - - space: 0, - - offset: 0, - passed: 0, - - distance: 0 - }; - }); - - // log('infoMap', performance.now() - start_time); - - - const bestPosition = calculateBestPosition(containerRect, targetRect, infoMap, withOrder, previousPositionInfo); - - // check left/top - bestPosition.changed = isPositionChanged(bestPosition, previousPositionInfo); - - return bestPosition; -}; - -// =========================================================================================== - -const getTemplatePath = (width, height, arrowOffset, arrowSize, borderRadius) => { - const p = (px, py) => { - return [px, py].join(','); - }; - - const px = function(num, alignEnd) { - const floor = Math.floor(num); - let n = num < floor + 0.5 ? floor + 0.5 : floor + 1.5; - if (alignEnd) { - n -= 1; - } - return n; - }; - - const pxe = function(num) { - return px(num, true); - }; - - const ls = []; - - const innerLeft = px(arrowSize); - const innerRight = pxe(width - arrowSize); - arrowOffset = clamp(arrowOffset, innerLeft, innerRight); - - const innerTop = px(arrowSize); - const innerBottom = pxe(height - arrowSize); - - const startPoint = p(innerLeft, innerTop + borderRadius); - const arrowPoint = p(arrowOffset, 1); - - const LT = p(innerLeft, innerTop); - const RT = p(innerRight, innerTop); - - const AOT = p(arrowOffset - arrowSize, innerTop); - const RRT = p(innerRight - borderRadius, innerTop); - - ls.push(`M${startPoint}`); - ls.push(`V${innerBottom - borderRadius}`); - ls.push(`Q${p(innerLeft, innerBottom)} ${p(innerLeft + borderRadius, innerBottom)}`); - ls.push(`H${innerRight - borderRadius}`); - ls.push(`Q${p(innerRight, innerBottom)} ${p(innerRight, innerBottom - borderRadius)}`); - ls.push(`V${innerTop + borderRadius}`); - - if (arrowOffset < innerLeft + arrowSize + borderRadius) { - ls.push(`Q${RT} ${RRT}`); - ls.push(`H${arrowOffset + arrowSize}`); - ls.push(`L${arrowPoint}`); - if (arrowOffset < innerLeft + arrowSize) { - ls.push(`L${LT}`); - ls.push(`L${startPoint}`); - } else { - ls.push(`L${AOT}`); - ls.push(`Q${LT} ${startPoint}`); - } - } else if (arrowOffset > innerRight - arrowSize - borderRadius) { - if (arrowOffset > innerRight - arrowSize) { - ls.push(`L${RT}`); - } else { - ls.push(`Q${RT} ${p(arrowOffset + arrowSize, innerTop)}`); - } - ls.push(`L${arrowPoint}`); - ls.push(`L${AOT}`); - ls.push(`H${innerLeft + borderRadius}`); - ls.push(`Q${LT} ${startPoint}`); - } else { - ls.push(`Q${RT} ${RRT}`); - ls.push(`H${arrowOffset + arrowSize}`); - ls.push(`L${arrowPoint}`); - ls.push(`L${AOT}`); - ls.push(`H${innerLeft + borderRadius}`); - ls.push(`Q${LT} ${startPoint}`); - } - return ls.join(''); -}; - -const getPathData = function(position, width, height, arrowOffset, arrowSize, borderRadius) { - - const handlers = { - - bottom: () => { - const d = getTemplatePath(width, height, arrowOffset, arrowSize, borderRadius); - return { - d, - transform: '' - }; - }, - - top: () => { - const d = getTemplatePath(width, height, width - arrowOffset, arrowSize, borderRadius); - return { - d, - transform: `rotate(180,${width * 0.5},${height * 0.5})` - }; - }, - - left: () => { - const d = getTemplatePath(height, width, arrowOffset, arrowSize, borderRadius); - const x = (width - height) * 0.5; - const y = (height - width) * 0.5; - return { - d, - transform: `translate(${x} ${y}) rotate(90,${height * 0.5},${width * 0.5})` - }; - }, - - right: () => { - const d = getTemplatePath(height, width, height - arrowOffset, arrowSize, borderRadius); - const x = (width - height) * 0.5; - const y = (height - width) * 0.5; - return { - d, - transform: `translate(${x} ${y}) rotate(-90,${height * 0.5},${width * 0.5})` - }; - } - }; - - return handlers[position](); -}; - -// =========================================================================================== - -// position style cache -const styleCache = { - // position: '', - // top: {}, - // bottom: {}, - // left: {}, - // right: {} -}; - -export const getPositionStyle = (info, options = {}) => { - - const o = { - bgColor: '#fff', - borderColor: '#ccc', - borderRadius: 5, - arrowSize: 10 - }; - Object.keys(o).forEach((k) => { - - if (hasOwn(options, k)) { - const d = o[k]; - const v = options[k]; - - if (typeof d === 'string') { - // string - if (typeof v === 'string' && v) { - o[k] = v; - } - } else { - // number - if (isNum(v) && v >= 0) { - o[k] = v; - } - - } - - } - }); - - const key = [ - info.width, - info.height, - info.offset, - o.arrowSize, - o.borderRadius, - o.bgColor, - o.borderColor - ].join('-'); - - const positionCache = styleCache[info.position]; - if (positionCache && key === positionCache.key) { - const st = positionCache.style; - st.changed = styleCache.position !== info.position; - styleCache.position = info.position; - return st; - } - - // console.log(options); - - const data = getPathData(info.position, info.width, info.height, info.offset, o.arrowSize, o.borderRadius); - // console.log(data); - - const viewBox = [0, 0, info.width, info.height].join(' '); - const svg = [ - ``, - ``, - '' - ].join(''); - - // console.log(svg); - const backgroundImage = `url("data:image/svg+xml;charset=utf8,${encodeURIComponent(svg)}")`; - - const background = `${backgroundImage} center no-repeat`; - - const padding = `${o.arrowSize + o.borderRadius}px`; - - const style = { - background, - backgroundImage, - padding, - changed: true - }; - - styleCache.position = info.position; - styleCache[info.position] = { - key, - style - }; - - return style; -}; diff --git a/js/snapshot.css b/js/snapshot.css deleted file mode 100644 index a06eb99b..00000000 --- a/js/snapshot.css +++ /dev/null @@ -1,65 +0,0 @@ -.snapshot-manager { - --grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; - z-index: 1099; - width: 80vw; - height: 75vh; - min-height: 30em; - display: flex; - flex-direction: column; - gap: 10px; - color: var(--fg-color); - font-family: arial, sans-serif; - text-underline-offset: 3px; - outline: none; - margin: calc(var(--spacing)*2); -} - -.snapshot-manager button { - width: auto; - position: relative; - overflow: hidden; - font-size: 16px; - color: var(--input-text); - background-color: var(--comfy-input-bg); - border-color: var(--border-color); - margin: 0; - min-width: 100px; - padding: 4px 8px; -} - -.snapshot-manager .snapshot-restore-btn { - background-color: #00158f !important; - border-color: #2025b9 !important; - color: white !important; -} - -.snapshot-manager .snapshot-remove-btn { - background-color: #970000 !important; - border-color: #be2127 !important; - color: white !important; -} - -.snapshot-manager button:hover { - filter: brightness(125%); -} - -.snapshot-manager .data-btns { - display: flex; - flex-direction: column; - gap: calc(var(--spacing)*2); - padding: calc(var(--spacing)*2); - align-items: center; - justify-content: center; - height: 100%; -} - -.snapshot-footer { - display: flex; - flex-wrap: wrap; - gap: 10px; - align-items: center; -} - -.snapshot-manager .cn-flex-auto { - flex: auto; -} \ No newline at end of file diff --git a/js/snapshot.js b/js/snapshot.js deleted file mode 100644 index b5dc0817..00000000 --- a/js/snapshot.js +++ /dev/null @@ -1,311 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js" -import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { manager_instance, rebootAPI, show_message, handle403Response, loadCss } from "./common.js"; -import { buildGuiFrame } from "./comfyui-gui-builder.js"; - -loadCss("./snapshot.css"); - -async function restore_snapshot(target) { - if(SnapshotManager.instance) { - try { - const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" }); - - if(response.status == 403) { - await handle403Response(response); - return false; - } - - if(response.status == 400) { - show_message(`Restore snapshot failed: ${target.title} / ${exception}`); - } - - app.ui.dialog.close(); - return true; - } - catch(exception) { - show_message(`Restore snapshot failed: ${target.title} / ${exception}`); - return false; - } - finally { - await SnapshotManager.instance.invalidateControl(); - SnapshotManager.instance.updateMessage("
    To apply the snapshot, please ComfyUI. And refresh browser.", 'cm-reboot-button2'); - } - } -} - -async function remove_snapshot(target) { - if(SnapshotManager.instance) { - try { - const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" }); - - if(response.status == 403) { - await handle403Response(response); - return false; - } - - if(response.status == 400) { - show_message(`Remove snapshot failed: ${target.title} / ${exception}`); - } - - app.ui.dialog.close(); - return true; - } - catch(exception) { - show_message(`Restore snapshot failed: ${target.title} / ${exception}`); - return false; - } - finally { - await SnapshotManager.instance.invalidateControl(); - } - } -} - -async function save_current_snapshot() { - try { - const response = await api.fetchApi('/snapshot/save', { cache: "no-store" }); - app.ui.dialog.close(); - return true; - } - catch(exception) { - show_message(`Backup snapshot failed: ${exception}`); - return false; - } - finally { - await SnapshotManager.instance.invalidateControl(); - SnapshotManager.instance.updateMessage("
    Current snapshot saved."); - } -} - -async function getSnapshotList() { - const response = await api.fetchApi(`/snapshot/getlist`); - const data = await response.json(); - return data; -} - -export class SnapshotManager extends ComfyDialog { - static instance = null; - - restore_buttons = []; - message_box = null; - data = null; - - content = $el("div.snapshot-manager"); - - clear() { - this.restore_buttons = []; - this.message_box = null; - this.data = null; - } - - constructor(app, manager_dialog) { - super(); - // this.manager_dialog = manager_dialog; - this.search_keyword = ''; - - const frame = buildGuiFrame( - 'snapshot-manager-dialog', // dialog id - 'Snapshot Manager', // title - 'i.mdi.mdi-puzzle', // icon class - this.content, // dialog content element - this - ); // send this so we can attach close functions - - this.element = frame; - } - - async remove_item() { - caller.disableButtons(); - - await caller.invalidateControl(); - } - - createControls() { - return [ - $el("button.p-button.p-component", { - type: "button", - textContent: "Close", - onclick: () => { this.close(); } - }) - ]; - } - - startRestore(target) { - const self = SnapshotManager.instance; - - self.updateMessage(`
    Restore snapshot '${target.name}'`); - - for(let i in self.restore_buttons) { - self.restore_buttons[i].disabled = true; - self.restore_buttons[i].style.backgroundColor = 'gray'; - } - } - - async invalidateControl() { - this.clear(); - this.data = (await getSnapshotList()).items; - - while (this.content.children.length) { - this.content.removeChild(this.content.children[0]); - } - - await this.createGrid(); - await this.createBottomControls(); - } - - updateMessage(msg, btn_id) { - this.message_box.innerHTML = msg; - if(btn_id) { - const rebootButton = document.getElementById(btn_id); - const self = this; - rebootButton.onclick = async function() { - if(await rebootAPI()) { - self.close(); - self.manager_dialog.close(); - } - }; - } - } - - async createGrid(models_json) { - var grid = document.createElement('table'); - grid.setAttribute('id', 'snapshot-list-grid'); - - var thead = document.createElement('thead'); - var tbody = document.createElement('tbody'); - - var headerRow = document.createElement('tr'); - thead.style.position = "sticky"; - thead.style.top = "0px"; - thead.style.borderCollapse = "collapse"; - thead.style.tableLayout = "fixed"; - - var header1 = document.createElement('th'); - header1.innerHTML = '  ID  '; - header1.style.width = "20px"; - var header2 = document.createElement('th'); - header2.innerHTML = 'Datetime'; - header2.style.width = "100%"; - var header_button = document.createElement('th'); - header_button.innerHTML = 'Action'; - header_button.style.width = "100px"; - - thead.appendChild(headerRow); - headerRow.appendChild(header1); - headerRow.appendChild(header2); - headerRow.appendChild(header_button); - - headerRow.style.backgroundColor = "Black"; - headerRow.style.color = "White"; - headerRow.style.textAlign = "center"; - headerRow.style.width = "100%"; - headerRow.style.padding = "0"; - - grid.appendChild(thead); - grid.appendChild(tbody); - - this.grid_rows = {}; - - if(this.data) - for (var i = 0; i < this.data.length; i++) { - const data = this.data[i]; - var dataRow = document.createElement('tr'); - var data1 = document.createElement('td'); - data1.style.textAlign = "center"; - data1.innerHTML = i+1; - var data2 = document.createElement('td'); - data2.innerHTML = ` ${data}`; - var data_button = document.createElement('td'); - data_button.style.textAlign = "center"; - data_button.className = "data-btns"; - - var restoreBtn = document.createElement('button'); - restoreBtn.className = "snapshot-restore-btn p-button p-component"; - restoreBtn.innerHTML = 'Restore'; - restoreBtn.style.width = "100px"; - - restoreBtn.addEventListener('click', function() { - restore_snapshot(data); - }); - - var removeBtn = document.createElement('button'); - removeBtn.className = "snapshot-remove-btn p-button p-component"; - removeBtn.innerHTML = 'Remove'; - removeBtn.style.width = "100px"; - - removeBtn.addEventListener('click', function() { - remove_snapshot(data); - }); - - data_button.appendChild(restoreBtn); - data_button.appendChild(removeBtn); - - dataRow.style.backgroundColor = "var(--bg-color)"; - dataRow.style.color = "var(--fg-color)"; - dataRow.style.textAlign = "left"; - - dataRow.appendChild(data1); - dataRow.appendChild(data2); - dataRow.appendChild(data_button); - tbody.appendChild(dataRow); - - this.grid_rows[i] = {data:data, control:dataRow}; - } - - let self = this; - const panel = document.createElement('div'); - panel.style.width = "100%"; - panel.style.height = "100%"; - panel.appendChild(grid); - - function handleResize() { - const parentHeight = self.element.clientHeight; - const gridHeight = parentHeight - 200; - - // grid.style.height = gridHeight + "px"; - } - window.addEventListener("resize", handleResize); - - grid.style.position = "relative"; - grid.style.display = "inline-block"; - grid.style.width = "100%"; - grid.style.height = "100%"; - grid.style.overflowY = "scroll"; - - this.content.appendChild(panel); - - handleResize(); - } - - async createBottomControls() { - var save_button = document.createElement("button"); - save_button.className = "p-button p-component"; - save_button.innerHTML = "Save snapshot"; - save_button.onclick = () => { save_current_snapshot(); } - save_button.style.horizontalAlign = "right"; - save_button.style.width = "170px"; - - this.message_box = $el('div', {id:'custom-download-message'}, [$el('br'), '']); - this.message_box.style.height = '60px'; - this.message_box.style.verticalAlign = 'middle'; - - const footer = $el("div.snapshot-footer"); - const spacer = $el("div.cn-flex-auto"); - footer.appendChild(spacer); - footer.appendChild(save_button); - - this.content.appendChild(this.message_box); - this.content.appendChild(footer); - } - - async show() { - try { - this.invalidateControl(); - this.element.style.display = "flex"; - this.element.style.zIndex = 1099; - } - catch(exception) { - app.ui.dialog.show(`Failed to get external model list. / ${exception}`); - } - } -} diff --git a/js/turbogrid.esm.js b/js/turbogrid.esm.js deleted file mode 100644 index fd0bfb57..00000000 --- a/js/turbogrid.esm.js +++ /dev/null @@ -1 +0,0 @@ -var t={915:(t,e,i)=>{i.d(e,{A:()=>l});var o=i(256),n=i.n(o),s=i(505),r=i.n(s)()(n());r.push([t.id,'.tg-turbogrid{position:relative;z-index:0;width:100%;height:100%;margin:0;padding:0;box-sizing:border-box;font-size:14px;font-family:arial,sans-serif;outline:0;cursor:default;overflow:hidden}.tg-turbogrid *,.tg-turbogrid *::before,.tg-turbogrid *::after{box-sizing:border-box}.tg-text-unselectable.tg-turbogrid{user-select:none}.tg-turbogrid svg{display:block;pointer-events:none}.tg-turbogrid .tg-symbols{font-family:webdings,sans-serif}.tg-turbogrid .tg-nowrap{white-space:nowrap}.tg-turbogrid .tg-align-left{text-align:left}.tg-turbogrid .tg-align-center{text-align:center}.tg-turbogrid .tg-align-right{text-align:right}@keyframes tg-fade-in{from{opacity:0}to{opacity:1}}@keyframes tg-fade-out{from{opacity:1}to{opacity:0}}.tg-turbogrid .tg-fade-in{animation-name:tg-fade-in;animation-duration:.2s;animation-fill-mode:both}.tg-turbogrid .tg-fade-in .tg-scrollbar-track{display:none}.tg-turbogrid .tg-fade-out{animation-name:tg-fade-out;animation-duration:.2s;animation-fill-mode:both}.tg-turbogrid .tg-fade-out .tg-scrollbar-track{display:none}.tg-turbogrid .tg-mask{position:absolute;top:0;left:0;z-index:200;display:none;width:100%;height:100%;background-color:#000;opacity:.1}@keyframes tg-loading-animation{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.tg-turbogrid .tg-loading{position:absolute;top:50%;left:50%;z-index:300;display:none;transform:translate(-50%, -50%);pointer-events:none}.tg-turbogrid .tg-loading-default{width:35px;height:35px;color:#0077cf;animation:1s tg-loading-animation linear infinite}.tg-turbogrid .tg-loading-fast{animation:.382s tg-loading-animation linear infinite}.tg-turbogrid .tg-checkbox{width:100%;height:100%;cursor:pointer;overflow:hidden}.tg-turbogrid .tg-checkbox:hover .tg-checkbox-item{fill:#005ba1}.tg-turbogrid .tg-checkbox.tg-select-icon-all{height:18px}.tg-turbogrid .tg-checkbox .tg-icon-checkbox{position:absolute;top:50%;left:50%;display:block;width:16px;height:16px;transform:translate(-50%, -50%)}.tg-turbogrid .tg-checkbox .tg-checkbox-item{display:none;fill:gray}.tg-turbogrid .tg-checkbox .tg-checkbox-none{display:block}.tg-turbogrid .tg-checkbox.tg-selected .tg-checkbox-selected{display:block;fill:#0077cf}.tg-turbogrid .tg-checkbox.tg-mixed .tg-checkbox-mixed{display:block;fill:#0077cf}.tg-turbogrid .tg-radio{cursor:pointer;overflow:hidden}.tg-turbogrid .tg-radio:hover .tg-icon-radio::before{border-color:#005ba1}.tg-turbogrid .tg-radio .tg-icon-radio{position:absolute;top:50%;left:50%;width:16px;height:16px;transform:translate(-50%, -50%)}.tg-turbogrid .tg-radio .tg-icon-radio::before{position:absolute;top:50%;left:50%;content:"";display:block;width:16px;height:16px;border:thin solid gray;border-radius:50%;background:#fff;transform:translate(-50%, -50%)}.tg-turbogrid .tg-radio .tg-icon-radio::after{position:absolute;top:50%;left:50%;content:"";display:none;width:10px;height:10px;border-radius:50%;background:#0077cf;transform:translate(-50%, -50%)}.tg-turbogrid .tg-radio.tg-selected .tg-icon-radio::after{display:block;border-color:#0077cf}.tg-turbogrid .tg-scrollbar{position:absolute;z-index:100;overflow:hidden;user-select:none}.tg-turbogrid .tg-scrollbar-v{top:0;right:0}.tg-turbogrid .tg-scrollbar-h{left:0;bottom:0}.tg-turbogrid .tg-scrollbar-track{position:relative;width:100%;height:100%;background:#f9f9f9;overflow:hidden;user-select:none}.tg-turbogrid .tg-scrollbar-thumb{position:absolute;top:0;left:0;border-radius:1px;background:#999;overflow:hidden;user-select:none}.tg-turbogrid .tg-scrollbar-thumb:hover{background:#888}.tg-turbogrid .tg-scrollbar-thumb-hold{background:#666}.tg-turbogrid .tg-scrollbar-thumb-hold:hover{background:#666}.tg-turbogrid .tg-scrollbar-round .tg-scrollbar-track{border-radius:10px}.tg-turbogrid .tg-scrollbar-round .tg-scrollbar-thumb{border-radius:10px}.tg-turbogrid .tg-scroll-pane{position:relative;margin:0;padding:0;border:none;outline:none;overflow:hidden}.tg-turbogrid .tg-scroll-view{position:relative;width:100%;height:100%;margin:0;padding:0;border:none;overflow:hidden}.tg-turbogrid .tg-scroll-body{position:absolute}.tg-turbogrid .tg-header{position:relative;width:10000px;border-left:0;overflow:hidden}.tg-turbogrid .tg-header-table{position:relative;color:#5e5e5e;font-weight:bold;font-size:14px;line-height:16px;border-bottom:thin solid #e5e5e5;overflow:hidden}.tg-turbogrid .tg-header-item{position:absolute;bottom:0}.tg-turbogrid .tg-header-group-item{overflow:hidden}.tg-turbogrid .tg-header-group-item::after{position:absolute;left:5px;bottom:0;content:"";display:block;width:calc(100% - 10px);height:1px;border-bottom:thin solid #ccc}.tg-turbogrid .tg-column-header{position:absolute;bottom:0;overflow:hidden}.tg-turbogrid .tg-column-header .tg-column-name{padding:10px 5px;text-overflow:ellipsis;overflow:hidden}.tg-turbogrid .tg-column-header .tg-column-name.tg-header-group-name{margin:0 5px;padding:5px 0}.tg-turbogrid .tg-column-resizing{position:absolute;top:0;right:-5px;z-index:100;width:10px;height:100%;background:#ccc;cursor:ew-resize;opacity:0}.tg-turbogrid .tg-header-column-last .tg-column-resizing{right:0}.tg-turbogrid .tg-column-sortable .tg-column-name{cursor:pointer}.tg-turbogrid .tg-column-sortable .tg-sort-indicator{cursor:pointer}.tg-turbogrid .tg-column-sorted{color:#000}.tg-turbogrid .tg-header-sort-h .tg-column-name{padding:12px 5px 15px}.tg-turbogrid .tg-header-sort-h .tg-column-sort{width:100%;height:15px;margin-top:-15px;padding:0 5px;overflow:hidden}.tg-turbogrid .tg-header-sort-h .tg-sort-indicator{position:relative;display:none;width:100%;height:100%}.tg-turbogrid .tg-header-sort-h .tg-column-sorted .tg-column-sort .tg-sort-indicator{display:block}.tg-turbogrid .tg-header-sort-h .tg-sort-indicator-line{position:absolute;top:1px;width:100%;height:0;border-top:thin solid #1e1e1e;overflow:hidden}.tg-turbogrid .tg-header-sort-h .tg-sort-indicator-icon{position:absolute;top:5px;left:0;right:inherit}.tg-turbogrid .tg-header-sort-h .tg-align-right .tg-sort-indicator-icon{left:inherit;right:0}.tg-turbogrid .tg-header-sort-h .tg-align-center .tg-sort-indicator-icon{left:50%;transform:translateX(-50%)}.tg-turbogrid .tg-header-sort-h .tg-sort-indicator-icon .tg-icon-sort-h{display:block;width:19px;height:6px}.tg-turbogrid .tg-header-sort-h .tg-sort-indicator-icon .tg-icon-item{display:none;fill:#1e1e1e}.tg-turbogrid .tg-header-sort-h .tg-sort-indicator-icon .tg-icon-item-light{fill:#ababab}.tg-turbogrid .tg-column-sort-v{display:flex;flex-direction:row;align-items:center}.tg-turbogrid .tg-column-sort-v .tg-column-name{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.tg-turbogrid .tg-column-sort-v.tg-align-right{justify-content:right}.tg-turbogrid .tg-column-sort-v .tg-sort-indicator{position:relative;width:16px;height:16px}.tg-turbogrid .tg-column-sort-v .tg-sort-indicator-icon{position:absolute}.tg-turbogrid .tg-column-sort-v .tg-sort-indicator-icon .tg-icon-sort-v{display:block;width:10px;height:16px}.tg-turbogrid .tg-column-sort-v .tg-sort-indicator-icon .tg-icon-item{fill:#ababab}.tg-turbogrid .tg-column-sort-v .tg-sort-indicator-icon .tg-icon-item-light{fill:#ababab}.tg-turbogrid .tg-column-sort-v.tg-column-sorted .tg-sort-indicator-icon .tg-icon-item{fill:#1e1e1e}.tg-turbogrid .tg-column-sort-v.tg-column-sorted .tg-sort-indicator-icon .tg-icon-item-light{fill:#ababab}.tg-turbogrid .tg-sort-desc .tg-sort-indicator .tg-sort-indicator-icon .tg-desc{display:block}.tg-turbogrid .tg-sort-desc .tg-sort-indicator .tg-sort-indicator-icon .tg-asc{display:none}.tg-turbogrid .tg-sort-asc .tg-sort-indicator .tg-sort-indicator-icon .tg-desc{display:none}.tg-turbogrid .tg-sort-asc .tg-sort-indicator .tg-sort-indicator-icon .tg-asc{display:block}.tg-turbogrid .tg-column-line{position:absolute;top:0;left:0;z-index:100;display:none;height:100%;pointer-events:none}.tg-turbogrid .tg-column-line-item{position:absolute;top:0;bottom:0;display:block;width:0;height:100%;border-left:thin solid #ccc}.tg-turbogrid .tg-column-line-item.tg-active{border-left:thin solid #0077cf}.tg-turbogrid .tg-column-dragging{cursor:ew-resize}.tg-turbogrid .tg-column-dragging .tg-column-name{cursor:ew-resize}.tg-turbogrid .tg-column-dragging .tg-column-resizing:not(.tg-resizing-active){display:none}.tg-turbogrid .tg-tree{position:relative;display:flex;flex-direction:row;place-items:center left;width:100%;height:100%;overflow:hidden}.tg-turbogrid .tg-tree-icon{position:relative;width:15px;height:100%;min-height:9px;text-align:left;cursor:pointer;overflow:hidden}.tg-turbogrid .tg-tree-icon .tg-icon-tree{position:absolute;top:50%;left:0;display:block;width:9px;height:9px;transform:translate(0, -50%);overflow:hidden}.tg-turbogrid .tg-tree-icon .tg-tree-item{display:none}.tg-turbogrid .tg-tree-icon-collapsed .tg-tree-collapsed{display:block}.tg-turbogrid .tg-tree-icon-empty .tg-tree-collapsed{opacity:.5}.tg-turbogrid .tg-tree-icon-expanded .tg-tree-expanded{display:block}.tg-turbogrid .tg-tree-name{flex:1;text-overflow:ellipsis;overflow:hidden}.tg-turbogrid .tg-tree-header .tg-tree .tg-tree-icon{display:none}.tg-turbogrid .tg-tree-icon-all{position:relative;height:17px}.tg-turbogrid .tg-tree-header-indent .tg-tree{padding-left:5px}.tg-turbogrid .tg-tree-header-indent .tg-tree .tg-tree-icon{display:block}.tg-turbogrid .tg-tree-header-indent.tg-column-sort-h .tg-column-sort{width:calc(100% - 20px);margin-left:20px}.tg-turbogrid .tg-pane{position:absolute;width:100%;outline:0;overflow:hidden}.tg-turbogrid .tg-header-frame{position:relative;display:block;outline:0;overflow:hidden}.tg-turbogrid .tg-header-frame .tg-pane{height:100%}.tg-turbogrid .tg-body-frame{position:relative;width:100%;outline:0}.tg-turbogrid .tg-body-message{position:absolute;display:none;width:100%;height:100%;padding:10px;overflow:hidden}.tg-turbogrid .tg-body-message img,.tg-turbogrid .tg-body-message div{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}.tg-turbogrid .tg-body{position:absolute;outline:0}.tg-turbogrid .tg-cell-hover-icon{display:none}.tg-touch-device.tg-turbogrid .tg-cell-hover-icon{display:inherit}.tg-turbogrid .tg-cell-row-number{font-weight:normal}.tg-turbogrid .tg-cell-row-drag .tg-row-drag-icon{position:absolute;top:50%;left:50%;width:24px;height:24px;cursor:move;opacity:.8;transform:translate(-50%, -50%)}.tg-turbogrid .tg-cell-row-drag .tg-row-drag-icon:hover{opacity:1}.tg-turbogrid .tg-cell{position:absolute;z-index:1;height:100%;margin:0;padding:0 5px;color:#1e1e1e;white-space:nowrap;text-overflow:ellipsis;vertical-align:middle;overflow:hidden}.tg-turbogrid .tg-cell:focus{outline:none}.tg-turbogrid .tg-cell.tg-flashing{border:1px solid red !important}.tg-turbogrid .tg-cell.tg-selected{background-color:beige}.tg-turbogrid .tg-cell.tg-align-left.tg-cell-negative{padding-left:1px}.tg-turbogrid .tg-cell.tg-align-right.tg-cell-negative{padding-right:1px}.tg-turbogrid .tg-cell.tg-cell-observer{display:flex;flex-direction:column;justify-content:center;padding:5px;line-height:normal;white-space:normal;text-overflow:ellipsis;overflow:hidden}.tg-turbogrid .tg-cell.tg-cell-observer .tg-observer{position:relative;margin:0;padding:0;border:none}.tg-turbogrid .tg-row{position:absolute;width:100%;border:0;border-bottom:thin solid #e5e5e5}.tg-turbogrid .tg-row.tg-group-line{border-bottom:thin solid #999}.tg-turbogrid .tg-row.tg-none-line{border-bottom:none}.tg-turbogrid .tg-row.tg-top-line{border-top:thin solid #e5e5e5}.tg-turbogrid .tg-row.tg-group{font-weight:bold;overflow:hidden}.tg-turbogrid .tg-row.tg-group .tg-cell.tg-align-left.tg-cell-negative{padding-left:0}.tg-turbogrid .tg-row.tg-group .tg-cell.tg-align-right.tg-cell-negative{padding-right:0}.tg-turbogrid .tg-row.tg-hover .tg-cell .tg-cell-hover-icon{display:inherit}.tg-turbogrid .tg-row.tg-dragging{opacity:.3}.tg-turbogrid .tg-row.tg-clone{z-index:1000;border:1px dashed #ccc;border-right:none;border-left:none;background:#fff;cursor:move;opacity:.5}.tg-turbogrid .tg-row.tg-clone *{cursor:move}.tg-turbogrid .tg-row-placeholder{position:absolute;z-index:9999;width:100%;border-top:2px solid #00a8e1;pointer-events:none}.tg-turbogrid .tg-row::before,.tg-turbogrid .tg-row::after{position:absolute;top:0;left:0;content:"";z-index:100;display:none;width:100%;height:100%;pointer-events:none}.tg-turbogrid .tg-hover.tg-row::before{display:block;background:rgba(0,0,0,.08)}.tg-turbogrid .tg-selected.tg-row::after{display:block;background:rgba(0,0,0,.13)}.tg-lightblue .tg-header-item{border-top:thin solid #e8eaf0;border-right:thin solid #e8eaf0}.tg-lightblue .tg-column-name{padding:5px;color:#304265}.tg-lightblue .tg-header-group-item::after{display:none}.tg-lightblue .tg-checkbox .tg-icon-item{fill:#d4d7e0}.tg-lightblue .tg-checkbox:hover .tg-icon-item{fill:#107fff}.tg-lightblue .tg-checkbox.tg-selected .tg-select-checkbox{fill:#107fff}.tg-lightblue .tg-checkbox.tg-mixed .tg-select-mixed{fill:#107fff}.tg-lightblue .tg-cell{color:#304265;border-right:thin solid #e8eaf0}.tg-lightblue .tg-row{border-bottom:thin solid #e8eaf0}.tg-lightblue .tg-row.tg-group-line{border-bottom:thin solid #c9ccd8}.tg-lightblue .tg-row.tg-selected{background:rgba(58,116,213,.05)}.tg-lightblue .tg-row.tg-hover{background:rgba(58,116,213,.05)}.tg-lightblue .tg-row.tg-even{background:#fbfcfe}.tg-lightblue .tg-row.tg-odd{background:#fff}.tg-lightblue .tg-hover.tg-row::before{background:rgba(58,116,213,.05)}.tg-lightblue .tg-selected.tg-row::after{background:rgba(58,116,213,.1)}.tg-lightblue .tg-header-frame{border-bottom:thin solid #e8eaf0}.tg-lightblue .tg-row-not-found .tg-frozen-line-v{border-right:none}.tg-lightblue .tg-scrollbar-track{background:#fff}.tg-lightblue .tg-scrollbar-thumb{background:rgba(48,66,101,.35)}.tg-lightblue .tg-scrollbar-thumb:hover{background-color:#a8a8a8}.tg-lightblue .tg-scrollbar-thumb:active{background-color:#787878}.tg-dark{background:#1e1e1e}.tg-dark .tg-checkbox .tg-icon-item{fill:#ababab}.tg-dark .tg-header-table{color:#ccc;border-bottom:thin solid #333}.tg-dark .tg-header-group-item::after{border-bottom:1px solid #999}.tg-dark .tg-column-sorted{color:#fff}.tg-dark .tg-column-sorted .tg-tree-icon-all .tg-icon-item{fill:#fff}.tg-dark .tg-header-sort-h .tg-sort-indicator-line{border-top:thin solid #eee}.tg-dark .tg-header-sort-h .tg-sort-indicator-icon .tg-icon-item{fill:#eee}.tg-dark .tg-header-sort-h .tg-sort-indicator-icon .tg-icon-item-light{fill:#666}.tg-dark .tg-column-sort-v .tg-sort-indicator-icon .tg-icon-item{fill:#666}.tg-dark .tg-column-sort-v .tg-sort-indicator-icon .tg-icon-item-light{fill:#666}.tg-dark .tg-column-sort-v.tg-column-sorted .tg-sort-indicator-icon .tg-icon-item{fill:#fff}.tg-dark .tg-column-sort-v.tg-column-sorted .tg-sort-indicator-icon .tg-icon-item-light{fill:#666}.tg-dark .tg-tree-icon .tg-icon-item{fill:#fff}.tg-dark .tg-tree-icon-all .tg-icon-item{fill:#999}.tg-dark .tg-header-item .tg-tree-icon .tg-icon-item{fill:#999}.tg-dark .tg-header-item .tg-column-sorted .tg-tree-icon .tg-icon-item{fill:#fff}.tg-dark .tg-row{border-bottom:thin solid #333}.tg-dark .tg-row.tg-group-line{border-bottom:thin solid #666}.tg-dark .tg-row.tg-clone{border:1px dashed #1e1e1e;opacity:.1}.tg-dark .tg-cell{color:#eee}.tg-dark .tg-body-message{color:#eee}.tg-dark .tg-hover.tg-row::before{background:rgba(255,255,255,.1)}.tg-dark .tg-selected.tg-row::after{background:rgba(255,255,255,.2)}.tg-dark .tg-mask{background-color:#fff}.tg-dark .tg-scrollbar-track{background:#333}.tg-dark .tg-scrollbar-thumb{background:#bbb}.tg-dark .tg-scrollbar-thumb:hover{background:#ddd}.tg-dark .tg-scrollbar-thumb-hold{background:#eee}.tg-dark .tg-scrollbar-thumb-hold:hover{background:#eee}.tg-pointer-events-none{pointer-events:none}',""]);const l=r},505:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var i="",o=void 0!==e[5];return e[4]&&(i+="@supports (".concat(e[4],") {")),e[2]&&(i+="@media ".concat(e[2]," {")),o&&(i+="@layer".concat(e[5].length>0?" ".concat(e[5]):""," {")),i+=t(e),o&&(i+="}"),e[2]&&(i+="}"),e[4]&&(i+="}"),i})).join("")},e.i=function(t,i,o,n,s){"string"==typeof t&&(t=[[null,t,void 0]]);var r={};if(o)for(var l=0;l0?" ".concat(c[5]):""," {").concat(c[1],"}")),c[5]=s),i&&(c[2]?(c[1]="@media ".concat(c[2]," {").concat(c[1],"}"),c[2]=i):c[2]=i),n&&(c[4]?(c[1]="@supports (".concat(c[4],") {").concat(c[1],"}"),c[4]=n):c[4]="".concat(n)),e.push(c))}},e}},256:t=>{t.exports=function(t){return t[1]}}},e={};function i(o){var n=e[o];if(void 0!==n)return n.exports;var s=e[o]={id:o,exports:{}};return t[o](s,s.exports,i),s.exports}i.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return i.d(e,{a:e}),e},i.d=(t,e)=>{for(var o in e)i.o(e,o)&&!i.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var o={};(()=>{i.d(o,{$:()=>w,MP:()=>e,_d:()=>k,xA:()=>Xt,In:()=>X,T8:()=>B,Gr:()=>kt,ht:()=>Kt,J0:()=>d,xv:()=>Yt,Ay:()=>qt});const t="turbogrid",e={ID:t,NS:`tg-${t}`,VERSION:"3.2.0",TIMESTAMP:"2024-06-20T10:33:49.165Z",UP:"up",DOWN:"down",LEFT:"left",RIGHT:"right",TREE_INDENT:15},n=function(t){if(!t||"object"!=typeof t)return!1;const e=Object.prototype.toString.call(t);return!!["[object Object]","[object Array]"].includes(e)&&(!t.constructor||!![Object,Array].includes(t.constructor))},s=function(t,e){let i;return t.forEach((t=>{n(t)&&(i||(i=t instanceof Array?[]:{}),t instanceof Array?function(t,e,i){const o=e.length;for(let s=0;s{this.execute()})):Promise.resolve().then((()=>{this.execute()}))}execute(){if(!this.started)return;this.started=!1;const t=this.callback;this.callback=null,"function"==typeof t&&t.call(this)}cancel(){this.started=!1,this.callback=null}}const a=new WeakMap,c={isObject:n,merge:l,hasOwn:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},uid:function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:8;const e="0123456789abcdefghijklmnopqrstuvwxyz";let i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";for(;t--;)i+=e[36*Math.random()|0];return i},isNum:function(t){if("number"!=typeof t||isNaN(t))return!1;return(e=t)!==Number.MAX_VALUE&&e!==Number.MIN_VALUE&&e!==Number.NEGATIVE_INFINITY&&e!==Number.POSITIVE_INFINITY;var e},toNum:function(t,e){return"number"!=typeof t&&(t=parseFloat(t)),isNaN(t)&&(t=0),e&&!Number.isInteger(t)&&(t=Math.round(t)),t},convertNum:function(t){if("string"==typeof t){if(/^[-+]?\d+(\.\d+)?$/gi.test(t))return parseFloat(t)}return t},clamp:function(t,e,i){return Math.max(Math.min(t,i),e)},per:function(t){return t=c.toNum(t),t=c.clamp(t,0,1)},replace:function(t,e){return t=`${t}`,e?t=t.replace(/\{([^}]+)\}/g,(function(t,i){return c.hasOwn(e,i)?e[i]:t})):t},isArray:function(t){return!!(t&&t instanceof Array)},toList:function(t){return t instanceof Array?t:void 0===t?[]:"string"==typeof t?[t]:t&&c.hasOwn(t,"length")?Array.from(t):[t]},isList:function(t){return!!(c.isArray(t)&&t.length>0)},inList:function(t,e){if(!c.isList(e))return!1;for(let i=0,o=e.length;i{if(!c.isList(t))return;let n=0;const s=t.length;for(;nt.startsWith(e))).forEach((e=>{t[e]=null}))},hasShiftKey:function(t){let e=!1;return t&&(e=t.shiftKey),e},isTouchDevice:function(){return"ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0},contains:function(t,e){if(!t||!e)return!1;if(t===e)return!0;if("function"==typeof t.contains)return t.contains(e);let i=e.parentNode;for(;i;){if(i===t)return!0;i=i.parentNode}return!1},isNarrowCharacter:function(t){const e=t.codePointAt(0);return e>=32&&e<=126||162===e||163===e||165===e||166===e||172===e||175===e||8361===e||e>=10214&&e<=10221||10629===e||10630===e||e>=65377&&e<=65470||e>=65474&&e<=65479||e>=65482&&e<=65487||e>=65490&&e<=65495||e>=65498&&e<=65500||e>=65512&&e<=65518},getCharLen:function(t){let e=0;if(!t)return e;for(const i of String(t))e+=c.isNarrowCharacter(i)?1:2;return e},pascalToKebabCase:function(t){return`${t}`.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/\W/g,(t=>/[À-ž]/.test(t)?t:"-")).replace(/^-+|-+$/g,"").replace(/-{2,}/g,"-").toLowerCase()},classMap:function(t){if("string"==typeof t)return t.trim();if(Array.isArray(t)){let e=t.filter((t=>t));return e=e.map((t=>t&&"object"==typeof t?c.classMap(t):String(t).trim())),e=e.filter((t=>t)),e=Array.from(new Set(e)),e.join(" ")}if(t&&"object"==typeof t){const e=[];return Object.keys(t).forEach((i=>{t[i]&&e.push(i)})),e.join(" ")}return""},styleMap:function(t){if("string"==typeof t)return t.trim();if(Array.isArray(t)){let e=t.filter((t=>t));return e=e.map((t=>{const e=String(t).trim();return e?-1===e.indexOf(":")?"":e.endsWith(";")?e:`${e};`:""})),e=e.filter((t=>t)),e=Array.from(new Set(e)),e.join(" ")}if(t&&"object"==typeof t){const e=[];return Object.keys(t).forEach((i=>{const o=t[i];if(o||0===o){const t=String(o).trim();t&&e.push(`${c.pascalToKebabCase(i)}: ${t};`)}})),e.join(" ")}return""},getInstance:function(t){if(t){const e=document.getElementById(t);if(e)return a.get(e)}},setInstance:function(t,e){t&&a.set(t,e)},bindEvents:function(t,e){t&&(c.unbindEvents(t),Object.keys(t).forEach((i=>{const o=t[i];o.target=o.target||e,o.target.addEventListener(i,o.handler,o.options)})))},unbindEvents:function(t){t&&Object.keys(t).forEach((e=>{const i=t[e];i.target&&i.target.removeEventListener(e,i.handler,i.options)}))},preventDefault:function(t){t&&"function"==typeof t.preventDefault&&t.cancelable&&t.preventDefault()},debounce:function(t){let e,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;const o=function(){clearTimeout(e),e=setTimeout((()=>{t.apply(this,arguments)}),i)};return o.cancel=()=>{clearTimeout(e)},o},throttle:function(t){let e,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100,o=0;const n=function(){const n=Date.now();if(n>o+i)return clearTimeout(e),o=n,void t.apply(this,arguments);clearTimeout(e),e=setTimeout((()=>{o=n,t.apply(this,arguments)}),i)};return n.cancel=()=>{clearTimeout(e),o=0},n},microtask:function(t){const e=new h,i=function(){e.start((()=>{t.apply(this,arguments)}))};return i.cancel=()=>{e.cancel()},i},nextTick:function(t){"function"==typeof window.queueMicrotask?window.queueMicrotask((()=>{t()})):Promise.resolve().then((()=>{t()}))},cancelAsync:function(t){t&&(Object.keys(t).filter((e=>e.startsWith("async")&&"function"==typeof t[e])).forEach((e=>{const i=t[e];"function"==typeof i.cancel&&(i.cancel(),t[e]=null)})),Object.keys(t).filter((t=>t.startsWith("timeout"))).forEach((e=>{clearTimeout(t[e])})))}},d=c,u={animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},g=function(t){return null!==t&&1===t.nodeType},f=function(t){let e=t.ownerDocument.defaultView;return e&&e.opener||(e=window),e.getComputedStyle(t)},p={},m=function(t,e){return null!=(i=t)&&i===i.window?t[`inner${e}`]:(function(t){return null!==t&&9===t.nodeType}(t)&&(t=t.body),t[`client${e}`]);var i},b=function(t){return this.list=[],t?this.create(t):this};function w(t){return new b(t)}b.prototype={constructor:b,Query:"Query",list:[],create:function(t){return t instanceof b?t:"string"==typeof t?this.createFromString(t):((t.nodeType||t===window)&&(this.list=[t]),this)},createFromString:function(t){if("<"===(t=t.trim())[0]&&">"===t[t.length-1]&&t.length>=3)this.parseHTML(t);else{const e=document.querySelectorAll(t);for(let t=0,i=e.length;t{this.css(e,t[e])}))}var i;return this.each((function(i){let o=e;"number"!=typeof o||u[t]||(o+="px"),i.style[t]=o})),this},attr:function(t,e){if(!t)return this;if(1===arguments.length){if("object"==typeof t)return Object.keys(t).forEach((e=>{this.attr(e,t[e])})),this;const e=this.get(0);return e?e.getAttribute(t):void 0}return this.each((function(i){i.setAttribute(t,e)})),this},removeAttr:function(t){return t?(this.each((function(e){e.hasAttribute(t)&&e.removeAttribute(t)})),this):this},removeClass:function(t){if(!arguments.length)return this.each((function(t){t.className=""})),this;if(!t||"string"!=typeof t)return this;const e=t.split(" ");return this.each((function(t){e.forEach((function(e){e&&t.classList.remove(e)}))})),this},addClass:function(t){if(!t||"string"!=typeof t)return this;const e=t.split(" ");return this.each((function(t){e.forEach((function(e){e&&t.classList.add(e)}))})),this},hasClass:function(t){if(!t||"string"!=typeof t)return!1;let e=!1;return this.each((function(i){if(i.classList.contains(t))return e=!0,!1})),e},show:function(){return this.each((function(t){if(!g(t))return;const e=function(t){if(!p[t]){const e=document.createElement(t);document.body.appendChild(e);const i=f(e).display;e.parentNode.removeChild(e),p[t]=i}return p[t]}(t.nodeName);t.style.display=e})),this},hide:function(){return this.each((function(t){if(!g(t))return;"none"!==t.style.display&&(t.style.display="none")})),this},click:function(){const t=this.get(0);return t&&"function"==typeof t.click&&t.click(),this},offset:function(){const t={left:0,top:0},e=this.get(0);if(e){const i=e.getBoundingClientRect();t.left=i.left+window.scrollX,t.top=i.top+window.scrollY}return t},clone:function(){const t=new b;return this.each((function(e){if(e&&e.cloneNode){const i=e.cloneNode(!0);t.add(i)}})),t},children:function(){const t=new b;return this.each((function(e){let i=e.firstChild;for(;i;)t.add(i),i=i.nextSibling})),t},parent:function(){const t=this.get(0);return t?new b(t.parentNode):new b},is:function(t){if(!t)return!1;const e=t.split(",");let i=!0;return this.each((function(t){if(!t.nodeName)return i=!1,!1;const o=t.nodeName.toLowerCase();return d.inList(o,e)?void 0:(i=!1,!1)})),i}},Object.defineProperty(b.prototype,"length",{get:function(){return this.list.length}});const v={createCache:function(){this.headerCache=new Map,this.rowsCache=new Map,this.dataCache=new WeakMap,this.cellResizeObserver=this.createResizeObserver((t=>{this.cellResizeHandler(t)}))},setHeaderCache:function(t,e){this.headerCache.set(t,e)},getHeaderCache:function(t){return this.headerCache.get(t)},clearHeaderCache:function(){this.headerCache.clear()},setRowCache:function(t,e){this.rowsCache.set(t,{rowNodes:e,cellNodes:new Map,observerNodes:new Map})},getRowCache:function(t){return this.rowsCache.get(t)},deleteRowCache:function(t){const e=this.getRowCache(t);if(!e)return;this.rowsCache.delete(t);const i=e.observerNodes;i&&i.forEach((t=>{t&&this.cellResizeObserver.unobserve(t)}));const o=e.rowNodes;o&&o.each((t=>{this.removeNode(t)}))},deleteCellCache:function(t,e,i){if(i){const e=i.get(t);e&&this.cellResizeObserver.unobserve(e),i.delete(t)}e&&(this.removeNode(e.get(t)),e.delete(t))},getRowNodesByIndex:function(t){const e=this.getRowCache(t);if(e)return e.rowNodes},getCellNodeByIndex:function(t,e){const i=this.getRowCache(t);if(i)return i.cellNodes.get(e)},forEachRowsCache:function(t){this.rowsCache.forEach(((e,i)=>{t.call(this,i,e.rowNodes,e.cellNodes,e.observerNodes)}))},updateRowCacheTopOffset:function(){const t=this.frozenInfo.row;this.forEachRowsCache(((e,i)=>{if(!(e<=t)&&i){const t=this.getViewRowItem(e),o=this.getViewRowTop(t);i.css("top",o)}}))},updateRowCacheTopAndHeight:function(){this.forEachRowsCache(((t,e)=>{if(e){const i=this.getViewRowItem(t),o=this.getViewRowTop(i),n=this.getRowHeight(i);e.css({top:o,height:n,"line-height":n})}}))},setNodeDataCache:function(t,e){if(t)return this.dataCache.set(t,e)},getNodeDataCache:function(t){if(t)return this.dataCache.get(t)},removeCache:function(){this.headerCache=null,this.rowsCache=null,this.dataCache=null,this.cellResizeObserver&&(this.cellResizeObserver.disconnect(),this.cellResizeObserver=null)}},H=["onUpdated","onFirstUpdated","onHeaderUpdated","onSort","onColumnAdded","onColumnRemoved","onColumnWidthChanged","onRowAdded","onRowRemoved","onRowExpanded","onRowCollapsed","onRowSubsRequest","onRowDragged","onRowDropped","onRowMoved","onRowMouseEnter","onRowMouseLeave","onSelectChanged","onCellUpdated","onCellMouseEnter","onCellMouseLeave","onClick","onDblClick","onContextMenu","onMouseOver","onMouseOut","onTouchStart","onTouchMove","onTouchEnd","onScroll","onScrollStateChanged","onMouseWheel","onResize","onLayout","onKeyDown","onDestroy"],C={};H.forEach((t=>{C[t]=t}));const y=C,R={renderCells:function(t,e){t.forEach((t=>{this.drawRowCells(t,e)}))},getCellValue:function(t,e){return t[e.id]},renderCell:function(t){const{rowItem:e,columnItem:i,cellNode:o,observerNode:n}=t,s=this.getCellValue(e,i);let r=s;this.nullFormatter&&(r=this.nullFormatter.call(this,r,e,i,o,n));const l=e.tg_formatter||i.tg_formatter;"function"==typeof l&&(r=l.call(this,r,e,i,o,n));const h=n||o;this.renderNodeContent(h,r);const{highlightKey:a}=this.options.highlightKeywords;e[a+i.id]&&this.renderSettings.highlightCells.push(o),this.trigger(y.onCellUpdated,{value:s,rowItem:e,columnItem:i,node:o})},getPreRenderColumnList:function(t,e){const i=[];if(!e.length)return i;for(let o=0,n=e.length;o{this.createCellNode(t,e)}))},getCellClass:function(t,e,i){const o=e.tg_view_index,n=["tg-cell"];return i&&n.push("tg-cell-observer"),n.push(`tg-c-${o}`),e.align&&n.push(`tg-align-${e.align}`),0===e.tg_list_index&&n.push("tg-list-first"),e.tg_list_last&&n.push("tg-list-last"),n.push(d.classMap(e.classMap)),n.push(d.classMap(t[`${e.id}ClassMap`])),d.classMap(n)},cellResizeObserverHandler:function(t,e){const i=this.options.cellResizeObserver;if("function"==typeof i)return i.apply(this,[t,e])},cellResizeHandler:function(t){const e=new Map;t.forEach((t=>{const{target:i}=t,o=this.getNodeDataCache(i.parentNode);if(!o)return;const{row:n,rowItem:s}=o;e.set(n,s)}));let i=!1;e.forEach(((t,e)=>{const o=this.getRowCache(e);if(!o)return;const n=o.observerNodes;if(!n)return;const{rowHeight:s,rowMinHeight:r}=this.options;let l=Math.max(r||s,1);n.forEach((t=>{if(t){const e=t.clientHeight+11;e>l&&(l=e)}}));this.getRowHeight(t)!==l&&(t.tg_height=l,i=!0)})),i&&this.render("rows_cache")},createCellNode:function(t,e){const i=this.getRowCache(t);if(!i)return;const o=this.getViewRowItem(t),n=this.getViewColumnItem(e);if(!o||!n)return;const s=this.cellResizeObserverHandler(o,n),r=document.createElement("div");r.setAttribute("column",e);const l=this.getCellClass(o,n,s);r.className=l;const h=d.styleMap(n.styleMap)+d.styleMap(o[`${n.id}StyleMap`]);h&&(r.style.cssText=h);const a=i.rowNodes,c=n.tg_frozen,u=this.getCellRowNode(a,c);let g;this.appendNode(u,r),s&&(g=document.createElement("div"),g.className="tg-observer",r.appendChild(g),this.cellResizeObserver.observe(g),i.observerNodes.set(e,g));const f={row:t,rowItem:o,rowNode:u,column:e,columnItem:n,cellNode:r,observerNode:g};this.setNodeDataCache(r,f),i.cellNodes.set(e,r),this.renderCell(f)},getCellRowNode:function(t,e){const i=t.get(0);if(this.frozenInfo.columns){const o=t.get(1);return this.frozenInfo.right?e?o:i:e?i:o}return i}},S={addColumn:function(t,e,i){let o=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];const n=this.getToBeAddedItemList(t);if(!n.length)return!1;let s;if(null!=e&&(s=this.getColumnItem(e),!s))return!1;const r=this.getToBeAddedParentSubs(s,this.columns),l=[this.getToBeAddedPositionIndex(i,r),0].concat(n);r.splice.apply(r,l),this.onNextUpdated((function(){this.trigger(y.onColumnAdded,n)}));const h={type:"columns"};return o&&(h.scrollColumn=n[n.length-1]),this.render(h),!0},deleteColumn:function(t){const e=this.toColumnItemList(t,(t=>!t.private));return!!e.length&&(this.removeColumnsHandler(e),this.onNextUpdated((function(){this.trigger(y.onColumnRemoved,e)})),this.render("columns"),!0)},removeColumnsHandler:function(t){const e=[].concat(t);e.sort((function(t,e){return e.tg_index-t.tg_index})),e.forEach((t=>{let e;if(t===this.sortColumn&&this.removeSortColumn(),t.tg_parent)e=t.tg_parent.subs,e.splice(t.tg_sub_index,1);else{e=this.columns;const i=e.findIndex((e=>e===t));-1!==i&&e.splice(i,1)}!e.length&&t.tg_parent&&(t.tg_parent.subs=null)}))}},T={setColumnWidth:function(t,e){return this.updateColumnWidth(t,e)?(this.resize(),this):this},updateColumnWidth:function(t,e){const i=this.getColumnItem(t);return!!i&&(!!d.isNum(e)&&(e=Math.round(e),e=Math.max(0,e),i.tg_width!==e&&(i.width=e,i.minWidth=Math.min(i.minWidth,e),i.maxWidth=Math.max(i.maxWidth,e),this.updateViewColumnWidth(i),!0)))},showColumn:function(t){return this.updateColumnsInvisible(this.toColumnItemList(t),!1)},hideColumn:function(t){return this.updateColumnsInvisible(this.toColumnItemList(t),!0)},updateColumnsInvisible:function(t,e){if(!t.length)return!1;const i=[];return t.forEach((t=>{t.invisible!==e&&(t.invisible=e,t.tg_invisible=e,i.push(t))})),!!i.length&&(this.render("columns"),!0)}},E={showColumnLine:function(t){t&&(this.$columnLineContainer.show(),this.renderColumnLine(t))},hideColumnLine:function(){this.previousColumnLineActive||this.$columnLineContainer.hide()},setColumnLineActive:function(t){this.setTextSelectable(!t),this.rowHoverable=!t,t!==this.previousColumnLineActive&&(this.previousColumnLineActive=t,t?this.$columnLineItem.addClass("tg-active"):this.$columnLineItem.removeClass("tg-active"))},getColumnLineLeft:function(t){let e=t.tg_left;return t.tg_frozen||(e-=this.scrollLeft),this.frozenInfo.right&&(t.tg_frozen?e=t.tg_left+this.paneWidthL:e-=this.columnsWidthR),e},renderColumnLine:function(t){const e=this.getHeaderItemNode(t).offsetTop,i=t.tg_width,o=this.getColumnLineLeft(t);this.$columnLineItemL.css({top:e,left:o}),this.$columnLineItemR.css({top:e,left:o+i-1}),this.frozenInfo.right||(this.frozenInfo.columns&&!t.tg_frozen&&o{this.renderColumnLine(e.columnItem)})),this.resize()},columnWidthTouchStartHandler:function(t,e){d.preventDefault(e.e);const i=e.columnItem;this.showColumnLine(i),this.setColumnLineActive(!0),e.index=i.tg_index;const o=this.getColumnHeaderNode(i);e.width=o.clientWidth},columnWidthTouchMoveHandler:function(t,e){d.preventDefault(e.e);const i=e.columnItem;let o=e.width+e.offsetX;o=d.clamp(o,i.minWidth,i.maxWidth),i.tg_width!==o&&(i.width=o,this.updateViewColumnWidth(i),this.renderColumnLine(i))},columnWidthTouchEndHandler:function(t,e){d.preventDefault(e.e),this.setColumnLineActive(!1),this.hideColumnLine(),this.resize()}},I={getColumnItem:function(t){return d.isNum(t)?(t<0&&(t=this.columnsInfo.length+t),this.columnsInfo.indexCache[t]):t?d.isNum(t.tg_index)?t:this.getColumnItemById(t.id||t):void 0},getColumnItemById:function(t){return this.getColumnItemBy("id",t)},getColumnItemBy:function(t,e){if(void 0!==e)return this.columnsInfo.indexCache.find((i=>i[t]===e))},getColumnsLength:function(t){return t?this.columnsInfo.length:this.viewColumns.length},getViewColumnItem:function(t){return this.viewAllColumns[t]},isColumnSortable:function(t){return!!t&&(!t.tg_group&&(!(!t.name||!t.id)&&this.isSortable(t)))},isColumnResizable:function(t){return!!t&&(!t.tg_group&&(!d.hasOwn(t,"resizable")||Boolean(t.resizable)))},updateViewColumnWidth:function(t){return t.tg_width=t.width,this.updateColumnHeaderSize(t),this.updateTotalColumnsWidth(),this.updateHeaderLayerHeight(),this.cssRulesInvalid=!0,this.resizeBodyHandler(),this.trigger(y.onColumnWidthChanged,t),!0},updateTotalColumnsWidth:function(){this.blankColumn.tg_width=0;const t=this.viewColumns;let e=0,i=0;const o=this.frozenInfo.columns,n=t.length;let s=0;for(let r=0;r0&&(s+=l,o&&r>=o?i+=l:e+=l)}if(this.frozenInfo.right){const t=e;e=i,i=t}this.columnsWidthL=e,this.columnsWidthR=i,this.columnsWidth=e+i},updateColumnHeaderSize:function(t){this.updateColumnHeaderWidth(t),this.updateColumnHeaderHeight(t,!0),this.updateColumnGroupWidth(t)},updateColumnHeaderWidth:function(t){const e=this.getColumnHeaderNode(t);if(!e)return;const i=t.tg_width;this.isInvisible(t)||i<=0?e.style.display="none":(e.style.display="",e.style.width=`${i}px`)},updateColumnHeaderHeight:function(t,e){if(t.tg_height=0,t.tg_width<=0)return;if(this.isInvisible(t))return;e&&(t.tg_element_height=0);const i=t.tg_element_height;if(i)return void(t.tg_height=i);const o=this.getColumnHeaderHeight(t);t.tg_height=o,t.tg_element_height=o},getColumnHeaderHeight:function(t){const e=this.getColumnHeaderNode(t);return e?e.clientHeight:0},updateColumnGroupWidth:function(t){const e=t.tg_parent;if(!e)return;const i=this.getColumnGroupWidth(e);e.tg_width!==i&&(e.tg_width=i,this.updateColumnHeaderSize(e))},getColumnGroupWidth:function(t){if(this.isInvisible(t))return 0;let e=0;return t.subs&&t.subs.forEach((t=>{this.isInvisible(t)||d.isNum(t.tg_width)&&(e+=t.tg_width)})),e}},L={initTreeInfo:function(t,e){const i=[];let o=!1,n=0,s=0;const r=function(t,r,l){(t=>{t.invisible?t.tg_invisible=!0:t.tg_invisible&&(t.tg_invisible=!1)})(t),((t,i)=>{if(e>=0&&!t.tg_invisible)return t.tg_frozen=!0,void(e-=1);t.tg_frozen&&(t.tg_frozen=!1)})(t),(t=>{if(d.hasOwn(t,"subs")){if(Array.isArray(t.subs))return o=!0,t.tg_group=!0,void(t.tg_subs_length=t.subs.length);t.subs=null}t.tg_group&&(t.tg_group=!1)})(t),((t,e)=>{t.tg_parent=e;let i=0;e&&(i=e.tg_level+1,i>n&&(n=i)),t.tg_level=i})(t,l),t.tg_index=s,t.tg_sub_index=r,i.push(t),s+=1},l=function(t,e){let i=0;const o=t.length;for(;i{if(!this.isInvisible(e))return this.isRowSelectable(e)?t(e,i,o):void 0})),this},toRowItemList:function(t,e){let i=d.toList(t).map((t=>this.getRowItem(t))).filter((t=>t));return"function"==typeof e&&(i=i.filter(e)),i},toColumnItemList:function(t,e){let i=d.toList(t).map((t=>this.getColumnItem(t))).filter((t=>t));return"function"==typeof e&&(i=i.filter(e)),i},isRowLeaf:function(t){return!!t&&("blank"!==t.formatter&&(!t.tg_frozen&&!t.tg_group))},isRowSelectable:function(t){return!!t&&(d.hasOwn(t,"selectable")?Boolean(t.selectable):this.isRowLeaf(t))},isEmptyGroup:function(t){return!(!t||!t.tg_group||0!==t.tg_subs_length)},isInvisible:function(t){return!!t&&(!(!t.tg_filtered&&!t.tg_invisible)||!!this.isInvisible(t.tg_parent))},isSortable:function(t){return!!t&&(!d.hasOwn(t,"sortable")||Boolean(t.sortable))},isCollapsedChanged:function(t,e){return Boolean(t.collapsed)!==e},isSelectedChanged:function(t,e){return Boolean(t.selected)!==e}},x={updateCssRules:function(){this.cssRulesInvalid&&(this.cssRulesInvalid=!1,this.initCssRules(),this.updateColumnsCssRules(),this.updateHeadersCssRules(),this.updateStyleElement())},initCssRules:function(){this.removeCssRules(),this.cssList={},this.cssDisplayCache={};const t=this.getRowHeight(),e=this.createCssRule(".tg-row");e.height=`${t}px`,e["line-height"]=`${t}px`},resetCssDisplay:function(t){if(this.cssDisplayCache){t=t||"";for(const e in this.cssDisplayCache)if(d.hasOwn(this.cssDisplayCache,e)){this.cssDisplayCache[e].style.display=t}}},updateColumnsCssRules:function(){const t=this.viewColumns,e=this.frozenInfo.column,i={};let o=0;for(let n=0,s=t.length;n=0;i--){const e=this.headerLayerHeight[i],o=this.createCssRule(`.tg-h-${i}`);o.bottom=`${t}px`,o.height=`${e}px`,t+=e}this.getLayerCombinations(e).forEach((t=>{const e=this.createCssRule(`.tg-h-${t}`);let i=0;t.split("").forEach((t=>{i+=this.headerLayerHeight[t]||0})),e.height=`${i}px`}))},getLayerCombinations:function(t){let e="";for(;t>=0;)e+=t,t--;if(e.length<2)return[];const i=[],o=function(t,e){const n=t.length;let s=e+2;for(;s<=n;){const o=t.substring(e,s);i.push(o),s++}e=i){let t="Possible Event memory leak detected. ";return t+=`More than ${i} (max limit) listeners added. `,t+="Use setMaxListeners(n) to increase limit.",void console.warn(t,e)}t.events.push(e)},addEvents:function(t,e,i){e.forEach((function(e){const o=e.type;t[o]||(t[o]={events:[]});if("function"!=typeof e.handler)return;const n=t[o];N.addEvent(n,e,i)}))},removeEventByNamespace:function(t,e){Object.keys(t).forEach((function(i){const o=t[i],n=[];o.events.forEach((function(t){t&&t.namespace!==e&&n.push(t)})),o.events=n}))},removeEventByHandler:function(t,e,i){const o=t[e];if(!o)return;const n=[];o.events.forEach((function(t){t&&t.handler!==i&&n.push(t)})),o.events=n},removeEventByType:function(t,e){const i=t[e];i&&(i.events=[])},removeEvent:function(t,e){const i=e.type,o=e.namespace;if(!i&&o)return void N.removeEventByNamespace(t,o);const n=e.handler;"function"!=typeof n?N.removeEventByType(t,i):N.removeEventByHandler(t,i,n)},removeEvents:function(t,e){e.forEach((function(e){N.removeEvent(t,e)}))},removeAllEvents:function(t){Object.keys(t).forEach((function(e){N.removeEventByType(t,e)}))},sendEventList:function(t,e,i,o){const n=e.events;for(let e=0;e!t.onceCalled))},sendEvent:function(t,e,i,o){const n=e[i];if(!n)return;const s=new P({type:i,target:t,currentTarget:t,data:o});N.sendEventList(t,n,s,o)}},_=N;class k{maxListeners=10;setMaxListeners(t){this.maxListeners=Number(t)||10}getMaxListeners(){return this.maxListeners}getEventListeners(){return this.eventListeners||(this.eventListeners={}),this.eventListeners}delEventListeners(){this.eventListeners=null}bind(t,e,i){const o=_.getEventList(this,t,e,i);if(!o.length)return this;const n=this.getEventListeners();return _.addEvents(n,o,this.maxListeners),this}once(t,e){return this.bind(t,e,{once:!0})}unbind(t,e,i){const o=this.getEventListeners();if(!arguments.length)return _.removeAllEvents(o),this;const n=_.getEventList(this,t,e,i);return n.length?(_.removeEvents(o,n),this):this}trigger(t,e){const i=this.getEventListeners();return _.sendEvent(this,i,t,e),this}}const V={DRAG_START:"drag_start",DRAG_MOVE:"drag_move",DRAG_END:"drag_end"};class O extends k{static EVENT=V;generateOptions(t){return d.merge({type:"mouse",startX:0,startY:0,previousX:0,previousY:0,currentX:0,currentY:0,moveX:0,moveY:0,offsetX:0,offsetY:0,changed:!1},t)}start(t,e){t&&(this.unbindEvents(),this.bindEvents(),this.options=this.generateOptions(e),this.startHandler(t))}bindEvents(){this.windowEvents={mousemove:{handler:t=>{this.iframeHandler(t),this.mouseMoveHandler(t)},options:!0},mouseup:{handler:t=>{this.mouseUpHandler(t)},options:{once:!0}}},d.bindEvents(this.windowEvents,window)}unbindEvents(){d.unbindEvents(this.windowEvents),this.windowEvents=null,this.previousIframe&&(this.previousIframe.classList.remove("tg-pointer-events-none"),this.previousIframe=null)}iframeHandler(t){const e=t.target;"IFRAME"===e.nodeName&&e!==this.previousIframe&&(this.previousIframe&&this.previousIframe.classList.remove("tg-pointer-events-none"),e.classList.add("tg-pointer-events-none"),this.previousIframe=e)}startHandler(t){const e=this.options;e.e=t,e.startX=t.pageX,e.startY=t.pageY,e.currentX=e.startX,e.currentY=e.startY,this.hasMoved=!1}mouseMoveHandler(t){d.preventDefault(t);const e=this.options;e.e=t,e.previousX=e.currentX,e.previousY=e.currentY,e.currentX=t.pageX,e.currentY=t.pageY,e.moveX=e.currentX-e.previousX,e.moveY=e.currentY-e.previousY,e.offsetX=e.currentX-e.startX,e.offsetY=e.currentY-e.startY,e.changed=!(0===e.offsetX&&0===e.offsetY),this.hasMoved?this.trigger(V.DRAG_MOVE,e):(this.hasMoved=!0,this.trigger(V.DRAG_START,e))}mouseUpHandler(t){this.unbindEvents();const e=this.options;this.hasMoved&&(e.e=t,d.preventDefault(t),this.trigger(V.DRAG_END,e))}destroy(){this.unbindEvents(),this.unbind()}}const $={Linear:{None:function(t){return t}}},D={MOTION_START:"motion_start",MOTION_MOVE:"motion_move",MOTION_END:"motion_end",MOTION_STOP:"motion_stop"};class B extends k{static EVENT=D;constructor(t){super(),this.constructorOptions=t,this.stopped=!0}generateOptions(t){return d.merge({easing:null,duration:100,from:0,till:1,data:0},this.constructorOptions,t)}stop(){return this.stopped||(this.stopped=!0,this.cancelAnimationFrame(),this.trigger(D.MOTION_STOP,this.data)),this}start(t){return this.stop(),this.stopped=!1,this.options=this.generateOptions(t),this.initCalculation(),this.data=this.calculateHandler(0),this.trigger(D.MOTION_START,this.data),this.stopped||(this.time=Date.now(),this.requestAnimationFrame(this.moveHandler)),this}requestAnimationFrame(t){this.requestId=window.requestAnimationFrame((()=>{t.apply(this)}))}cancelAnimationFrame(){window.cancelAnimationFrame(this.requestId)}getEasing(t){return"function"!=typeof t&&(t=d.getValue($,t,$.Linear.None)),t}moveHandler(){const t=Date.now()-this.time,e=this.duration;if(t{o[n]=this.calculateNumber(t,e[n],i[n])})),o):(this.calculateKeys=[],Object.keys(e).forEach((n=>{const s=e[n],r=i[n];d.isNum(s)&&d.isNum(r)&&(o[n]=this.calculateNumber(t,s,r),this.calculateKeys.push(n))})),o)}calculateNumber(t,e,i){return(i-e)*t+e}calculateNone(t,e,i){return e}destroy(){this.stop(),this.unbind()}}const A={TOUCH_START:"touch_start",TOUCH_MOVE:"touch_move",TOUCH_END:"touch_end",TOUCH_INERTIA:"touch_inertia"};class W extends k{static EVENT=A;generateOptions(t){return d.merge({type:"touch",startX:0,startY:0,previousX:0,previousY:0,currentX:0,currentY:0,moveX:0,moveY:0,offsetX:0,offsetY:0,changed:!1,touchLength:0,direction:"",inertia:!1,inertiaTime:200},t)}start(t,e){t&&(this.unbindEvents(),this.bindEvents(),this.options=this.generateOptions(e),this.startHandler(t))}bindEvents(){this.touchEvents={touchmove:{handler:t=>{this.touchMoveHandler(t)},options:{passive:!1}},touchend:{handler:t=>{this.touchEndHandler(t)},options:{passive:!1,once:!0}},touchcancel:{handler:t=>{this.touchCancelHandler(t)},options:{passive:!1,once:!0}}},d.bindEvents(this.touchEvents,document.body)}unbindEvents(){this.motionStop(),d.unbindEvents(this.touchEvents),this.touchEvents=null}startHandler(t){this.trackingPoints=[];const e=t.touches,i=e[0];if(!i)return;const o=this.options;o.e=t,o.startX=i.clientX,o.startY=i.clientY,o.currentX=o.startX,o.currentY=o.startY,o.touchLength=e.length,this.addTrackingPoint(o),this.trigger(A.TOUCH_START,o)}touchMoveHandler(t){const e=t.touches,i=e[0];if(!i)return;const o=this.options;o.e=t,o.previousX=o.currentX,o.previousY=o.currentY,o.currentX=i.clientX,o.currentY=i.clientY,o.moveX=o.currentX-o.previousX,o.moveY=o.currentY-o.previousY,o.offsetX=o.currentX-o.startX,o.offsetY=o.currentY-o.startY,o.changed=!(0===o.offsetX&&0===o.offsetY),o.touchLength=e.length,o.direction=this.getDirection(o),this.addTrackingPoint(o),this.trigger(A.TOUCH_MOVE,o)}touchEndHandler(t){this.unbindEvents();const e=this.options;e.e=t,this.trigger(A.TOUCH_END,e);const i=t.changedTouches[0];if(!i)return;const o=t.touches;e.touchLength=o.length,e.touchLength>0||(e.currentX=i.clientX,e.currentY=i.clientY,this.addTrackingPoint(e),this.motionStart())}touchCancelHandler(t){this.unbindEvents(),this.trigger(A.TOUCH_END,this.options)}getMotionInfo(){const t=this.trackingPoints;if(t.length<2)return;if(this.filterTrackingPoints(t),t.length<2)return;const e=t[0],i=t[t.length-1],o=i.t-e.t;if(o<=0)return;let n=i.x-e.x,s=i.y-e.y;const r=Math.abs(n),l=Math.abs(s);r>l?s=0:n=0;return{offsetDistance:Math.max(r,l),offsetTime:o,offsetX:n,offsetY:s}}motionStart(){const t=this.options;if(!t.inertia)return;const e=this.getMotionInfo();if(!e)return;const i=500*e.offsetDistance/50,o=d.clamp(i,20,2e3),n={x:20*(e.offsetX/e.offsetTime),y:20*(e.offsetY/e.offsetTime)};this.motion=new B,this.motion.bind(B.EVENT.MOTION_MOVE,((e,i)=>{t.touchInertiaX=i.x,t.touchInertiaY=i.y,this.trigger(A.TOUCH_INERTIA,t)})),this.motion.start({duration:o,from:n,till:{x:0,y:0}})}motionStop(){this.motion&&(this.motion.destroy(),this.motion=null)}getDirection(t){const i=t.offsetX,o=t.offsetY,n=Math.abs(i),s=Math.abs(o);if(n0)return e.UP;if(o<0)return e.DOWN}if(n>s){if(i>0)return e.LEFT;if(i<0)return e.RIGHT}return""}filterTrackingPoints(t){t.reverse();const e=t.length,i=Date.now(),o=this.options.inertiaTime;for(let n=0;no){t.length=n;break}t.reverse()}addTrackingPoint(t){if(!t.inertia)return;const e=t.currentX,i=t.currentY,o=Date.now(),n=this.trackingPoints;n.push({x:e,y:i,t:o}),n.length>100&&this.filterTrackingPoints(n)}destroy(){this.unbindEvents(),this.unbind()}}const F={getAllEvents:function(){return[].concat(H)},bindEvents:function(){this.unbindEvents(),this.containerEvents={mousedown:{handler:t=>{this.containerMouseDownHandler(t)},options:!0},mousemove:{handler:t=>{this.containerMouseMoveHandler(t)},options:!0},mouseover:{handler:t=>{this.containerMouseOverOutHandler(t,!0)},options:!0},mouseout:{handler:t=>{this.containerMouseOverOutHandler(t,!1)},options:!0},mouseenter:{handler:t=>{this.containerMouseEnterLeaveHandler(t,!0)},options:!0},mouseleave:{handler:t=>{this.containerMouseEnterLeaveHandler(t,!1)},options:!0},touchstart:{handler:t=>{this.containerTouchStartHandler(t)},options:{passive:!1}},touchmove:{handler:t=>{this.containerTouchMoveHandler(t)},options:{passive:!1}},touchend:{handler:t=>{this.containerTouchEndHandler(t)},options:{passive:!1}},touchcancel:{handler:t=>{this.containerTouchCancelHandler(t)},options:{passive:!1}},wheel:{handler:t=>{this.containerWheelHandler(t)},options:{passive:!1}},click:{handler:t=>{this.containerClickHandler(t)},options:!0},dblclick:{handler:t=>{this.containerDblClickHandler(t)},options:!0},contextmenu:{handler:t=>{this.containerContextMenuHandler(t)},options:!0},selectstart:{handler:t=>{this.containerSelectStartHandler(t)},options:!0},keydown:{handler:t=>{this.containerKeyDownHandler(t)},options:!0}},d.bindEvents(this.containerEvents,this.container),this.columnWidthDrag=new O,this.columnWidthDrag.bind(O.EVENT.DRAG_START,((t,e)=>{this.columnWidthDragStartHandler(t,e)})).bind(O.EVENT.DRAG_MOVE,((t,e)=>{this.columnWidthDragMoveHandler(t,e)})).bind(O.EVENT.DRAG_END,((t,e)=>{this.columnWidthDragEndHandler(t,e)})),this.columnWidthTouch=new W,this.columnWidthTouch.bind(W.EVENT.TOUCH_START,((t,e)=>{this.columnWidthTouchStartHandler(t,e)})).bind(W.EVENT.TOUCH_MOVE,((t,e)=>{this.columnWidthTouchMoveHandler(t,e)})).bind(W.EVENT.TOUCH_END,((t,e)=>{this.columnWidthTouchEndHandler(t,e)})),this.rowDrag=new O,this.rowDrag.bind(O.EVENT.DRAG_START,((t,e)=>{this.rowDragStartHandler(t,e)})).bind(O.EVENT.DRAG_MOVE,((t,e)=>{this.rowDragMoveHandler(t,e)})).bind(O.EVENT.DRAG_END,((t,e)=>{this.rowDragEndHandler(t,e)})),this.rowTouch=new W,this.rowTouch.bind(W.EVENT.TOUCH_START,((t,e)=>{this.rowDragStartHandler(t,e)})).bind(W.EVENT.TOUCH_MOVE,((t,e)=>{this.rowDragMoveHandler(t,e)})).bind(W.EVENT.TOUCH_END,((t,e)=>{this.rowDragEndHandler(t,e)})),this.scrollTouch=new W,this.scrollTouch.bind(W.EVENT.TOUCH_START,((t,e)=>{this.scrollTouchStartHandler(t,e)})).bind(W.EVENT.TOUCH_MOVE,((t,e)=>{this.scrollTouchMoveHandler(t,e)})).bind(W.EVENT.TOUCH_END,((t,e)=>{this.scrollTouchEndHandler(t,e)})).bind(W.EVENT.TOUCH_INERTIA,((t,e)=>{this.scrollTouchInertiaHandler(t,e)}))},isDefaultPrevented:function(t){if(t){if(t.defaultPrevented)return!0;if(t.e&&t.e.defaultPrevented)return!0}return!1},getEventClosestNode:function(t,e){if(t&&t!==this.container)return t.classList.contains(e)?t:this.getEventClosestNode(t.parentNode,e)},getEventClosestData:function(t){if(!t||t===this.container)return;const e=this.getNodeDataCache(t);return e||this.getEventClosestData(t.parentNode)},getEventData:function(t){const e=this.getEventClosestData(t.target);if(e)return e.e=t,e},getWheelDelta:function(t,e,i){let o=t.deltaX,n=t.deltaY;return d.isNum(o)||(o=d.toNum(t.wheelDeltaX)),d.isNum(n)||(n=d.toNum(t.wheelDeltaY||t.wheelDelta)),1===t.deltaMode?(n*=e,o*=e):2===t.deltaMode&&(n*=i,o*=i),{deltaX:o,deltaY:n}},columnResizingMouseDownHandler:function(t){const e=this.getEventData(t);e&&this.columnWidthDrag.start(t,{columnItem:e.columnItem})},columnResizingTouchStartHandler:function(t){const e=this.getEventData(t);e&&this.columnWidthTouch.start(t,{columnItem:e.columnItem})},columnResizingMouseEnterLeaveHandler:function(t,e){const i=this.getEventData(t);i&&(e?this.showColumnLine(i.columnItem):this.hideColumnLine())},rowDragMouseDownHandler:function(t){const e=this.getEventData(t);e&&this.rowDrag.start(t,{rowItem:e.rowItem})},rowDragTouchStartHandler:function(t){const e=this.getEventData(t);e&&(this.protectedItem=e,this.rowTouch.start(t,{rowItem:e.rowItem}))},scrollPaneTouchStartHandler:function(t){if(!this.hasHScroll&&!this.hasVScroll)return;const e=this.getEventData(t);this.protectedItem=e,this.scrollTouch.start(t,{inertia:!0})},sortHandler:function(t,e){const i=e.columnItem;if(!this.isColumnSortable(i))return;const o=this.getEventClosestNode(t.target,"tg-column-name"),n=this.getEventClosestNode(t.target,"tg-column-sort");(o||n)&&(this.trigger(y.onSort,e),this.isDefaultPrevented(e)||this.setSortColumn(i))},selectIconAllClickHandler:function(t){const e=w(t);let i=!1;(e.hasClass("tg-selected")||e.hasClass("tg-mixed"))&&(i=!0),i=!i,this.selectAll(i)},cellEnterLeaveHandler:function(t,e){const i=this.getEventData(t);i&&(e?this.trigger(y.onCellMouseEnter,i):this.trigger(y.onCellMouseLeave,i))},rowEnterLeaveHandler:function(t,e){const i=this.getEventData(t);if(i&&(e?this.trigger(y.onRowMouseEnter,i):this.trigger(y.onRowMouseLeave,i),!this.isDefaultPrevented(i)))return this.renderRowHover(i.rowItem,e),this},containerMouseDownHandler:function(t){if(this.getEventClosestNode(t.target,"tg-column-resizing"))this.columnResizingMouseDownHandler(t);else if(this.options.rowDragVisible){this.getEventClosestNode(t.target,"tg-row-drag-icon")&&this.rowDragMouseDownHandler(t)}},containerMouseMoveHandler:function(t){this.scrollbarFadeInOutHandler(t,!0)},containerMouseOverOutHandler:function(t,e){const i=this.getEventClosestNode(t.target,"tg-cell"),o=this.getEventClosestNode(t.target,"tg-header-item");if(i||o){const i=this.getEventData(t);if(!i)return;e?this.trigger(y.onMouseOver,i):this.trigger(y.onMouseOut,i)}},containerMouseEnterLeaveHandler:function(t,e){this.scrollbarFadeInOutHandler(t,e);if(w(t.target).hasClass("tg-column-resizing"))return void this.columnResizingMouseEnterLeaveHandler(t,e);if(w(t.target).hasClass("tg-cell"))return void this.cellEnterLeaveHandler(t,e);w(t.target).hasClass("tg-row")&&this.rowEnterLeaveHandler(t,e)},containerTouchStartHandler:function(t){this.scrollTouch.motionStop();if(this.getEventClosestNode(t.target,"tg-column-resizing"))return void this.columnResizingTouchStartHandler(t);if(this.options.rowDragVisible){if(this.getEventClosestNode(t.target,"tg-row-drag-icon"))return void this.rowDragTouchStartHandler(t)}const e=this.getEventData(t);e&&(this.trigger(y.onTouchStart,e),this.isDefaultPrevented(e))||this.scrollPaneTouchStartHandler(t)},containerTouchMoveHandler:function(t){const e=this.getEventData(t);e&&this.trigger(y.onTouchMove,e)},containerTouchEndHandler:function(t){const e=this.getEventData(t);e&&this.trigger(y.onTouchEnd,e)},containerTouchCancelHandler:function(t){this.trigger(y.onTouchEnd,{e:t})},containerWheelHandler:function(t){if(this.hasMask)return;const e=this.getRowHeight(),i=this.bodyHeight,o=this.getWheelDelta(t,e,i),n={e:t,deltaX:o.deltaX,deltaY:o.deltaY,delta:o};if(this.trigger(y.onMouseWheel,n),this.isDefaultPrevented(n))return;let s=!1;this.scrollPaneHidden&&(s=this.scrollPaneFrozen.setOffsetH(o.deltaX),o.deltaX=0);(this.scrollPane.mouseWheelHandler(o)||s)&&d.preventDefault(t)},containerClickHandler:function(t){if(this.getEventClosestNode(t.target,"tg-tree-icon-all"))return void this.toggleAllRows();const e=this.getEventClosestNode(t.target,"tg-select-icon-all");if(e)return void this.selectIconAllClickHandler(e);const i=this.getEventData(t);if(!i)return;if(this.getEventClosestNode(t.target,"tg-header-item")){if(this.trigger(y.onClick,i),this.isDefaultPrevented(i))return;return void this.sortHandler(t,i)}if(this.getEventClosestNode(t.target,"tg-tree-icon"))return void this.toggleRow(i.rowItem);this.getEventClosestNode(t.target,"tg-select-icon")?this.setRowSelected(i.rowItem,t):this.trigger(y.onClick,i)},containerDblClickHandler:function(t){const e=this.getEventData(t)||{e:t};this.trigger(y.onDblClick,e)},containerContextMenuHandler:function(t){const e=this.getEventData(t)||{e:t};this.trigger(y.onContextMenu,e)},containerSelectStartHandler:function(t){if(this.options.textSelectable)return;w(t.target).is("input,textarea,code")||d.preventDefault(t)},containerKeyDownHandler:function(t){if(this.hasMask)return;const e={e:t};if(this.trigger(y.onKeyDown,e),this.isDefaultPrevented(e))return;const i=t.keyCode,o={9:this.keyTabHandler,13:this.keyEnterHandler,27:this.keyEscHandler,33:this.keyPageUpHandler,34:this.keyPageDownHandler,35:this.keyEndHandler,36:this.keyHomeHandler,37:this.keyLeftHandler,38:this.keyUpHandler,39:this.keyRightHandler,40:this.keyDownHandler}[i];if(!o)return;o.call(this,t)&&d.preventDefault(t)},unbindEvents:function(){d.unbindEvents(this.containerEvents),this.containerEvents=null,this.columnWidthDrag&&(this.columnWidthDrag.destroy(),this.columnWidthDrag=null),this.columnWidthTouch&&(this.columnWidthTouch.destroy(),this.columnWidthTouch=null),this.rowDrag&&(this.rowDrag.destroy(),this.rowDrag=null),this.rowTouch&&(this.rowTouch.destroy(),this.rowTouch=null),this.scrollTouch&&(this.scrollTouch.destroy(),this.scrollTouch=null),this.protectedItem=null}},G={exportData:function(t){const e=this.getData();return{columns:this.getTreeSnapshot(e.columns,t),rows:this.getTreeSnapshot(e.rows,t)}},isItemExportable:function(t){return!!t&&(!d.hasOwn(t,"exportable")||Boolean(t.exportable))},getTreeSnapshot:function(t,e){const i=(t,o)=>{d.isList(o)&&o.forEach((o=>{if(!this.isItemExportable(o))return;const n=this.getItemSnapshot(o,e),s=o.subs;Array.isArray(s)&&(n.subs=[],i(n.subs,s)),t.push(n)}))},o=[];return i(o,t),o},getItemSnapshot:function(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const i={};return Object.keys(t).forEach((o=>{!0!==e[o]?!1!==e[o]&&"subs"!==o&&0!==o.indexOf("tg_")&&(i[o]=t[o]):i[o]=t[o]})),i}},j={flushRow:function(t){d.toList(t).forEach((t=>{this.deleteRowCache(t)}))},flushRowFrom:function(t){d.isNum(t)&&(0!==t?this.forEachRowsCache((e=>{e>=t&&this.deleteRowCache(e)})):this.flushBody())},flushBody:function(){this.forEachRowsCache((t=>{this.deleteRowCache(t)}))},flushSort:function(){this.frozenInfo.rows?this.flushRowFrom(this.frozenInfo.rows):this.flushBody()},flushColumn:function(t){const e=d.toList(t);this.forEachRowsCache(((t,i,o,n)=>{e.forEach((t=>{this.deleteCellCache(t,o,n)}))}))},flushColumnFrom:function(t){d.isNum(t)&&this.forEachRowsCache(((e,i,o,n)=>{o.forEach(((e,i)=>{i>=t&&this.deleteCellCache(i,o,n)}))}))},flushCell:function(t,e){const i=d.toList(t),o=d.toList(e);i.forEach((t=>{const e=this.getRowCache(t);if(!e)return;const i=e.cellNodes,n=e.observerNodes;o.forEach((t=>{this.deleteCellCache(t,i,n)}))}))},flushWithViewport:function(){const{rows:t,columns:e}=this.viewport;this.forEachRowsCache(((i,o,n,s)=>{t.includes(i)?n.forEach(((t,i)=>{e.includes(i)||this.deleteCellCache(i,n,s)})):this.deleteRowCache(i)}))}},U={"sort-h":'\n\n \n \n \n \n\n',"sort-v":'\n\n \n \n \n \n\n',checkbox:'\n\n \n \n \n\n',radio:'
    ',drag:'\n\n \n\n',tree:'\n\n \n \n\n'},X={icons:U,getIcon:function(t){let e=U[t];return e=String(e).trim(),e}},Y={header:function(t,e,i,o){return t},null:function(t,e,i,o){return e&&e.tg_group?t:null==t?"—":t},blank:function(t,e,i,o){return""},string:function(t,e,i,o){return t},number:function(t,e,i,o){return t},icon:function(t,e,i,o){return`${t}`},select:function(t,e,i,o){return this.isRowSelectable(e)?this.getSelectFormatterContent(e):""},rowDrag:function(t,e,i,o){return this.getRowDragFormatterContent(e)},rowNumber:function(t,e,i,o){return e.tg_row_number||""},tree:function(t,e,i,o){return this.getTreeFormatterContent(t,e)}},K={setFormatter:function(t,e){this.renderType="all";let i=t;if("string"==typeof t){if(this.formatters)return this.formatters[t]=e,this;i={},i[t]=e}return this.customFormatters=i,this},getFormatter:function(t){if(!t)return;const e=this.formatters[t];return"function"==typeof e?e.bind(this):void 0},getDefaultFormatter:function(t){return(Y[t]||Y.string).bind(this)},getSelectFormatterContent:function(t){let e="radio";this.options.selectMultiple&&(e="checkbox");const i=X.getIcon(e);return`
    ${i}
    `},getRowDragFormatterContent:function(t){if(t.tg_frozen)return"";return`
    ${X.getIcon("drag")}
    `},getTreeIndentWidth:function(t,i,o){if(!t)return 0;let n=5;return i||(n+=e.TREE_INDENT),n+=o*e.TREE_INDENT,n},getTreeFormatterContent:function(t,e){const i=this.rowsInfo.isTree,o=e.tg_group,n=this.isEmptyGroup(e);n&&(e.collapsed=!0);const s=e.collapsed,r=d.toNum(e.tg_level),l=this.getTreeIndentWidth(i,o,r),h=[];if(h.push(`
    `),o){const t={"tg-tree-icon":!0,"tg-tree-icon-collapsed":s,"tg-tree-icon-expanded":!s,"tg-tree-icon-empty":n},e=X.getIcon("tree"),i=`
    ${e}
    `;h.push(i)}return h.push(`
    ${t}
    `),h.push("
    "),h.join("")}},q={renderHeaderTables:function(){this.clearHeaderCache();const t=this.viewColumns,e=this.frozenInfo.columns;this.hasTreeColumn=!1,this.hasSortColumn=!1;let i=[],o=[];for(let n=0,s=t.length;n=e?o.push(s):i.push(s)}if(this.frozenInfo.right){const t=i;i=o,o=t}this.renderHeaderTable(i,this.$headerL),this.renderHeaderTable(o,this.$headerR)},renderHeaderTable:function(t,e){const i=document.createElement("div"),o=["tg-header-table"];this.hasSortColumn&&(o.push("tg-header-sortable"),o.push(`tg-header-sort-${this.options.sortIndicator}`)),i.className=d.classMap(o);const n=t.length;if(n){let e=t[n-1];e&&"tg-column-blank"===e.id&&(e=t[n-2]),t.forEach((t=>{this.renderHeaderItem(t,i,e)}))}e.append(i)},renderHeaderItem:function(t,e,i){const o=t.tg_view_index;if(this.getHeaderCache(o))return;const n=this.getHeaderItemClass(t,i),s=d.styleMap(t.headerStyleMap),r={column:o,class:n,data:t.id};s&&(r.style=s);const l=[this.createColumnHeader(t)];if(this.isColumnResizable(t)){const e=this.createColumnResizing(t);l.push(e)}const h=this.createElement("div",r,l);e.appendChild(h),this.setHeaderCache(o,h),this.setNodeDataCache(h,{rowItem:this.headerRowItem,column:o,columnItem:t,headerNode:h}),t.tg_parent&&this.renderHeaderItem(t.tg_parent,e)},createColumnHeader:function(t){const e={class:this.getHeaderClass(t),style:this.getHeaderStyle(t)},i=[this.createColumnName(t)];if(this.hasSortColumn&&!t.tg_group){const e=this.createColumnSort(t);i.push(e)}return this.createElement("div",e,i)},createColumnName:function(t){const e=["tg-column-name"];t.tg_group&&e.push("tg-header-group-name");const i={class:e.join(" ")};let o=t.name;const n=t.tg_headerFormatter;return"function"==typeof n&&(o=n.call(this,o,this.headerRowItem,t)),"tree"===t.formatter?o=this.createHeaderTreeName(o):t===this.selectColumn&&this.isSelectAllVisible()&&(o=this.createHeaderSelectName()),this.createElement("div",i,o)},createHeaderTreeName:function(t){this.hasTreeColumn=!0;const e=[];if(this.options.collapseAllVisible){const t=X.getIcon("tree"),i=this.createElement("div",{class:"tg-tree-icon tg-tree-icon-all"},t);e.push(i)}else{const t=this.createElement("div",{class:"tg-tree-icon"});e.push(t)}const i=this.createElement("div",{class:"tg-tree-name"},t);e.push(i);return this.createElement("div",{class:"tg-tree"},e)},createHeaderSelectName:function(){const t=X.getIcon("checkbox");return this.createElement("div",{class:"tg-select-icon-all tg-checkbox"},t)},createColumnSort:function(t){let e;return this.isColumnSortable(t)&&(e="h"===this.options.sortIndicator?this.createSortIndicatorH(t):this.createSortIndicatorV(t)),this.createElement("div",{class:"tg-column-sort"},e)},createSortIndicatorH:function(t){const e=X.getIcon("sort-h"),i=[this.createElement("div",{class:"tg-sort-indicator-line"}),this.createElement("div",{class:"tg-sort-indicator-icon"},e)];return this.createElement("div",{class:"tg-sort-indicator"},i)},createSortIndicatorV:function(t){const e=X.getIcon("sort-v"),i=[this.createElement("div",{class:"tg-sort-indicator-icon"},e)];return this.createElement("div",{class:"tg-sort-indicator"},i)},createColumnResizing:function(){return this.createElement("div",{class:"tg-column-resizing"})},getHeaderItemClass:function(t,e){const i=["tg-header-item"];return t.tg_group&&i.push("tg-header-group-item"),t===e&&i.push("tg-header-column-last"),i.push(`tg-c-${t.tg_view_index}`),i.push(`tg-h-${t.tg_layer}`),t.tg_combination&&i.push(`tg-h-${t.tg_combination}`),i.push(d.classMap(t.headerClassMap)),d.classMap(i)},getHeaderClass:function(t){const e=["tg-column-header"];return"tree"===t.formatter&&(e.push("tg-tree-header"),this.rowsInfo.isTree&&e.push("tg-tree-header-indent")),this.isColumnSortable(t)&&e.push(`tg-column-sortable tg-column-sort-${this.options.sortIndicator}`),t.align&&e.push(`tg-align-${t.align}`),e.join(" ")},getHeaderStyle:function(t){const e=[d.styleMap(t.headerStyleMap)],i=t.tg_width;return this.isInvisible(t)||i<=0?e.push("display:none;"):e.push(`width:${i}px;`),e.join("")}},J={renderHeader:function(){this.cssRulesInvalid=!0,this.$headerL.empty(),this.$headerR.empty(),this.resetCssDisplay(),this.renderHeaderTables(),this.renderHeaderSort(),this.headerCreated=!0,this.trigger(y.onHeaderUpdated,{node:this.$headerFrame.get(0)})},initHeaderLayerHeight:function(){this.updateScrollPaneHiddenState(),this.resetCssDisplay(),this.viewAllColumns.forEach((t=>{this.updateColumnHeaderHeight(t)})),this.resetCssDisplay("none"),this.updateHeaderLayerHeight()},updateHeaderLayerHeight:function(){const t={},e=this.columnsInfo.maxLevel;for(let i=0;i<=e;i++)t[i]=0;const i=[];this.viewAllColumns.forEach((function(e){if(e.tg_combination)i.push(e);else{const i=e.tg_height,o=e.tg_layer;t[o]=Math.max(t[o],i)}})),i.forEach((function(e){let i=e.tg_height;const o=e.tg_combination.split(""),n=o.pop();o.forEach((function(e){i-=t[e]||0})),t[n]=Math.max(t[n],i)}));const o=JSON.stringify(t);this.previousHeaderLayerHeight!==o&&(this.previousHeaderLayerHeight=o,this.headerLayerHeight=t,this.cssRulesInvalid=!0)}};var Q=i(915);const Z={create:function(t){this.id=d.uid(4,"tg-"),d.isObject(t)||(t={container:t}),this.constructorOptions=t,this.createCache(),this.createView(t.container)},createView:function(t){this.createHolder(t),this.$holder?(this.createGlobalStyle(),this.createContainer()):console.error("ERROR: Grid requires a container")},createHolder:function(t){const e=w(t);if(!e.length)return;this.$holder=e,this.$holder.empty(),this.holder=this.$holder.get(0);const i=this.holder.getRootNode();this.shadowRoot=null,i&&i.host&&(this.shadowRoot=i)},createGlobalStyle:function(){const t=this.shadowRoot||document.head;if(t.querySelector(`style[context="${e.ID}"]`))return;const i=document.createElement("style");i.setAttribute("context",e.ID),i.innerHTML=Q.A.toString(),t.appendChild(i)},createContainer:function(){return this.$container=w('
    \r\n\r\n
    \r\n\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n\r\n
    \r\n\r\n
    \r\n\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n\r\n
    \r\n\r\n
    \r\n\r\n
    \r\n
    \r\n
    \r\n
    \r\n\r\n
    \r\n\r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n\r\n
    \r\n').appendTo(this.$holder),this.$container.attr("id",this.id),this.$container.addClass(`${e.NS} ${this.id}`),this.container=this.$container.get(0),d.setInstance(this.container,this),this.$headerFrame=this.$container.find(".tg-header-frame"),this.$paneHL=this.$headerFrame.find(".tg-pane-header-left"),this.$paneHR=this.$headerFrame.find(".tg-pane-header-right"),this.$headerL=this.$paneHL.find(".tg-header-left"),this.$headerR=this.$paneHR.find(".tg-header-right"),this.$header=w().add(this.$headerL).add(this.$headerR),this.$bodyFrame=this.$container.find(".tg-body-frame"),this.$paneTL=this.$bodyFrame.find(".tg-pane-top-left"),this.$paneTR=this.$bodyFrame.find(".tg-pane-top-right"),this.$paneBL=this.$bodyFrame.find(".tg-pane-bottom-left"),this.$paneBR=this.$bodyFrame.find(".tg-pane-bottom-right"),this.$bodyTL=this.$paneTL.find(".tg-body-top-left"),this.$bodyTR=this.$paneTR.find(".tg-body-top-right"),this.$bodyBL=this.$paneBL.find(".tg-body-bottom-left"),this.$bodyBR=this.$paneBR.find(".tg-body-bottom-right"),this.$body=w().add(this.$bodyTL).add(this.$bodyTR).add(this.$bodyBL).add(this.$bodyBR),this.$columnLineContainer=this.$container.find(".tg-column-line"),this.$columnLineItem=this.$columnLineContainer.find(".tg-column-line-item"),this.$columnLineItemL=this.$columnLineContainer.find(".tg-column-line-l"),this.$columnLineItemR=this.$columnLineContainer.find(".tg-column-line-r"),this}},tt={initColumnsHandler:function(){this.columns=this.data.columns,this.columns.forEach(((t,e)=>{t&&"object"==typeof t||(this.columns[e]={})}));const t=this.getPrivateColumns();this.columnsInfo=this.initTreeInfo(t,this.frozenInfo.column);const e=[],i=[],o=(t,n)=>{if(!d.isList(t))return;let s,r=0;t.forEach((t=>{if(!this.isInvisible(t))if(t.tg_group){if(this.isEmptyGroup(t))return;i.push(t),o(t.subs,t)}else t.tg_list_index=r,r+=1,t.tg_list_last=!1,s=t,e.push(t)})),s&&(s.tg_list_last=!0)};o(t),e.forEach((t=>{this.initColumnItemHandler(t)})),i.forEach((t=>{this.initColumnGroupHandler(t)}));const n=[].concat(e).concat(i);this.initViewList(n,((t,e)=>{})),this.viewColumns=e,this.viewGroupColumns=i,this.viewAllColumns=n,this.initHeaderHandler(t),this.initSortColumn()},getPrivateColumns:function(){const t=this.options;this.selectColumn=t.selectColumn,this.rowDragColumn=t.rowDragColumn,this.rowNumberColumn=t.rowNumberColumn,this.blankColumn=t.blankColumn;let e=[];const i=()=>{t.selectVisible&&e.push(this.selectColumn),t.rowDragVisible&&e.push(this.rowDragColumn),t.rowNumberVisible&&(this.rowNumberColumn.width=t.rowNumberWidth,e.push(this.rowNumberColumn))};if(this.frozenInfo.right){const t=this.frozenInfo.column;this.columns.forEach(((o,n)=>{e.push(o),n===t&&i()}))}else i(),e=e.concat(this.columns);return e.push(this.blankColumn),e},setColumns:function(t){this.data.columns=d.toList(t),this.rerender()},getColumns:function(){return this.columns},getViewColumns:function(t){return t?this.viewAllColumns:this.viewColumns},initColumnItemHandler:function(t){this.initColumnProps(t),this.initColumnFormatter(t),this.initColumnWidth(t)},initColumnGroupHandler:function(t){this.initColumnFormatterByName(t,"headerFormatter","header")},initColumnProps:function(t){const e=this.options.columnTypes;if(!d.hasOwn(t,"type")){const i=e[t.id];"string"==typeof i&&(t.type=i)}let i=this.options.columnProps;const o=e[t.type];o&&"object"==typeof o&&(i=d.merge(i,o));for(const e in i)d.hasOwn(t,e)||(t[e]=i[e])},initColumnFormatter:function(t){this.initColumnFormatterByName(t,"headerFormatter","header");let e=t.type;const i=t.formatter;"string"==typeof i&&(e=i),this.initColumnFormatterByName(t,"formatter",e)},initColumnFormatterByName:function(t,e,i){let o=t[e];"function"!=typeof o?(o=this.getFormatter(i),t[`tg_${e}`]=o||this.getFormatter("string")):t[`tg_${e}`]=o.bind(this)},initColumnWidth:function(t){if(t!==this.blankColumn)return d.isNum(t.width)&&t.width>=0?(t.tg_width=t.width,t.minWidth=Math.min(t.minWidth,t.tg_width),void(t.maxWidth=Math.max(t.maxWidth,t.tg_width))):void this.initColumnWidthByName(t);t.tg_width=0},initColumnWidthByName:function(t){const e=this.getComputedColumnWidth(t);d.isNum(e)&&(t.tg_width=e)},getComputedColumnWidth:function(t){const e=t.name||"",i=d.getCharLen(e);let o=Math.round(10*i);return o>103&&(o=Math.max(103,Math.round(10*i/2)),o>133&&(o=Math.max(133,Math.round(10*i/3)),o>163&&(o=Math.max(163,Math.round(10*i/4))))),d.clamp(o,t.minWidth,t.maxWidth)},initSortColumn:function(){this.sortColumn=null;const t=this.options,e=t.sortField;if(!e)return;const i=this.getColumnItemById(e);return i&&this.isColumnSortable(i)?(d.hasOwn(i,"sortAsc")||(i.sortAsc=t.sortAsc),this.sortColumn=i,this):void 0}},et={initHeaderHandler:function(t){this.initHeaderRowItem(),this.viewGroupColumns.reverse(),this.initGroupColumnsWidth(),this.initGroupColumnsLayer(t)},initHeaderRowItem:function(){this.headerRowItem={tg_index:-1,tg_view_index:-1},this.viewAllColumns.forEach((t=>{d.hasOwn(t,"id")&&(this.headerRowItem[t.id]=t.name)}))},initGroupColumnsWidth:function(){this.viewGroupColumns.forEach((t=>{let e=0;t.subs.forEach((t=>{this.isInvisible(t)||(e+=t.tg_width)})),t.tg_width=e}))},initGroupColumnsLayer:function(t){const e=this.columnsInfo.maxLevel;this.viewColumns.forEach((function(t){t.tg_layer=e,t.tg_parent&&(t.tg_parent.tg_layer=e-1)})),this.viewGroupColumns.forEach((function(t){const e=t.tg_layer,i=t.tg_parent;if(i){let t=e-1;d.isNum(i.tg_layer)&&(t=Math.min(t,i.tg_layer)),i.tg_layer=t}})),this.initColumnRowspanHandler(t,0)},initColumnRowspanHandler:function(t,e){t.forEach((t=>{const i=this.initColumnCombinationHandler(t,e);t.tg_group&&this.initColumnRowspanHandler(t.subs,e+i)}))},initColumnCombinationHandler:function(t,e){const i=[],o=t.tg_layer;for(;e<=o;)i.push(e),e+=1;i.reverse();const n=i.length;let s="";return n>1&&(s=i.join("")),t.tg_combination=s,n}},it={},ot={name:"",minWidth:81,maxWidth:300},nt=function(t){return null==t},st=function(t,e){const i=nt(t),o=nt(e);return i&&o?0:i?1:o?-1:void 0},rt=function(t,e){return t.tg_index>e.tg_index?1:-1},lt=function(t,e){return rt(t,e)},ht=function(t,e){if("string"==typeof t&&"string"==typeof e){const i=t.toUpperCase(),o=e.toUpperCase();if(i!==o)return i>o?-1:1}return t>e?-1:1},at=function(t,e,i,o){return t?-1:e?1:ht(i,o)},ct=function(t,e){const i="number"==typeof t,o="number"==typeof e;return i&&o?t>e?-1:1:at(i,o,t,e)},dt=function(t,e){const i=new Date(t),o=new Date(e),n=d.isDate(i),s=d.isDate(o);if(n&&s){const t=i.getTime(),e=o.getTime();if(t===e)return;return t>e?-1:1}return at(n,s,t,e)},ut=function(t,e){const i="boolean"==typeof t,o="boolean"==typeof e;return i&&o?t>e?-1:1:at(i,o,t,e)},gt=function(t,e,i,o){const n=t[i.sortField],s=e[i.sortField],r=st(n,s);if("number"==typeof r)return 0===r?lt(t,e):i.sortBlankFactor*r;if(n!==s&&"function"==typeof o){const t=o(n,s);if(d.isNum(t))return i.sortFactor*t}return lt(t,e)},ft={blankValue:st,equal:lt,index:rt,value:gt,diffType:at,string:function(t,e,i){return gt(t,e,i,ht)},stringValue:ht,number:function(t,e,i){return gt(t,e,i,ct)},numberValue:ct,date:function(t,e,i){return gt(t,e,i,dt)},dateValue:dt,boolean:function(t,e,i){return gt(t,e,i,ut)},booleanValue:ut};const pt={initOptionsHandler:function(){return this.options=this.generateOptions(),this.initOptionsFormatters(),this.initOptionsSort(),this.initOptionsFrozen(),this.initOptionsScrollbar(),this.initOptionsContainer(),this.initBindWindowResize(),this.initBindContainerResize(),this},generateOptions(){const t={className:e.NS,theme:e.ID,headerVisible:!0,rowHeight:32,rowFilter:null,rowFilteredSort:null,rowNotFound:"",rowMoveCrossLevel:!0,rowCacheLength:0,rowProps:it,columnTypes:{tree:{type:"tree",formatter:"tree",width:230,minWidth:120,maxWidth:810},number:{type:"number",align:"right"},date:{type:"date",align:"right"},name:"tree"},columnCacheLength:0,columnProps:ot,collapseAllOnInit:null,collapseAllVisible:!0,selectAllOnInit:null,selectVisible:!1,selectAllVisible:!0,selectMultiple:!0,selectColumn:{private:!0,id:"tg-column-select",name:"",formatter:"select",headerClassMap:"tg-header-select",classMap:"tg-cell-select",width:36,align:"center",resizable:!1,sortable:!1,exportable:!1},rowDragCrossLevel:!0,rowDragVisible:!1,rowDragColumn:{private:!0,id:"tg-column-row-drag",name:"",formatter:"rowDrag",headerClassMap:"tg-header-row-drag",classMap:"tg-cell-row-drag",align:"center",width:36,resizable:!1,sortable:!1,exportable:!1},rowNumberWidth:36,rowNumberFilter:null,rowNumberVisible:!1,rowNumberColumn:{private:!0,id:"tg-column-row-number",name:"",formatter:"rowNumber",headerClassMap:"tg-header-row-number",classMap:"tg-cell-row-number",align:"center",maxWidth:100,sortable:!1,exportable:!1},blankColumn:{private:!0,id:"tg-column-blank",name:"",formatter:"blank",headerClassMap:"tg-header-blank",classMap:"tg-cell-blank",width:0,minWidth:0,maxWidth:4096,resizable:!1,sortable:!1,exportable:!1},sortField:"",sortAsc:!0,sortBlankValueBottom:!0,sortComparers:ft,sortOnInit:!1,sortIndicator:"h",highlightKeywords:{textKey:"tg_text_",textGenerator:null,highlightKey:"tg_highlight_",highlightPre:"",highlightPost:""},frozenRow:-1,frozenRowMax:10,frozenRowHoverable:!1,frozenBottom:!1,frozenColumn:-1,frozenColumnMax:10,frozenRight:!1,scrollbarSize:12,scrollbarSizeH:null,scrollbarSizeV:null,scrollbarRound:!1,scrollbarFade:!1,scrollbarFadeTimeout:1e3,scrollbarType:"auto",scrollPaneMinWidth:30,scrollPaneGradient:30,autoHeight:!1,textSelectable:!1,bindWindowResize:!1,bindContainerResize:!1,cellResizeObserver:null},i=this.generateThemeOptions();return d.merge(t,i,this.constructorOptions,this.customOptions,this.dataOptions)},generateThemeOptions(){const t=this.pickOptions("theme").pop();if(t)return this.getThemeOptions(t)},pickOptions(t){return[this.constructorOptions,this.customOptions,this.dataOptions].map((e=>e&&e[t])).filter((t=>t))},initOptionsFormatters(){let t;const e=this.pickOptions("formatters");e.length&&(t=d.merge.apply(null,e)),this.formatters=d.merge(Y,t,this.customFormatters),this.nullFormatter=this.getFormatter("null")},initOptionsSort(){"v"!==this.options.sortIndicator&&(this.options.sortIndicator="h")},initOptionsFrozen:function(){const t=this.options;this.frozenInfo={column:-1,row:-1,columns:0,rows:0,bottom:Boolean(t.frozenBottom),right:Boolean(t.frozenRight)};let e=d.toNum(t.frozenColumn,!0);e=d.clamp(e,-1,t.frozenColumnMax),e>-1&&!this.frozenInfo.right&&(t.selectVisible&&(e+=1),t.rowDragVisible&&(e+=1),t.rowNumberVisible&&(e+=1)),this.frozenInfo.column=e,e>-1?this.frozenInfo.columns=e+1:(this.frozenInfo.columns=0,this.frozenInfo.right=!1);let i=d.toNum(t.frozenRow,!0);i=d.clamp(i,-1,t.frozenRowMax),this.frozenInfo.row=i,i>-1?this.frozenInfo.rows=i+1:(this.frozenInfo.rows=0,this.frozenInfo.bottom=!1)},initOptionsScrollbar:function(){const t=this.options;("auto"===t.scrollbarType&&d.isTouchDevice()||["touch","mobile"].includes(t.scrollbarType))&&(t.scrollbarFade=!0,t.scrollbarSize=6,t.scrollbarRound=!0);const e=d.toNum(t.scrollbarSize);this.scrollbarSizeH=e,d.isNum(t.scrollbarSizeH)&&(this.scrollbarSizeH=t.scrollbarSizeH),this.scrollbarSizeV=e,d.isNum(t.scrollbarSizeV)&&(this.scrollbarSizeV=t.scrollbarSizeV)},initOptionsContainer:function(){this.$container.attr("id",this.id);const t=this.options;this.$container.removeClass();const i=[e.NS,this.id,`tg-${t.theme}`,t.className];t.textSelectable||i.push("tg-text-unselectable"),d.isTouchDevice()&&i.push("tg-touch-device"),this.$container.addClass(d.classMap(i))},setTextSelectable:function(t){this.options.textSelectable&&(t?this.$container.removeClass("tg-text-unselectable"):this.$container.addClass("tg-text-unselectable"))}},mt={initBindWindowResize:function(){this.unbindWindowResize(),this.options.bindWindowResize&&(this.windowResizeEvents={resize:{handler:t=>{this.resize()}}},d.bindEvents(this.windowResizeEvents,window))},unbindWindowResize:function(){d.unbindEvents(this.windowResizeEvents)},createResizeObserver:function(t){if("undefined"==typeof ResizeObserver)return console.error("ERROR: This browser does not support ResizeObserver"),{observe:()=>{},unobserve:()=>{},disconnect:()=>{}};return new ResizeObserver((e=>{t.call(this,e)}))},initBindContainerResize:function(){this.unbindContainerResize(),this.options.bindContainerResize&&this.holder&&(this.resizeObserver=this.createResizeObserver((t=>{Boolean(this.holder.offsetWidth||this.holder.offsetHeight||this.holder.getClientRects().length)&&this.resize()})),this.resizeObserver.observe(this.holder))},unbindContainerResize:function(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null)}},bt={initRowsHandler:function(){this.rows=this.data.rows,this.rowsInfo=this.initTreeInfo(this.rows,this.frozenInfo.row)},getRows:function(){return this.rows},getViewRows:function(){return this.viewRows},createViewRows:function(){this.initRowFilterHandler();const t=[],e=this.getRowNumberFilter();let i=1;const o=(t,o)=>{if(e.call(this,t,o))return t.tg_row_number=i,void(i+=1);t.tg_row_number=""},n=(e,i,s)=>{if(!d.isList(e))return;let r,l=0;e.forEach((e=>{if(this.isInvisible(e))return;e.tg_list_index=l,l+=1,e.tg_list_last=!1,r=e,this.gridRowItemHandler(e),o(e,l),s||t.push(e);const i=s||e.tg_group&&e.collapsed;n(e.subs,e,i)})),r&&(r.tg_list_last=!0)};n(this.rows);let s,r=0;return this.initViewList(t,((t,e)=>{t.tg_top=r,r+=this.getRowHeight(t),t.tg_group_line=!1,t.collapsed&&(t.tg_group_line=!0),s&&(t.tg_group||t.tg_level{if(e.tg_invisible)return;const n=!t.call(this,e,i,o);if(e.tg_filtered=n,!n){let t=e;for(;t.tg_parent;)t.tg_parent.tg_filtered=!1,t=t.tg_parent}})),this.sortColumn)return;let e=this.options.rowFilteredSort;if("function"==typeof e&&(e=e.call(this)),!e)return;"string"==typeof e&&(e={sortField:e,sortAsc:this.options.sortAsc});const i=e.sortField||e.id;i&&this.sortRows(i,e)},highlightKeywordsFilter:function(t,e,i){const{textKey:o,textGenerator:n,highlightKey:s}=this.options.highlightKeywords;if(e.forEach((e=>{t[`${s}${e}`]=null})),!i)return!0;const r=`${i}`.trim().toLowerCase().split(/\s+/g).filter((t=>t));if(!r.length)return!0;let l=!1;const h=(e,i)=>(/<\/?[a-z][\s\S]*>/i.test(e)&&(e=((e,i)=>{const n=`${o}${i}`,s=t[n];if(s)return s;const r=document.createElement("div");r.innerHTML=e;const l=r.innerText;return t[n]=l,l})(e,i)),(t=>{const e=t.toLowerCase();let i=0;for(const t of r){const o=e.indexOf(t,i);if(-1===o)return;i=o+t.length}return!0})(e));let a=function(t,e){return t[e]};return"function"==typeof n&&(a=n),e.forEach((e=>{const i=a(t,e);if(null==i)return;const o=`${i}`.trim();if(!o)return;const n=h(o,e);n&&(t[`${s}${e}`]=n,l=!0,this.highlightKeywords=r)})),l},highlightKeywordsHandler:function(){const{highlightCells:t}=this.renderSettings;if(!t.length)return;const e=this.highlightKeywords;e&&(this.asyncHighlightKeywords||(this.asyncHighlightKeywords=d.debounce(this.highlightKeywordsSync,10)),this.asyncHighlightKeywords.apply(this,[t,e]))},highlightKeywordsSync:function(t,e){t.forEach((t=>{const i=Array.from(t.querySelectorAll("svg")).concat(Array.from(t.querySelectorAll("textarea"))),o=document.createTreeWalker(t,NodeFilter.SHOW_TEXT,(t=>{if(i.length)for(const e of i)if(e.contains(t))return NodeFilter.FILTER_SKIP;return NodeFilter.FILTER_ACCEPT})),n=[];let s=o.nextNode();for(;s;)n.push(s),s=o.nextNode();n.length&&this.highlightTextNodes(n,e)}))},highlightTextNodes:function(t,e){const{highlightPre:i,highlightPost:o}=this.options.highlightKeywords;let n=0;const s=()=>(n>=e.length&&(n=0),e[n++]);let r=s();t.forEach((t=>{const e=t.textContent,n=e.toLowerCase(),l=[];let h=0;const a=e.length;let c=!1;for(;h{if(e.selected){if(t)return void(e.selected=!1);t=e}}))}const t=this.options.selectAllOnInit;!0!==t?!1===t&&this.updateAllRowsSelected(!1):this.updateAllRowsSelected(!0)},updateAllRowsSelected:function(t){this.forEachSelectableRow((e=>{e.selected=t}))},initCollapseAllOnInitHandler:function(){const t=this.options.collapseAllOnInit;!0!==t?!1===t&&this.updateAllRowsCollapsed(!1):this.updateAllRowsCollapsed(!0)},getToBeAddedItemList:function(t){const e=[];return d.toList(t).forEach((t=>{t&&"object"==typeof t?e.push(t):void 0!==t&&e.push({name:t})})),e},getToBeAddedParentSubs:function(t,e){return t?(t.subs||(t.subs=[]),t.subs):e},getToBeAddedPositionIndex:function(t,e){const i=e.length;return d.isNum(t)&&t>=0&&t<=i?Math.round(t):i},generateDataSnapshot:function(t){if(!t||"object"!=typeof t)return t;const e=this.cleanTreeList(t.rows),i=this.cleanTreeList(t.columns);return this.convertNumberType(e,i),t.rows=e,t.columns=i,t},cleanTreeList:function(t){if(!d.isList(t))return[];const e=(t,i)=>{i.forEach((i=>{if(!i||"object"!=typeof i)return void t.push({});const o=this.getItemSnapshot(i),n=i.subs;Array.isArray(n)&&(o.subs=[],e(o.subs,n)),t.push(o)}))},i=[];return e(i,t),i},convertNumberType:function(t,e){const i=[];d.forEachTree(e,(function(t){"number"===t.type&&t.id&&i.push(t.id)})),i.length&&d.forEachTree(t,(function(t){i.forEach((function(e){t[e]=d.convertNum(t[e])}))}))}},vt={setDefaultLoading:function(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!t)return;const i=t.style;e.size&&(i.width=e.size,i.height=e.size),e.color&&(i.color=e.color),e.size||e.color||t.removeAttribute("style"),e.fast?t.classList.add("tg-loading-fast"):t.classList.remove("tg-loading-fast")},getDefaultLoading:function(t){return this.setDefaultLoading(this.$defaultLoading,t),this.$defaultLoading},getLoadingHolder:function(){return this.$container?this.$container.find(".tg-loading"):w()},setLoading:function(t){if(!this.$container)return this;this.$defaultLoading||(this.$defaultLoading=this.$container.find(".tg-loading-default").get(0));const e=this.getLoadingHolder().get(0);return"function"==typeof t&&(t=t.call(this,e)),d.isObject(t)&&(t=this.getDefaultLoading(t)),t||(t=this.getDefaultLoading()),this.renderNodeContent(e,t),this},showLoading:function(){return this.getLoadingHolder().show(),this},hideLoading:function(){return this.getLoadingHolder().hide(),this}},Ht={showMask:function(t){if(!this.hasMask||t){const e=this.$container.find(".tg-mask"),i=e.get(0);if(t&&i){const e=d.styleMap(t);e&&(i.style.cssText=e)}e.show(),this.hasMask=!0}return this},hideMask:function(){return this.hasMask&&(this.$container.find(".tg-mask").hide(),this.hasMask=!1),this}},Ct={renderNodeContent:function(t,e){if(t){if(e&&e.nodeType)return this.emptyNode(t),void t.appendChild(e);if(Array.isArray(e))return this.emptyNode(t),void e.forEach((e=>{e&&e.nodeType&&t.appendChild(e)}));void 0===e&&(e=""),t.innerHTML=e}},emptyNode:function(t){if(t)for(;t.firstChild;)t.removeChild(t.firstChild)},removeNode:function(t){t&&t.parentNode&&t.parentNode.removeChild(t)},appendNode:function(t,e){t&&e&&t.appendChild(e)},createElement:function(t,e,i){const o=document.createElement(t);e&&Object.keys(e).forEach((function(t){const i=e[t];void 0!==i&&o.setAttribute(t,i)})),d.isArray(i)||(i=[i]);let n="";return i.forEach((function(t){t&&t.nodeType?o.appendChild(t):void 0!==t&&(n+=t)})),n&&(o.innerHTML=n),o},find:function(t,e){return w(e||this.$container).find(t)},getRowNodes:function(t){const e=this.getRowItem(t);if(e)return this.getRowNodesByIndex(e.tg_view_index)},getCellNode:function(t,e){const i=this.getRowItem(t);if(!i)return;const o=this.getColumnItem(e);return o?this.getCellNodeByIndex(i.tg_view_index,o.tg_view_index):void 0},getHeaderItemNode:function(t){const e=this.getColumnItem(t);if(e)return this.getHeaderCache(e.tg_view_index)},getColumnHeaderNode:function(t){const e=this.getHeaderItemNode(t);if(e)return e.querySelector(".tg-column-header")}},yt={render:function(){this.asyncRender||(this.asyncRender=d.microtask(this.renderSync)),this.asyncRender.apply(this,arguments)},renderSync:function(){this.renderStartedTimestamp=Date.now();const t=this.generateRenderSettings.apply(this,arguments);return this.renderSettings=t,"all"===t.type?(this.flushBody(),this.initHandler(),this.renderHeader(),this.updateViewRowsAndSize(),this.renderBody(),this):"columns"===t.type?(this.flushBody(),this.initColumnsHandler(),this.renderHeader(),this.updateViewRowsAndSize(),this.renderBody(),this):"rows"===t.type?(this.updateViewRowsAndSize(),this.renderBody(),this):"rows_cache"===t.type?(this.cssRulesInvalid=!0,this.updateViewRowsAndSize(),this.updateRowCacheTopAndHeight(),this.renderBody(),this):"resize"===t.type?(this.resizeHandler(),this.renderBody(),this):(this.renderBody(),this)},generateRenderSettings:function(t){const e={type:this.renderType,scrollLeft:null,scrollTop:null,scrollColumn:null,scrollRow:null,highlightCells:[]};return"string"==typeof t?e.type=t:t&&Object.assign(e,t),this.headerCreated||(e.type="all"),e},renderBody:function(){this.scrollOnInit(),this.scrollTopOffset=this.scrollPane.getScrollTopOffset();const t=this.getViewport();return this.viewport=t,this.flushWithViewport(),this.previousScrollTopOffset!==this.scrollTopOffset&&(this.previousScrollTopOffset=this.scrollTopOffset,this.updateRowCacheTopOffset()),this.renderRows(t.rows),this.renderCells(t.rows,t.columns),this.renderUpdatedTimestamp=Date.now(),this.renderDuration=this.renderUpdatedTimestamp-this.renderStartedTimestamp,this.trigger(y.onUpdated,t),this.firstUpdated||(this.firstUpdated=!0,this.trigger(y.onFirstUpdated,t)),this.layoutEventHandler(),this.resizeEventHandler(),this.highlightKeywordsHandler(),this.renderSettings=null,this.renderType=null,this},rerender:function(){return this.render("all"),this}},Rt={resize:function(){return this.asyncResize||(this.asyncResize=d.throttle(this.resizeSync,100)),this.asyncResize.apply(this,arguments),this},resizeSync:function(){return this.headerCreated?(this.resizeHolderHandler.apply(this,arguments),this.firstUpdated&&this.isHolderInvisible()||this.render("resize"),this):this},resizeHolderHandler(t,e){if(0!==arguments.length)return 1===arguments.length?t&&"object"==typeof t?void this.$holder.css(t):void this.$holder.css({width:t}):void this.$holder.css({width:t,height:e})},isHolderInvisible(){const t=this.$holder.width(),e=this.$holder.height();return!t||!e},resizeHandler:function(){this.containerWidth=this.$container.width(),this.containerHeight=this.$container.height(),this.headerWidth=this.containerWidth,this.bodyWidth=this.containerWidth,this.updateTotalColumnsWidth(),this.resizeHeaderHandler(),this.resizeBodyHandler()},layoutEventHandler:function(){const t=this.previousLayout||{},e={headerWidth:this.headerWidth,headerHeight:this.headerHeight,bodyWidth:this.bodyWidth,bodyHeight:this.bodyHeight,scrollbarWidth:this.getScrollbarWidth(),scrollbarHeight:this.getScrollbarHeight()};Object.values(e).join("")!==Object.values(t).join("")&&(this.previousLayout=e,this.trigger(y.onLayout,d.merge({previous:t},e)))},resizeEventHandler:function(){const t=this.previousSize||{},e={width:this.containerWidth,height:this.containerHeight};Object.values(e).join("")!==Object.values(t).join("")&&(this.previousSize=e,this.trigger(y.onResize,d.merge({previous:t},e)))},resizeHeaderHandler:function(){this.initHeaderLayerHeight();const t=this.options;t.autoHeight&&this.viewRows.length>5e3&&(t.autoHeight=!1),this.headerHeight=0,t.headerVisible&&(this.containerHeight>0||t.autoHeight)&&this.updateHeaderTableHeight(),this.$headerFrame.css({width:this.headerWidth,height:this.headerHeight})},updateHeaderTableHeight:function(){let t=0;Object.keys(this.headerLayerHeight).forEach((e=>{t+=this.headerLayerHeight[e]}));const e=this.$headerL.find(".tg-header-table"),i=this.$headerR.find(".tg-header-table");e.css({height:t}),i.css({height:t}),this.headerHeight=t},resizeBodyHandler:function(){this.updateScrollState(),this.bodyHeight=this.containerHeight-this.headerHeight,this.$bodyFrame.css({width:this.bodyWidth,height:this.bodyHeight}),this.updatePaneWidth(),this.updatePaneHeight(),this.updateCanvasWidth(),this.updateCanvasHeight(),this.updateScrollPane(),this.updateCssRules()},updatePaneWidth:function(){let t=this.bodyWidth,e=0;if(this.frozenInfo.columns){const i=this.getScrollbarWidth();this.frozenInfo.right?(e=this.columnsWidthR+i,t=this.bodyWidth-e):(t=this.columnsWidthL,e=this.bodyWidth-t),this.scrollPaneHidden&&(this.frozenInfo.right?(t<=0&&(t=0),e=Math.max(0,this.bodyWidth-t)):(e3&&void 0!==arguments[3])||arguments[3];const n=this.getToBeAddedItemList(t);if(!n.length)return!1;let s;if(null!=e&&(s=this.getRowItem(e),!s))return!1;const r=this.getToBeAddedParentSubs(s,this.rows),l=this.getToBeAddedPositionIndex(i,r),h=[l,0].concat(n);r.splice.apply(r,h),this.initRowsHandler(),s?(s.collapsed=!1,this.flushRowFrom(s.tg_view_index+l)):this.flushRowFrom(l),this.onNextUpdated((function(){this.trigger(y.onRowAdded,n)}));const a={type:"rows"};return o&&(a.scrollRow=n[n.length-1]),this.render(a),!0},deleteRow:function(t){const e=d.toList(t),i=[];if(e.forEach((t=>{const e=this.getRowItem(t);e&&i.push(e)})),!i.length)return!1;const o=this.removeRowsHandler(i);this.initRowsHandler();const n=this.getRemovedMinIndex(o);return this.flushRowFrom(n),this.onNextUpdated((function(){this.trigger(y.onRowRemoved,i)})),this.render("rows"),!0},getRemovedMinIndex:function(t){let e=0;const i=t[t.length-1];if(this.isInvisible(i))return e;e=i.tg_view_index,e>0&&(e-=1);let o=i.tg_parent;for(;o;)o.collapsed&&(e=o.tg_view_index),o=o.tg_parent;return e},removeRowsHandler:function(t){const e=[].concat(t);e.sort((function(t,e){return e.tg_index-t.tg_index}));const i=[];return e.forEach((t=>{this.getRowParentSubs(t).splice(t.tg_sub_index,1),i.push(t)})),i}},Tt={renderCollapseAllState:function(){this.hasTreeColumn&&(this.asyncRenderCollapseAllState||(this.asyncRenderCollapseAllState=d.microtask(this.renderCollapseAllStateSync)),this.asyncRenderCollapseAllState.apply(this,arguments))},renderCollapseAllStateSync:function(){const t=this.$header.find(".tg-tree-header");this.rowsInfo.isTree?t.addClass("tg-tree-header-indent"):t.removeClass("tg-tree-header-indent"),this.renderCollapseAllIcon()},checkCollapseAllState:function(t){if(t!==this.allRowsCollapsed){if(t){let t=0;const e=this.rows.length;for(;t{if(e.tg_group&&e.tg_subs_length&&e.collapsed)return t=!0,!1})),t)return}this.allRowsCollapsed=t,this.renderCollapseAllIcon()}},expandAllRows:function(){return this.renderAllRowsCollapsed(!1)},collapseAllRows:function(){return this.renderAllRowsCollapsed(!0)},toggleAllRows:function(){return this.allRowsCollapsed?this.expandAllRows():this.collapseAllRows()},renderAllRowsCollapsed:function(t){const e=this.updateAllRowsCollapsed(t);return e.length?(this.flushBody(),this.onNextUpdated((()=>{this.renderCollapseAllIcon(),t?this.trigger(y.onRowCollapsed,e):this.trigger(y.onRowExpanded,e)})),this.render("rows"),this):this},updateAllRowsCollapsed:function(t){this.allRowsCollapsed=t;const e=[];return this.forEachRow((i=>{i.subs&&i.tg_subs_length&&this.isCollapsedChanged(i,t)&&(i.collapsed=t,e.push(i))})),e},expandRow:function(t){const e=this.getRowItem(t);return e?this.isEmptyGroup(e)?(this.trigger(y.onRowSubsRequest,e),this):this.isCollapsedChanged(e,!1)?(e.collapsed=!1,this.flushRowFrom(e.tg_view_index),this.renderCollapseIcon(e),this.onNextUpdated((()=>{this.checkCollapseAllState(!1),this.trigger(y.onRowExpanded,e)})),this.render("rows"),this):this:this},collapseRow:function(t){const e=this.getRowItem(t);return e&&e.subs&&e.tg_subs_length&&this.isCollapsedChanged(e,!0)?(e.collapsed=!0,this.flushRowFrom(e.tg_view_index),this.renderCollapseIcon(e),this.onNextUpdated((()=>{this.checkCollapseAllState(!0),this.trigger(y.onRowCollapsed,e)})),this.render("rows"),this):this},toggleRow:function(t){const e=this.getRowItem(t);return e?(e.collapsed?this.expandRow(e):this.collapseRow(e),this):this},expandRowLevel:function(t){t=d.toNum(t,!0);const e=[],i=[];return this.forEachRow((o=>{o.subs&&o.tg_subs_length&&(o.tg_level<=t?this.isCollapsedChanged(o,!1)&&(o.collapsed=!1,i.push(o)):this.isCollapsedChanged(o,!0)&&(o.collapsed=!0,e.push(o)))})),e.length||i.length?(this.flushBody(),this.onNextUpdated((()=>{e.length&&this.trigger(y.onRowCollapsed,e),i.length&&this.trigger(y.onRowExpanded,i)})),this.render("rows"),this):this},renderCollapseAllIcon:function(){if(!this.options.collapseAllVisible||!this.hasTreeColumn)return;const t=this.$header.find(".tg-tree-icon-all");this.renderTreeIcon(t,this.allRowsCollapsed)},renderCollapseIcon:function(t){if(!this.headerCreated)return;const e=this.getRowNodesByIndex(t.tg_view_index);if(!e)return;const i=e.find(".tg-tree-icon");this.renderTreeIcon(i,t.collapsed)},renderTreeIcon:function(t,e){t&&(e?t.removeClass("tg-tree-icon-expanded").addClass("tg-tree-icon-collapsed"):t.removeClass("tg-tree-icon-collapsed").addClass("tg-tree-icon-expanded"))}},Et={rowDragStartHandler:function(t,e){this.removeSortColumn();const i=e.rowItem;if(!i)return;const o=this.getRowNodesByIndex(i.tg_view_index);if(!o)return;e.dragCloneNodes=this.getRowDragCloneNodes(o),e.dropPlaceholder=this.getRowDropPlaceholder(o),e.dragStartTop=this.getRowTop(i),e.dragRowHeight=this.getRowHeight(i),e.dragStartScrollTop=this.scrollTop,e.dragMaxScrollTop=this.scrollPane.getMaxScrollTop();const n={e:t,rowItem:i};this.trigger(y.onRowDragged,n),this.isDefaultPrevented(n)||("touch"===e.type&&d.preventDefault(e.e),this.setRowState(i,"dragging"),this.setTextSelectable(!1),this.rowDropListHandler(e),this.updateDragCloneRowPosition(e))},rowDragMoveHandler:function(t,e){"touch"===e.type&&d.preventDefault(e.e),this.updateDragCloneRowPosition(e),this.updateDragPlaceholderPosition(e),this.rowDragAutoScrollHandler(e)},rowDragEndHandler:function(t,e){"touch"===e.type&&(this.protectedItem=null,d.preventDefault(e.e)),this.autoScrollStop(),this.setRowState(e.rowItem,"dragging",!1),this.setTextSelectable(!0),e.dragCloneNodes&&(e.dragCloneNodes.remove(),e.dragCloneNodes=null),e.dropPlaceholder&&(e.dropPlaceholder.remove(),e.dropPlaceholder=null),e.changed&&this.rowDropHandler(e)},updateDragCloneRowPosition:function(t){const e=this.scrollTop-t.dragStartScrollTop,i=t.dragStartTop+t.offsetY+e,o=i-this.scrollTopOffset;t.dragCloneNodes&&t.dragCloneNodes.css("top",o).show(),t.dragCurrentPosition=i+.5*t.dragRowHeight},getRowDragCloneNodes:function(t){const e=w();return t.each((function(t){const i=w(t),o=i.clone();o.appendTo(i.parent()),e.add(o)})),e.addClass("tg-clone").hide(),e},getRowDropPlaceholder:function(t){const e=w();return t.each((function(t){const i=w(t),o=w("
    ").addClass("tg-row-placeholder").hide(),n=i.parent();n.find(".tg-row-placeholder").remove(),o.appendTo(n),e.add(o)})),e},updateDragPlaceholderPosition:function(t){this.rowDropItemHandler(t);const e=t.dropItem;if(!e)return;let i=t.dropPosition-1;t.dropBottom?e.tg_view_last&&(i=t.dropPosition-2):e.tg_view_index-this.frozenInfo.rows==0&&(i=t.dropPosition);const o=i-this.scrollTopOffset;t.dropPlaceholder&&t.dropPlaceholder.css("top",o).show()},rowDragAutoScrollHandler:function(t){const e=t.dragCurrentPosition,i=this.scrollTop,o=this.bodyHeight-this.frozenRowsHeight,n=i+o,s=this.options.rowHeight,r=Math.min(3*s,.5*o);if(!(rn-r){const i=e-(n-r),o=this.getAutoScrollOffset(i,r);this.autoScrollStart(o,t)}else this.autoScrollStop()},getAutoScrollOffset:function(t,e){return Math.floor(t/e*20)},autoScrollStop:function(){this.autoScrollMotion&&(this.autoScrollMotion.destroy(),this.autoScrollMotion=null)},autoScrollStart:function(t,e){this.autoScrollStop();const i=e.dragMaxScrollTop;this.autoScrollMotion=new B,this.autoScrollMotion.bind(B.EVENT.MOTION_MOVE,(()=>{const o=d.clamp(this.scrollTop+t,0,i);o!==this.scrollTop?(this.setScrollTop(o),this.updateDragCloneRowPosition(e),this.updateDragPlaceholderPosition(e)):this.autoScrollStop()})),this.autoScrollMotion.once(B.EVENT.MOTION_END,(()=>{this.autoScrollStart(t,e)})),this.autoScrollMotion.start({duration:200})},rowDropListHandler:function(t){const e=this.getRowDropList(t);if(!d.isList(e))return;const i=t.rowItem,o=e.filter((t=>{if(t===i)return!1;if(t.tg_frozen)return!1;let e=t.tg_parent;for(;e;){if(e===i)return!1;e=e.tg_parent}return!0}));if(!d.isList(o))return;const n=[];o.forEach((t=>{const e=this.getRowTop(t),i=this.getRowHeight(t);n.push({rowItem:t,position:e}),n.push({rowItem:t,position:e+i-1,dropBottom:!0})})),t.dropList=n},getRowDropList:function(t){const e=this.options.rowDragCrossLevel;return e?"function"==typeof e?e.call(this,t):this.viewRows:this.getRowParentSubs(t.rowItem)},rowDropItemHandler:function(t){const e=t.dropList;if(!e)return;const i=t.dragCurrentPosition;let o=Number.MAX_VALUE;for(let n=0,s=e.length;no)break;o=r,t.dropItem=s.rowItem,t.dropBottom=s.dropBottom,t.dropPosition=s.position}},rowDragDropPositionHandler:function(t,e,i){const o=this.getRowParentSubs(t),n=t.tg_sub_index;let s,r;return this.isDropIntoGroupFirstChild(e,i)?(s=e.subs,r=0):(s=this.getRowParentSubs(e),r=e.tg_sub_index,o===s&&n{this.trigger(y.onRowDropped,n)})),this.render({type:"rows",scrollRow:i})}},It={getMoveFocusRow:function(t,e){let i=t[0];return e>0&&(i=t[t.length-1]),i},getMoveLengthInList:function(t,e){let i=0;return t.forEach((t=>{this.getRowParentSubs(t)===e&&(i+=1)})),i},getMoveInfo:function(t,e,i){const o=this.getRowParentSubs(i);let n=i.tg_sub_index+e;const s=i.tg_parent;if(s&&this.options.rowMoveCrossLevel){const e=0,i=s.tg_subs_length-1;if(ni){const e=n-i;return this.getMoveInfo(t,e,s)}}if(e>0){n-=this.getMoveLengthInList(t,o)-1}return n=d.clamp(n,0,o.length),{list:o,index:n}},moveRowsHandler:function(t,e){(t=this.removeRowsHandler(t)).reverse();const i=this.getMoveFocusRow(t,e),o=this.getMoveInfo(t,e,i),n=[o.index,0].concat(t);return o.list.splice.apply(o.list,n),this.initRowsHandler(),this.onNextUpdated((function(){this.scrollRowIntoView(i),this.trigger(y.onRowMoved,t)})),this.removeSortColumn(),this.update(),!0},moveRows:function(t,e){t=d.toList(t);const i=[];return t.forEach((t=>{const e=this.getRowItem(t);e&&i.push(e)})),!!i.length&&(!(i.length>=this.getRowsLength())&&(0!==(e=d.toNum(e,!0))&&this.moveRowsHandler(i,e)))},moveRowsUp:function(t){return this.moveRows(t,-1)},moveRowsDown:function(t){return this.moveRows(t,1)},moveRowsToTop:function(t){return this.moveRows(t,-this.getRowsLength(!0))},moveRowsToBottom:function(t){return this.moveRows(t,this.getRowsLength(!0))},moveSelectedRowsUp:function(){return this.moveRows(this.getSelectedRows(),-1)},moveSelectedRowsDown:function(){return this.moveRows(this.getSelectedRows(),1)},moveSelectedRowsToTop:function(){return this.moveRows(this.getSelectedRows(),-this.getRowsLength(!0))},moveSelectedRowsToBottom:function(){return this.moveRows(this.getSelectedRows(),this.getRowsLength(!0))}},Lt={getSelectedRow:function(){let t=null;return this.forEachSelectableRow((function(e){if(e.selected)return t=e,!1})),t},getSelectedRows:function(){const t=[];return this.forEachSelectableRow((function(e){e.selected&&t.push(e)})),t.length>1&&t.sort((function(t,e){const i=t.tg_selected_index,o=e.tg_selected_index;return i>o?1:i0&&void 0!==arguments[0])||arguments[0];if(t=Boolean(t),this.globalSelectedIndex=0,t&&!this.options.selectMultiple)return this;const e=this.getAllSelectedChangedList(t);return e.length?(this.updateRowsSelectedState(e),this):this},setRowSelected:function(){return(this.options.selectMultiple?this.setRowMultipleSelected:this.setRowSingleSelected).apply(this,arguments)},setRowSingleSelected:function(t){const e=this.getRowItem(t);if(!e)return this;if(!this.isRowSelectable(e))return this;if(e.selected)return this;const i=[],o=this.getSelectedRow();return o&&o.selected&&i.push(o),e.selected||i.push(e),i.length?(this.updateRowsSelectedState(i),this):this},setRowMultipleSelected:function(t,e){if(0===arguments.length)return this;if(1===arguments.length&&!1===arguments[0])return this.selectAll(!1);const i=this.toRowItemList(t,(t=>this.isRowSelectable(t)));return i.length?!1===e?(this.setRowListUnselected(i),this):d.hasShiftKey(e)&&1===i.length?(this.setRowBetweenListSelected(i[0]),this):(this.updateRowsSelectedState(i),this):this},setRowListUnselected:function(t){const e=this.getSelectedChangedList(t,!1);e.length&&this.updateRowsSelectedState(e)},setRowBetweenListSelected:function(t){const e=this.previousSelectedRow;if(e&&e!==t){const i=this.getBetweenSelectedChangedList(e,t);if(!i.length)return;this.updateRowsSelectedState(i,!0)}else this.updateRowsSelectedState([t])},getAllSelectedChangedList:function(t){const e=[];return this.forEachSelectableRow((i=>{this.isSelectedChanged(i,t)&&e.push(i)})),e},getSelectedChangedList:function(t,e){const i=[];return t.forEach((t=>{this.isSelectedChanged(t,e)&&i.push(t)})),i},getBetweenSelectedChangedList:function(t,e){const i=t.tg_index,o=e.tg_index,n=[];if(i=o;)n.push(t),t--}return this.toRowItemList(n,(t=>this.isRowSelectable(t)&&!t.selected))},updateRowsSelectedState:function(t,e){let i;t.forEach((t=>{const e=!t.selected;t.selected=e,e&&(t.tg_selected_index=this.globalSelectedIndex++,i=t),this.renderRowSelectedState(t)})),e||(this.previousSelectedRow=i),this.renderSelectAllState(),this.onNextUpdated((()=>{this.trigger(y.onSelectChanged,t)})),this.render()},renderRowSelectedState:function(t){const e=t.tg_view_index;this.viewport.rows.includes(e)&&(this.renderRowState(t,"selected"),this.flushCell(e,this.selectColumn.tg_view_index))},renderSelectAllState:function(){this.isSelectAllVisible()&&(this.asyncRenderSelectAllState||(this.asyncRenderSelectAllState=d.microtask(this.renderSelectAllStateSync)),this.asyncRenderSelectAllState.apply(this,arguments))},renderSelectAllStateSync:function(){const t=this.getSelectAllState();if(t===this.previousSelectAllState)return;this.previousSelectAllState=t;const e=this.selectColumn,i=w(this.getColumnHeaderNode(e)).find(".tg-select-icon-all");i.length&&(i.removeClass("tg-selected tg-mixed"),t&&i.addClass(`tg-${t}`))},getSelectAllState:function(){let t=0;this.forEachSelectableRow((e=>{t+=1}));const e=this.getSelectedRows().length;let i="mixed";return 0===e?(i="",this.previousSelectedRow=null):e===t&&(i="selected"),i},isSelectAllVisible:function(){const t=this.options;return!!(t.selectVisible&&t.selectAllVisible&&t.selectMultiple)}},xt={setRowHover:function(t,e){const i=this.getRowItem(t);return i?(this.renderRowHover(i,e),this):this},renderRowHover:function(t,e){if(this.previousHover&&(this.previousHover.removeClass("tg-hover"),this.previousHover=null),!e)return this;if(!1===this.rowHoverable)return;if(!1===t.hoverable)return this;if(t.tg_frozen&&!this.options.frozenRowHoverable)return this;const i=t.tg_view_index;return this.previousHover=this.$body.find(`.tg-row[row='${i}']`).addClass("tg-hover"),this},setRowState:function(t,e){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];const o=this.getRowItem(t);return o?(o.tg_state_names||(o.tg_state_names=new Set),o.tg_state_names.add(e),o[e]=i,this.renderRowState(o,e),this):this},renderRowState:function(t,e){const i=this.getRowNodesByIndex(t.tg_view_index);if(i){const o=`tg-${e}`;t[e]?i.addClass(o):i.removeClass(o)}}},zt={getRowItem:function(t){return d.isNum(t)?(t<0&&(t=this.rowsInfo.length+t),this.rowsInfo.indexCache[t]):t?d.isNum(t.tg_index)?t:this.getRowItemById(t.id||t):void 0},getRowItemById:function(t){return this.getRowItemBy("id",t)},getRowItemBy:function(t,e){if(void 0!==e)return this.rowsInfo.indexCache.find((i=>i[t]===e))},getRowsLength:function(t){return t?this.rowsInfo.length:this.viewRows.length},getViewRowItem:function(t){return this.viewRows[t]},getPreRenderRowInfo:function(t){const e={rows:[],rowNotFound:!1,benchmark:0};if(!t.length)return 0===this.getRowsLength()&&(e.rowNotFound=!0),e;const i=[],o=this.frozenInfo.row;return t.forEach((t=>{this.getRowCache(t)?t>o&&i.push(t):e.rows.push(t)})),i.length&&(e.benchmark=Math.min.apply(Math,i)),e.rows.sort((function(t,i){return t{this.renderRowNodes(t,e.benchmark)}))},createRowNode:function(t,e,i,o,n,s){const r=document.createElement("div");return r.setAttribute("row",t),r.className=i,o&&(r.style.cssText=o),r.style.top=`${n}px`,s!==this.options.rowHeight&&(r.style.height=`${s}px`,r.style.lineHeight=`${s}px`),this.setNodeDataCache(r,{row:t,rowItem:e,rowNode:r}),r},appendRowNode:function(t,e,i,o){ithis.frozenInfo.row))return e;e-=this.frozenRowsHeight}return e-=this.scrollTopOffset,e},getRowTop:function(t){let e=t.tg_top;return t.tg_frozen||(e-=this.frozenRowsHeight),e},getRowVPos:function(t){const e=this.frozenInfo.bottom,i=this.frozenInfo.row;let o="top";return this.frozenInfo.rows&&(t<=i?e&&(o="bottom"):e||(o="bottom")),o},getRowCanvas:function(t,e){return"top"===t?"left"===e?this.$bodyTL:this.$bodyTR:"left"===e?this.$bodyBL:this.$bodyBR}},Mt={CHANGE:"change",START:"start",END:"end"},Pt={h:{type:"h",className:"tg-scrollbar-h",offset:"left",size:"width",page:"pageX",axis:"x",offsetName:"offsetX"},v:{type:"v",className:"tg-scrollbar-v",offset:"top",size:"height",page:"pageY",axis:"y",offsetName:"offsetY"}};class Nt extends k{static EVENT=Mt;static H="h";static V="v";type="h";settings={};size=0;viewSize=0;bodySize=0;trackSize=0;position=0;scale=0;thumbPosition=0;thumbScale=0;constructor(t,e){super(),this.settings=Pt[t]||Pt.h,this.type=this.settings.type,this.id=d.uid(4,`tg-scrollbar-${this.type}-`),this.$holder=w(e),this.$holder.find(`.${this.settings.className}`).remove(),this.options=this.generateOptions()}generateOptions(t){return d.merge({size:15,round:!1,blank:!1,motionDuration:200},t)}updateOptions(t){this.options=this.generateOptions(t);let e=this.options.size;d.isNum(e)||(e=d.toNum(e)),e=Math.round(e),e=Math.max(e,0),e=Math.min(e,30),this.size=e}create(){this.$container=w('
    ').appendTo(this.$holder),this.$container.attr("id",this.id),this.$container.addClass(d.classMap(["tg-scrollbar",this.settings.className,{"tg-scrollbar-round":this.options.round}])),this.$track=this.$container.find(".tg-scrollbar-track"),this.$thumb=this.$container.find(".tg-scrollbar-thumb"),this.thumbDrag=new O,this.thumbDrag.bind(O.EVENT.DRAG_START,((t,e)=>{this.thumbDragStart(e)})).bind(O.EVENT.DRAG_MOVE,((t,e)=>{this.thumbDragMove(e)})).bind(O.EVENT.DRAG_END,((t,e)=>{this.thumbDragEnd(e)}));const t=this.$container.get(0);return this.scrollEvents={mousedown:{handler:e=>{e.target.classList.contains("tg-scrollbar-thumb")?this.thumbMouseDownHandler(e):(this.trackEvents={mouseup:{handler:t=>{this.trackMouseupHandler(t)},options:{once:!0}}},d.bindEvents(this.trackEvents,t),this.trackMousedownHandler(e))},options:!0},selectstart:{handler:t=>{d.preventDefault(t)},options:!0}},d.bindEvents(this.scrollEvents,t),this}getBlank(){return this.options.blank}getSize(){return this.size}getViewSize(){return this.viewSize}getBodySize(){return this.bodySize}getTrackMouseDirection(){let t=1;return this.trackMousePosition0){const i=this.getMaxThumbPosition();t=Math.round(i*this.position/e),t=d.clamp(t,0,i)}return this.setThumbPosition(t),this}trackMousedownHandler(t){return this.motionStop(),this.trackMousePosition=this.getTrackMousePos(t),this.motionStart(),this}trackMouseupHandler(t){return d.unbindEvents(this.trackEvents),this.motionStop(),this.motionStarted||(this.trackMousePosition=this.getTrackMousePos(t),this.trackScrollHandler(),this.triggerEvent()),this}trackScrollHandler(){const t=Math.max(0,this.viewSize-20)*this.getTrackMouseDirection();return this.setOffset(t),this}motionStop(){return this.motion&&(this.motion.destroy(),this.motion=null),this}motionStart(){const t=this.position,e=Math.round(this.trackMousePosition/this.viewSize*this.getMaxPosition());return this.motionStarted=!1,this.motion=new B,this.motion.bind(B.EVENT.MOTION_START,((t,e)=>{this.motionStarted=!0})),this.motion.bind(B.EVENT.MOTION_MOVE,((t,e)=>{this.motionUpdateHandler(t,e)})),this.motion.start({duration:this.options.motionDuration,from:t,till:e}),this}motionUpdateHandler(t,e){e!==this.position&&(this.setPosition(e),this.triggerEvent())}thumbMouseDownHandler(t){this.$thumb.addClass("tg-scrollbar-thumb-hold"),this.thumbDrag.start(t,{target:this.$thumb})}thumbDragStart(t){this.motionStop(),t.thumbPositionStart=this.thumbPosition,this.trigger(Mt.START)}thumbDragMove(t){let e=t.thumbPositionStart+t[this.settings.offsetName];const i=this.getMaxThumbPosition();e=d.clamp(e,0,i),this.setThumbPosition(e);let o=0;i>0&&(o=d.per(e/i)*this.getMaxPosition(),o=Math.round(o)),this.position=o,this.triggerEvent()}thumbDragEnd(t){this.$thumb&&this.$thumb.removeClass("tg-scrollbar-thumb-hold"),this.trigger(Mt.END)}triggerEvent(){this.trigger(Mt.CHANGE,this.position)}getPosition(){return this.position}setPosition(t){t=d.toNum(t,!0);const e=this.getMaxPosition();t=d.clamp(t,0,e),this.position=t,this.updateThumbPosition()}getMaxPosition(){return this.bodySize-this.viewSize}updatePosition(){const t=this.getMaxPosition(),e=d.clamp(this.position,0,t);this.position=e}setOffset(t){t=d.toNum(t);const e=this.position+t;return this.setPosition(e),this}getScale(){return this.scale}setScale(t){return t=d.per(t),this.scale=t,this.scaleChangeHandler(),this}scaleChangeHandler(){let t=Math.round(this.viewSize*this.scale);if(t=Math.max(t,Math.round(1.5*this.options.size)),t=Math.min(t,this.viewSize-1),this.thumbSize=t,this.$thumb){const t={};"h"===this.type?(t.height=this.size,t.width=this.thumbSize):(t.width=this.size,t.height=this.thumbSize),this.$thumb.css(t)}}updateTrackSize(){const t={};return"h"===this.type?(t.width=this.trackSize,t.height=this.size):(t.height=this.trackSize,t.width=this.size),this.$container.css(t),this}updateThumbSize(){let t=0;return this.bodySize&&(t=this.trackSize/this.bodySize),this.setScale(t),this}parseSize(t){return t=d.toNum(t),t=Math.round(t),t=Math.max(t,0)}updateSize(t,e,i){t=this.parseSize(t),this.viewSize=t,e=this.parseSize(e),this.bodySize=e,i=d.isNum(i)?this.parseSize(i):t,this.trackSize=i,this.previousFadeIn=null}fade(t){return!(!this.$container||!this.size)&&(this.previousFadeIn!==t&&(this.previousFadeIn=t,t?this.$container.hasClass("tg-fade-out")&&this.$container.removeClass("tg-fade-out").addClass("tg-fade-in"):this.$container.removeClass("tg-fade-in").addClass("tg-fade-out"),!0))}show(){if(this.updatePosition(),!this.getBlank())return!this.$container&&this.size>0&&this.create(),this.$container?(this.updateTrackSize(),this.updateThumbSize(),this):this;this.remove()}hide(){return this.updatePosition(),this.remove(),this}remove(){if(this.motionStop(),d.unbindEvents(this.scrollEvents),d.unbindEvents(this.trackEvents),this.thumbDrag&&(this.thumbDrag.destroy(),this.thumbDrag=null),!this.$container)return this;this.$thumb=null,this.$track=null,this.$container.remove(),this.$container=null}destroy(){return this.remove(),this}}const _t={CHANGE:"change",START:"start",END:"end"};class kt extends k{static EVENT=_t;visible=!0;constructor(t,e){super(),this.id=d.uid(4,`tg-scroll-pane-${e}-`),this.gradientInfo=[],this.$container=w(t).attr("id",this.id),this.$container.addClass("tg-scroll-pane"),this.$scrollView=this.$container.find(".tg-scroll-view"),this.$scrollBody=this.$scrollView.find(".tg-scroll-body"),this.scrollbarH=new Nt(Nt.H,this.$container),this.scrollbarH.bind(Nt.EVENT.CHANGE,((t,e)=>{this.scrollHChangeHandler()})).bind(Nt.EVENT.START,(t=>{this.scrollStartEndHandler(!0)})).bind(Nt.EVENT.END,(t=>{this.scrollStartEndHandler()})),this.scrollbarV=new Nt(Nt.V,this.$container),this.scrollbarV.bind(Nt.EVENT.CHANGE,((t,e)=>{this.scrollVChangeHandler()})).bind(Nt.EVENT.START,(t=>{this.scrollStartEndHandler(!0)})).bind(Nt.EVENT.END,(t=>{this.scrollStartEndHandler()})),this.options=this.generateOptions()}generateOptions(t){return d.merge({scrollbarH:{},scrollbarV:{},scrollbarFade:!1,scrollSizeOnKeyPress:20,gradient:30},t)}scrollStartEndHandler(t){t?this.trigger(_t.START):this.trigger(_t.END)}show(){return this.$container.show(),this.visible=!0,this}hide(){return this.$container.hide(),this.visible=!1,this}width(){return this.scrollPaneW}height(){return this.scrollPaneH}render(t){return this.visible?(this.options=this.generateOptions(t),this.update(),this):this}update(){this.scrollPaneW=this.options.scrollPaneW,this.scrollPaneH=this.options.scrollPaneH,this.scrollBodyW=this.options.scrollBodyW,this.scrollBodyH=this.options.scrollBodyH,this.updateScrollbar()}setGroupH(t){this.groupH=d.toList(t)}setGroupV(t){this.groupV=d.toList(t)}updateGroupH(){if(!d.isList(this.groupH))return this;const t=this.scrollbarH.getPosition();return this.groupH.forEach((function(e){e&&e.updateScrollHFromGroup(t)})),this}updateGroupV(){if(!d.isList(this.groupV))return this;const t=this.scrollbarV.getPosition();return this.groupV.forEach((function(e){e&&e.updateScrollVFromGroup(t)})),this}updateGroupList(){this.updateGroupH(),this.updateGroupV()}updateScrollHFromGroup(t){this.scrollbarH.getPosition()!==t&&(this.scrollbarH.setPosition(t),this.updateScrollLeft(),this.triggerEvent())}updateScrollVFromGroup(t){this.scrollbarV.getPosition()!==t&&(this.scrollbarV.setPosition(t),this.updateScrollTop(),this.triggerEvent())}setPosition(t,e){return this.scrollbarH.setPosition(t),this.scrollbarV.setPosition(e),this.updateScrollLeft(),this.updateScrollTop(),this.updateGroupList(),this}updateScrollbar(){this.scrollbarH.updateOptions(this.options.scrollbarH),this.scrollbarV.updateOptions(this.options.scrollbarV),this.updateScrollState(),this.updateScrollView(),this.updateScrollTrack(),this.scrollbarH.updateSize(this.scrollViewW,this.scrollBodyW,this.scrollTrackW),this.scrollbarV.updateSize(this.scrollViewH,this.scrollBodyH,this.scrollTrackH),this.hasScrollH?(this.scrollbarH.show(),this.scrollbarH.setPosition(this.scrollbarH.getPosition())):this.scrollbarH.hide(),this.hasScrollV?(this.scrollbarV.show(),this.scrollbarV.setPosition(this.scrollbarV.getPosition())):this.scrollbarV.hide(),this.updateScrollLeft(),this.updateScrollTop(),this.updateGroupList()}updateScrollState(){const t=this.scrollbarH.getSize(),e=this.scrollbarV.getSize(),i=this.scrollbarH.getBlank(),o=this.scrollbarV.getBlank(),n=this.options.scrollbarFade;let s=!1,r=0;(function(){(this.scrollPaneWe&&t.push("left"),ie&&t.push("top"),o{const i=`tg-gradient-${e}`;t.includes(e)?this.$container.addClass(i):this.$container.removeClass(i)})))}getScrollLeft(){return this.scrollbarH.getPosition()}getScrollTop(){return this.scrollbarV.getPosition()}getMaxScrollLeft(){return this.scrollbarH.getMaxPosition()}getMaxScrollTop(){return this.scrollbarV.getMaxPosition()}getScrollTopOffset(){const t=this.getScrollTop();return t-t%1e4}triggerEvent(){this.trigger(_t.CHANGE,{scrollLeft:this.getScrollLeft(),scrollTop:this.getScrollTop()})}scrollHChangeHandler(){this.updateScrollLeft(),this.updateGroupList(),this.triggerEvent()}scrollVChangeHandler(){this.updateScrollTop(),this.updateGroupList(),this.triggerEvent()}setOffsetH(t){const e=this.getScrollLeft();this.scrollbarH.setOffset(t);return this.getScrollLeft()!==e&&(this.updateScrollLeft(),this.updateGroupList(),this.triggerEvent(),!0)}setOffsetV(t){const e=this.getScrollTop();this.scrollbarV.setOffset(t);return this.getScrollTop()!==e&&(this.updateScrollTop(),this.updateGroupList(),this.triggerEvent(),!0)}mouseWheelHandler(t){const e=t.deltaX,i=t.deltaY,o=Math.abs(e);if(o>Math.abs(i)){if(this.hasScrollH)return this.setOffsetH(e)}else{if(this.hasScrollV)return this.setOffsetV(i);if(this.hasScrollH&&!o)return this.setOffsetH(i)}return!1}keyPageUpHandler(t){return this.setOffsetV(-this.scrollViewH)}keyPageDownHandler(t){return this.setOffsetV(this.scrollViewH)}keyEndHandler(t){return this.setOffsetV(this.scrollBodyH)}keyHomeHandler(t){return this.setOffsetV(-this.scrollBodyH)}keyLeftHandler(t){return this.setOffsetH(-this.options.scrollSizeOnKeyPress)}keyUpHandler(t){return this.setOffsetV(-this.options.scrollSizeOnKeyPress)}keyRightHandler(t){return this.setOffsetH(this.options.scrollSizeOnKeyPress)}keyDownHandler(t){return this.setOffsetV(this.options.scrollSizeOnKeyPress)}destroy(){return this.visible=!1,this.groupH=null,this.groupV=null,this.scrollbarV&&(this.scrollbarV.destroy(),this.scrollbarV=null),this.scrollbarH&&(this.scrollbarH.destroy(),this.scrollbarH=null),this.$container=null,this.$scrollView=null,this.$scrollBody=null,this}}const Vt={initScrollPane:function(){this.initFrozenStyle(),this.createScrollPane()},initFrozenStyle:function(){const t={HL:{container:this.$paneHL,cls:[]},HR:{container:this.$paneHR,cls:[]},TL:{container:this.$paneTL,cls:[]},TR:{container:this.$paneTR,cls:[]},BL:{container:this.$paneBL,cls:[]},BR:{container:this.$paneBR,cls:[]}},e="tg-frozen-h";this.frozenInfo.rows&&(this.frozenInfo.bottom?(t.BL.cls.push(e),t.BR.cls.push(e)):(t.TL.cls.push(e),t.TR.cls.push(e)));const i="tg-frozen-v",o="tg-frozen-line-v";this.frozenInfo.columns&&(this.frozenInfo.right?(t.HR.cls.push(i),t.TR.cls.push(i),t.BR.cls.push(i)):(t.HL.cls.push(i),t.TL.cls.push(i),t.BL.cls.push(i)),t.HL.cls.push(o),t.TL.cls.push(o),t.BL.cls.push(o));const n="tg-frozen",s=[n,e,i,o].join(" ");Object.keys(t).forEach((function(e){const i=t[e],o=i.container;o.removeClass(s);const r=i.cls;if(!r.length)return;const l=[n].concat(r).join(" ");o.addClass(l)}))},createScrollPane:function(){this.removeScrollPane(),this.scrollPaneMap={HL:new kt(this.$paneHL,"header-left"),HR:new kt(this.$paneHR,"header-right"),TL:new kt(this.$paneTL,"top-left"),TR:new kt(this.$paneTR,"top-right"),BL:new kt(this.$paneBL,"bottom-left"),BR:new kt(this.$paneBR,"bottom-right")},this.scrollPaneMap.BR.setGroupH([this.scrollPaneMap.HR,this.scrollPaneMap.TR]),this.scrollPaneMap.TR.setGroupH([this.scrollPaneMap.HR,this.scrollPaneMap.BR]),this.scrollPaneMap.BL.setGroupH([this.scrollPaneMap.HL,this.scrollPaneMap.TL]),this.scrollPaneMap.TL.setGroupH([this.scrollPaneMap.HL,this.scrollPaneMap.BL]),this.scrollPaneMap.BR.setGroupV(this.scrollPaneMap.BL),this.scrollPaneMap.BL.setGroupV(this.scrollPaneMap.BR),this.scrollPaneMap.TR.setGroupV(this.scrollPaneMap.TL),this.scrollPaneMap.TL.setGroupV(this.scrollPaneMap.TR),this.initActiveScrollPane(),this.initPaneVisibility()},initActiveScrollPane:function(){const t=this.getScrollPaneVP(),e=this.getScrollPaneHP(),i=`${t}${e}`;this.scrollPane=this.scrollPaneMap[i],this.scrollPane.bind(kt.EVENT.CHANGE,((t,e)=>{this.scrollPaneChangeHandler(t,e)})).bind(kt.EVENT.START,(t=>{this.rowHoverable=!1})).bind(kt.EVENT.END,(t=>{this.rowHoverable=!0}));let o={L:"L",R:"L"};this.frozenInfo.columns&&this.frozenInfo.right&&(o={L:"R",R:"L"});const n=`${t}${o[e]}`;this.scrollPaneFrozen=this.scrollPaneMap[n]},getScrollPaneVP:function(){return this.frozenInfo.rows&&!this.frozenInfo.bottom?"B":"T"},getScrollPaneHP:function(){return this.frozenInfo.columns&&!this.frozenInfo.right?"R":"L"},initPaneVisibility:function(){this.scrollPaneMap.HL.show(),this.scrollPaneMap.TL.show(),this.frozenInfo.columns?(this.scrollPaneMap.HR.show(),this.scrollPaneMap.TR.show(),this.frozenInfo.rows?(this.scrollPaneMap.BL.show(),this.scrollPaneMap.BR.show()):(this.scrollPaneMap.BL.hide(),this.scrollPaneMap.BR.hide())):(this.scrollPaneMap.HR.hide(),this.scrollPaneMap.TR.hide(),this.scrollPaneMap.BR.hide(),this.frozenInfo.rows?this.scrollPaneMap.BL.show():this.scrollPaneMap.BL.hide())},scrollPaneChangeHandler:function(t,e){this.hideColumnLine(),this.scrollLeft=e.scrollLeft,this.scrollTop=e.scrollTop,this.scrollRenderHandler()},scrollbarFadeInOutHandler:function(t,e){this.options.scrollbarFade&&(e?this.updateScrollPaneFade(!0):this.options.scrollbarFadeTimeout||this.updateScrollPaneFade(!1))},updateScrollPaneFade:function(t){if(!this.options.scrollbarFade)return;this.updateScrollPaneFadeSync(t);const e=this.options.scrollbarFadeTimeout;e&&(clearTimeout(this.timeout_fade),this.timeout_fade=setTimeout((()=>{this.updateScrollPaneFadeSync(!1)}),e))},updateScrollPaneFadeSync:function(t){if(this.previousScrollbarFadeIn===t)return;this.previousScrollbarFadeIn=t;const e=[];Object.keys(this.scrollPaneMap).forEach((t=>{const i=this.scrollPaneMap[t];i.hasScrollbar()&&e.push(i)})),e.length&&e.forEach((function(e){e.fade(t)}))},updateScrollPane:function(){const t=this.getScrollbarOptions();this.scrollPaneMap.HL.render(this.getScrollPaneOptions({scrollPaneW:this.paneWidthL,scrollPaneH:this.headerHeight,scrollBodyW:this.bodyWidthL,scrollBodyH:this.headerHeight,scrollbarV:t.HLV,scrollbarH:t.HLH})),this.scrollPaneMap.HR.render(this.getScrollPaneOptions({scrollPaneW:this.paneWidthR,scrollPaneH:this.headerHeight,scrollBodyW:this.bodyWidthR,scrollBodyH:this.headerHeight,scrollbarV:t.HRV,scrollbarH:t.HRH})),this.scrollPaneMap.TL.render(this.getScrollPaneOptions({scrollPaneW:this.paneWidthL,scrollPaneH:this.paneHeightT,scrollBodyW:this.bodyWidthL,scrollBodyH:this.bodyHeightT,scrollbarV:t.TLV,scrollbarH:t.TLH})),this.scrollPaneMap.TR.render(this.getScrollPaneOptions({scrollPaneW:this.paneWidthR,scrollPaneH:this.paneHeightT,scrollBodyW:this.bodyWidthR,scrollBodyH:this.bodyHeightT,scrollbarV:t.TRV,scrollbarH:t.TRH})),this.scrollPaneMap.BL.render(this.getScrollPaneOptions({scrollPaneW:this.paneWidthL,scrollPaneH:this.paneHeightB,scrollBodyW:this.bodyWidthL,scrollBodyH:this.bodyHeightB,scrollbarV:t.BLV,scrollbarH:t.BLH})),this.scrollPaneMap.BR.render(this.getScrollPaneOptions({scrollPaneW:this.paneWidthR,scrollPaneH:this.paneHeightB,scrollBodyW:this.bodyWidthR,scrollBodyH:this.bodyHeightB,scrollbarV:t.BRV,scrollbarH:t.BRH})),this.scrollLeft=this.getScrollLeft(),this.scrollTop=this.getScrollTop(),this.updateScrollPaneFade(Boolean(this.options.scrollbarFadeTimeout))},getScrollPaneOptions:function(t){const e=this.options;return t.scrollbarFade=e.scrollbarFade,t.gradient=d.clamp(d.toNum(e.scrollPaneGradient,!0),0,100),t},getScrollbarOptions:function(){const t=this.options.scrollbarRound,e={};return["HLH","HLV","HRH","HRV","TLH","TLV","TRH","TRV","BLH","BLV","BRH","BRV"].forEach((function(i){e[i]={size:0,round:t,blank:!1}})),this.scrollbarOptionsHandler(e),this.scrollbarFadeHandler(e),e},scrollbarOptionsHandler:function(t){const e=this.scrollbarSizeH,i=this.scrollbarSizeV;this.scrollbarHeaderHandler(t,e,i),this.frozenInfo.columns?this.frozenInfo.rows?this.scrollbarC1R1Handler(t,e,i):this.scrollbarC1R0Handler(t,e,i):this.frozenInfo.rows?this.scrollbarC0R1Handler(t,e,i):this.scrollbarC0R0Handler(t,e,i)},scrollbarFadeHandler:function(t){if(this.options.scrollbarFade)for(const e in t)if(d.hasOwn(t,e)){const i=t[e];i.size>0&&i.blank&&(i.blank=!1,i.size=0)}},scrollbarHeaderHandler:function(t,e,i){this.hasVScroll&&(this.frozenInfo.columns?(t.HRV.size=i,t.HRV.blank=1):(t.HLV.size=i,t.HLV.blank=1))},scrollbarC0R0Handler:function(t,e,i){t.TLH.size=e,t.TLV.size=i},scrollbarC0R1Handler:function(t,e,i){this.frozenInfo.bottom?this.scrollbarC0R1B1Handler(t,e,i):this.scrollbarC0R1B0Handler(t,e,i)},scrollbarC0R1B1Handler:function(t,e,i){t.BLH.size=e,t.TLV.size=i,this.hasVScroll&&(t.BLV.size=i,t.BLV.blank=1)},scrollbarC0R1B0Handler:function(t,e,i){t.BLH.size=e,t.BLV.size=i,this.hasVScroll&&(t.TLV.size=i,t.TLV.blank=1)},scrollbarC1R0Handler:function(t,e,i){this.frozenInfo.right?this.scrollbarC1R0R1Handler(t,e,i):this.scrollbarC1R0R0Handler(t,e,i)},scrollbarC1R0R1Handler:function(t,e,i){this.hasHScroll&&(t.TLH.size=e,this.scrollPaneHidden?(t.TRH.size=e,t.TLH.blank=!0):(t.TRH.size=e,t.TRH.blank=!0)),t.TRV.size=i},scrollbarC1R0R0Handler:function(t,e,i){this.hasHScroll&&(t.TRH.size=e,this.scrollPaneHidden?(t.TLH.size=e,t.TRH.blank=!0):(t.TLH.size=e,t.TLH.blank=!0)),t.TRV.size=i},scrollbarC1R1Handler:function(t,e,i){this.frozenInfo.right?this.frozenInfo.bottom?this.scrollbarC1R1R1B1Handler(t,e,i):this.scrollbarC1R1R1B0Handler(t,e,i):this.frozenInfo.bottom?this.scrollbarC1R1R0B1Handler(t,e,i):this.scrollbarC1R1R0B0Handler(t,e,i)},scrollbarC1R1R1B1Handler:function(t,e,i){this.hasHScroll&&(t.BLH.size=e,this.scrollPaneHidden&&(t.BRH.size=e,t.BLH.blank=!0)),t.TRV.size=i,this.hasVScroll&&(t.BRV.size=i,t.BRV.blank=1)},scrollbarC1R1R1B0Handler:function(t,e,i){this.hasHScroll&&(t.BLH.size=e,this.scrollPaneHidden?(t.BRH.size=e,t.BLH.blank=!0):(t.BRH.size=e,t.BRH.blank=!0)),t.BRV.size=i,this.hasVScroll&&(t.TRV.size=i,t.TRV.blank=1)},scrollbarC1R1R0B1Handler:function(t,e,i){this.hasHScroll&&(t.BRH.size=e,this.scrollPaneHidden&&(t.BLH.size=e,t.BRH.blank=!0)),t.TRV.size=i,this.hasVScroll&&(t.BRV.size=i,t.BRV.blank=1)},scrollbarC1R1R0B0Handler:function(t,e,i){this.hasHScroll&&(t.BRH.size=e,this.scrollPaneHidden?(t.BLH.size=e,t.BRH.blank=!0):(t.BLH.size=e,t.BLH.blank=!0)),t.BRV.size=i,this.hasVScroll&&(t.TRV.size=i,t.TRV.blank=1)},removeScrollPane:function(){clearTimeout(this.timeout_fade),this.previousScrollbarFadeIn=null,this.scrollPaneMap&&(Object.keys(this.scrollPaneMap).forEach((t=>{const e=this.scrollPaneMap[t];e&&e.destroy()})),this.scrollPaneMap=null,this.scrollPane=null,this.scrollPaneFrozen=null)}},Ot={updateScrollState:function(){this.updateGlobalScrollInfo(),this.updateHScrollState(),this.updateVScrollState(),this.updateBlankColumnWidth(),this.scrollStateChanged=!1,this.previousHasHScroll===this.hasHScroll&&this.previousHasVScroll===this.hasVScroll||(this.scrollStateChanged=!0,this.previousHasHScroll=this.hasHScroll,this.previousHasVScroll=this.hasVScroll,this.trigger(y.onScrollStateChanged,{hasHScroll:this.hasHScroll,hasVScroll:this.hasVScroll}))},updateGlobalScrollInfo:function(){this.totalRowsLength=this.getRowsLength(),this.totalRowsHeight=this.getRowsHeight(),this.frozenRowsHeight=this.getFrozenRowsHeight(),this.scrollRowsHeight=this.totalRowsHeight-this.frozenRowsHeight,this.totalRowsHeight=Math.max(this.totalRowsHeight,1),this.scrollRowsHeight=Math.max(this.scrollRowsHeight,1),this.flushRowFrom(this.totalRowsLength)},updateHScrollState:function(){if(this.hasHScroll=!0,this.updateScrollPaneHiddenState(),this.updateHScrollByScrollPaneHidden(),this.scrollPaneHidden)return;this.containerWidth-this.columnsWidth>=0&&(this.hasHScroll=!1)},getScrollPaneCurrentWidth:function(){return this.frozenInfo.right?this.bodyWidth-this.columnsWidthR:this.bodyWidth-this.columnsWidthL},updateHScrollByScrollPaneHidden:function(){if(this.scrollPaneHidden){this.hasHScroll=!1;this.getScrollPaneCurrentWidth()=this.totalRowsHeight&&(this.hasVScroll=!1)}},updateBlankColumnWidth:function(){let t=this.containerWidth-this.columnsWidth;!this.hasVScroll||this.hasHScroll||this.options.scrollbarFade||(t-=this.scrollbarSizeV),this.scrollPaneHidden&&(t=0),this.hasHScroll||(t>=0?(this.frozenInfo.columns?this.columnsWidthR+=t:this.columnsWidthL+=t,this.blankColumn.tg_width=t):this.hasHScroll=!0)}},$t={scrollToRow:function(t){const e=this.getRowItem(t);return this.scrollToItem(e,null),this},scrollToColumn:function(t){const e=this.getColumnItem(t);return this.scrollToItem(null,e),this},scrollToCell:function(t,e){const i=this.getRowItem(t),o=this.getColumnItem(e);return this.scrollToItem(i,o),this},scrollToFirstRow:function(){return this.setScrollTop(0),this},scrollToLastRow:function(){const t=this.getViewRows(),e=t[t.length-1],i=this.getScrollRowPosition(e);if(d.isNum(i))return this.setScrollTop(i),this},scrollToFirstColumn:function(){return this.setScrollLeft(0),this},scrollToLastColumn:function(t){const e=this.getViewColumns();let i=e[e.length-2];t&&(i=e[e.length-1]);const o=this.getScrollColumnPosition(i);if(d.isNum(o))return this.setScrollLeft(o),this},scrollRowIntoView:function(t){const e=this.getRowItem(t);return this.scrollItemIntoView(e,null),this},scrollColumnIntoView:function(t){const e=this.getColumnItem(t);return this.scrollItemIntoView(null,e),this},scrollCellIntoView:function(t,e){const i=this.getRowItem(t),o=this.getColumnItem(e);return this.scrollItemIntoView(i,o),this},setScroll:function(t,e){return t===this.scrollLeft&&e===this.scrollTop||(this.scrollLeft=t,this.scrollTop=e,this.scrollHandler()),this},setScrollLeft:function(t){return t===this.scrollLeft||(this.scrollLeft=t,this.scrollHandler()),this},setScrollTop:function(t){return t===this.scrollTop||(this.scrollTop=t,this.scrollHandler()),this},getScrollRowPosition:function(t){if(!t)return;let e=t.tg_view_index;return e-=this.frozenInfo.rows,e>=0?this.getRowTop(t):void 0},getScrollColumnPosition:function(t){if(!t)return;let e=t.tg_left;return this.frozenInfo.columns&&(e-=this.bodyWidthL),e>=0?e:void 0},scrollToItem:function(t,e){return this.scrollToChanged=!1,this.scrollToRowHandler(t),this.scrollToColumnHandler(e),this.scrollToChanged?(this.scrollHandler(),this):this},scrollToRowHandler:function(t){if(!t)return;const e=this.getScrollRowPosition(t);d.isNum(e)&&e!==this.scrollTop&&(this.scrollTop=e,this.scrollToChanged=!0)},scrollToColumnHandler:function(t){if(!t)return;const e=this.getScrollColumnPosition(t);d.isNum(e)&&e!==this.scrollLeft&&(this.scrollLeft=e,this.scrollToChanged=!0)},scrollItemIntoView:function(t,e){return this.scrollIntoViewChanged=!1,this.scrollRowIntoViewHandler(t),this.scrollColumnIntoViewHandler(e),this.scrollIntoViewChanged?(this.scrollHandler(),this):this},scrollRowIntoViewHandler:function(t){if(!t)return;const e=this.getScrollRowPosition(t);if(!d.isNum(e))return;if(ethis.scrollTop+o){const t=e-(o-i);this.scrollTop=t,this.scrollIntoViewChanged=!0}},scrollColumnIntoViewHandler:function(t){if(!t)return;const e=this.getScrollColumnPosition(t);if(!d.isNum(e))return;if(ethis.scrollLeft+o){const t=e-(o-i);this.scrollLeft=t,this.scrollIntoViewChanged=!0}},scrollOnInit:function(){const{scrollLeft:t,scrollTop:e,scrollColumn:i,scrollRow:o}=this.renderSettings;this.scrollIntoViewChanged=!1,Number.isInteger(t)&&t!==this.scrollLeft&&(this.scrollLeft=t,this.scrollIntoViewChanged=!0),Number.isInteger(e)&&e!==this.scrollTop&&(this.scrollTop=e,this.scrollIntoViewChanged=!0),i&&this.scrollColumnIntoViewHandler(i),o&&this.scrollRowIntoViewHandler(o),this.scrollIntoViewChanged&&this.scrollPane.setPosition(this.scrollLeft,this.scrollTop)},scrollHandler:function(){this.scrollPane.setPosition(this.scrollLeft,this.scrollTop),this.scrollRenderHandler()},scrollRenderHandler:function(){this.previousScrollLeft===this.scrollLeft&&this.previousScrollTop===this.scrollTop||(this.previousScrollLeft=this.scrollLeft,this.previousScrollTop=this.scrollTop,this.onNextUpdated((()=>{this.updateScrollPaneFade(!0),this.trigger(y.onScroll,{scrollLeft:this.scrollLeft,scrollTop:this.scrollTop})})),this.render())},scrollTouchStartHandler:function(t,e){this.hideColumnLine(),this.scrollTouchLeft=this.getScrollLeft(),this.scrollTouchTop=this.getScrollTop(),this.scrollMaxTouchLeft=this.getMaxScrollLeft(),this.scrollMaxTouchTop=this.getMaxScrollTop()},getTouchOrientation:function(t){return t.orientation?t.orientation:[e.LEFT,e.RIGHT].includes(t.direction)?(t.orientation||(t.orientation="Y"),t.orientation):[e.UP,e.DOWN].includes(t.direction)?(t.orientation||(t.orientation="X"),t.orientation):void 0},scrollTouchMoveHandler:function(t,e){if(e.touchLength>1)return;let i=e.offsetX,o=e.offsetY;const n=this.getTouchOrientation(e);"X"===n?i=0:"Y"===n&&(o=0);let s=this.scrollTouchLeft-i,r=this.scrollTouchTop-o;s=d.clamp(s,0,this.scrollMaxTouchLeft),r=d.clamp(r,0,this.scrollMaxTouchTop);let l=!1;this.scrollPaneHidden&&(l=this.scrollPaneFrozen.setOffsetH(-e.moveX),s=0);const h=this.getScrollLeft(),a=this.getScrollTop();(s!==h||r!==a||l)&&(d.preventDefault(e.e),this.setScroll(s,r))},scrollTouchEndHandler:function(){this.protectedItem=null},scrollTouchInertiaHandler:function(t,e){const i=this.getScrollLeft(),o=this.getScrollTop(),n=i-e.touchInertiaX,s=o-e.touchInertiaY;this.setScroll(n,s)},getScrollViewWidth:function(){let t=this.getScrollPaneWidth();return this.frozenInfo.right||(t-=this.getScrollbarWidth()),t},getScrollViewHeight:function(){let t=this.getScrollPaneHeight();return this.frozenInfo.bottom||(t-=this.getScrollbarHeight()),t},getScrollPaneWidth:function(){return this.scrollPane.width()},getScrollPaneHeight:function(){return this.scrollPane.height()},getScrollbarWidth:function(){return this.hasVScroll&&!this.options.scrollbarFade?this.scrollbarSizeV:0},getScrollbarHeight:function(){return this.hasHScroll&&!this.options.scrollbarFade?this.scrollbarSizeH:0},getScrollLeft:function(){return this.scrollPane.getScrollLeft()},getScrollTop:function(){return this.scrollPane.getScrollTop()},getMaxScrollLeft:function(){return this.scrollPane.getMaxScrollLeft()},getMaxScrollTop:function(){return this.scrollPane.getMaxScrollTop()}};class Dt{constructor(t){this.options=this.generateOptions(t)}generateOptions(t){return d.merge({ignore:null,sortField:"",sortFactor:1,sortBlankFactor:1,sortComparer:null},t)}sortList(t){if(!d.isList(t)||1===t.length)return!1;this.ignoreExcludeHandler(t);const e=this.comparerHandler(t);return this.ignoreIncludeHandler(t),e}getDefaultComparer(t){return(ft[t]||ft.string).bind(this)}comparerHandler(t){const e=this.options,i=e.sortField,o=e.sortFactor,n=e.sortBlankFactor,s=e.sortComparer;return"function"==typeof s&&(t.sort(((t,e)=>s.call(this,t,e,{sortField:i,sortFactor:o,sortBlankFactor:n}))),!0)}ignoreExcludeHandler(t){const e=this.options.ignore;this.ignoreListTop=[],this.ignoreListBottom=[];const i=[];for(let o=0,n=t.length;o{t.unshift(e.item)})),this.ignoreListBottom.forEach((e=>{t.push(e.item)}))}}const Bt={removeSortColumn:function(){return this.sortColumn=null,this.$header&&this.$header.find(".tg-column-sorted").removeClass("tg-column-sorted"),this},setSortColumn:function(t){if(!(t=this.getColumnItem(t)))return;if(!this.isColumnSortable(t))return;t===this.sortColumn?t.sortAsc=!t.sortAsc:d.hasOwn(t,"sortAsc")||(t.sortAsc=this.options.sortAsc),this.sortColumn=t;if(this.getRowsLength()-this.frozenInfo.rows<2)return;if(!this.headerCreated)return;this.updateRowsSort()&&(this.renderHeaderSort(),this.flushSort(),this.render("rows"))},renderHeaderSort:function(){const t=this.sortColumn;if(!t)return this;if(!this.isColumnSortable(t))return this;this.$header.find(".tg-column-sorted").removeClass("tg-column-sorted");const e=t.tg_view_index,i=this.$header.find(`.tg-header-item[column='${e}']`).find(".tg-column-header").addClass("tg-column-sorted");return t.sortAsc?i.removeClass("tg-sort-desc").addClass("tg-sort-asc"):i.removeClass("tg-sort-asc").addClass("tg-sort-desc"),this},getSortComparer:function(t){const e=t.comparer;if("function"==typeof e)return e;const i=this.options.sortComparers,o=i[e||t.type];return"function"==typeof o?o:i.string},updateRowsSort:function(){const t=this.sortColumn;if(!t)return!1;const e=t.id;return!!e&&this.sortRows(e,t)},sortRows:function(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const i=e.sortAsc?-1:1,o=this.options.sortBlankValueBottom?1:i,n=this.getSortComparer(e);let s=!1;const r=new Dt({ignore:function(t){return t.tg_frozen?{item:t,top:!0}:t.sortFixed?{item:t,top:"top"===t.sortFixed}:void 0},sortField:t,sortFactor:i,sortBlankFactor:o,sortComparer:n}),l=function(t){r.sortList(t)&&(s=!0),t.forEach((function(t,e){t.tg_sub_index=e,t.subs&&l(t.subs)}))};return l(this.rows),s&&this.initRowsHandler(),s}},At={default:{},lightblue:{rowHeight:35,scrollbarSize:10,scrollbarRound:!0},dark:{}},Wt={getAllThemes:function(){return Object.keys(At)},getThemeOptions:function(t){return At[t]}},Ft={update:function(){return this.flushBody(),this.render("rows"),this},updateRow:function(t,e){const i=this.getRowItem(t);if(!i)return this;if(e&&"object"==typeof e){const t=this.getItemSnapshot(e);Object.keys(t).forEach((function(e){i[e]=t[e]}))}return this.flushRow(i.tg_view_index),this.render("rows"),this},updateCell:function(t,e,i){const o=this.getRowItem(t);if(!o)return this;const n=this.getColumnItem(e);return n?(arguments.length>2&&(o[n.id]=i),this.flushCell(o.tg_view_index,n.tg_view_index),this.render("rows"),this):this},onNextUpdated:function(t){return"function"!=typeof t||this.once(y.onUpdated,t),this}},Gt={getViewport:function(){this.scrollLeft=this.getScrollLeft(),this.scrollTop=this.getScrollTop();return{rows:this.getViewportRows(),columns:this.getViewportColumns()}},getViewportRows:function(){const t=[],e=this.viewRows,i=e.length;if(!i)return t;let o=this.options.rowCacheLength;o=d.clamp(d.toNum(o,!0),0,i);const n=this.frozenInfo.rows;if(n){let e=0;for(;e1;){const n=Math.floor(.5*(e+i)),s=t[n],r=this.getRowTop(s),l=this.getRowHeight(s);if(or+l))return n;e=n}}const n=t[i];return o=e)return[];const i=[],o=this.frozenInfo.columns,n=this.viewColumns;for(let s=o,r=n.length;s0;){const n=t[0]-1;n>o&&t.unshift(n);const s=t[t.length-1]+1;si)&&!(n0&&(e.rows.length=o),i=t.options}return this.data=e,this.dataOptions=i,this}setDataSnapshot(t){return this.setData(this.generateDataSnapshot(t)),this}getData(){return this.data}toString(){return"[object Grid]"}}var Ut;Ut=jt.prototype,[v,R,S,T,E,I,L,x,z,F,G,j,K,q,J,Z,tt,et,pt,mt,bt,wt,vt,Ht,{keyTabHandler:function(t){},keyEnterHandler:function(t){},keyEscHandler:function(t){},keyPageUpHandler:function(t){return this.scrollPane.keyPageUpHandler(t)},keyPageDownHandler:function(t){return this.scrollPane.keyPageDownHandler(t)},keyEndHandler:function(t){return this.scrollPane.keyEndHandler(t)},keyHomeHandler:function(t){return this.scrollPane.keyHomeHandler(t)},keyLeftHandler:function(t){return this.scrollPaneHidden?this.scrollPaneFrozen.keyLeftHandler(t):this.scrollPane.keyLeftHandler(t)},keyUpHandler:function(t){return this.scrollPane.keyUpHandler(t)},keyRightHandler:function(t){return this.scrollPaneHidden?this.scrollPaneFrozen.keyRightHandler(t):this.scrollPane.keyRightHandler(t)},keyDownHandler:function(t){return this.scrollPane.keyDownHandler(t)}},Ct,yt,Rt,St,Tt,{showRow:function(t){return this.updateRowsInvisible(this.toRowItemList(t),!1)},hideRow:function(t){return this.updateRowsInvisible(this.toRowItemList(t),!0)},updateRowsInvisible:function(t,e){if(!t.length)return!1;const i=[];return t.forEach((t=>{t.invisible!==e&&(t.invisible=e,t.tg_invisible=e,i.push(t))})),!!i.length&&(this.update(),!0)}},Et,It,Lt,xt,zt,Vt,Ot,$t,Bt,Wt,Ft,Gt].forEach((t=>{for(const e in t){if(d.hasOwn(Ut,e))throw new Error(`ERROR: extends with an existing key: "${e}"`);Ut[e]=t[e]}}));const Xt=jt,Yt=e.VERSION,Kt=e.TIMESTAMP,qt={VERSION:Yt,TIMESTAMP:Kt,Grid:Xt,$:w,CONST:e,EventBase:k,Icon:X,Motion:B,ScrollPane:kt,Util:d}})();var n=o.$,s=o.MP,r=o._d,l=o.xA,h=o.In,a=o.T8,c=o.Gr,d=o.ht,u=o.J0,g=o.xv,f=o.Ay;export{n as $,s as CONST,r as EventBase,l as Grid,h as Icon,a as Motion,c as ScrollPane,d as TIMESTAMP,u as Util,g as VERSION,f as default}; \ No newline at end of file diff --git a/js/workflow-metadata.js b/js/workflow-metadata.js deleted file mode 100644 index 82dbe016..00000000 --- a/js/workflow-metadata.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Attaches metadata to the workflow on save - * - custom node pack version to all custom nodes used in the workflow - * - * Example metadata: - * "nodes": { - * "1": { - * type: "CheckpointLoaderSimple", - * ... - * properties: { - * cnr_id: "comfy-core", - * version: "0.3.8", - * }, - * }, - * } - * - * @typedef {Object} NodeInfo - * @property {string} ver - Version (git hash or semantic version) - * @property {string} cnr_id - ComfyRegistry node ID - * @property {boolean} enabled - Whether the node is enabled - */ - -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; - -class WorkflowMetadataExtension { - constructor() { - this.name = "Comfy.CustomNodesManager.WorkflowMetadata"; - this.installedNodes = {}; - this.comfyCoreVersion = null; - } - - /** - * Get the installed nodes info - * @returns {Promise>} The mapping from node name to its info. - * ver can either be a git commit hash or a semantic version such as "1.0.0" - * cnr_id is the id of the node in the ComfyRegistry - * enabled is true if the node is enabled, false if it is disabled - */ - async getInstalledNodes() { - const res = await api.fetchApi("/customnode/installed"); - return await res.json(); - } - - async init() { - this.installedNodes = await this.getInstalledNodes(); - this.comfyCoreVersion = (await api.getSystemStats()).system.comfyui_version; - } - - /** - * Called when any node is created - * @param {LGraphNode} node The newly created node - */ - nodeCreated(node) { - try { - // nodeData doesn't exist if node is missing or node is frontend only node - if (!node?.constructor?.nodeData?.python_module) return; - - const nodeProperties = (node.properties ??= {}); - const modules = node.constructor.nodeData.python_module.split("."); - const moduleType = modules[0]; - - if (moduleType === "custom_nodes") { - const nodePackageName = modules[1]; - const { cnr_id, aux_id, ver } = - this.installedNodes[nodePackageName] ?? - this.installedNodes[nodePackageName.toLowerCase()] ?? - {}; - - if (cnr_id === "comfy-core") return; // don't allow hijacking comfy-core name - if (cnr_id) nodeProperties.cnr_id = cnr_id; - else nodeProperties.aux_id = aux_id; - if (ver) nodeProperties.ver = ver.trim(); - } else if (["nodes", "comfy_extras", "comfy_api_nodes"].includes(moduleType)) { - nodeProperties.cnr_id = "comfy-core"; - nodeProperties.ver = this.comfyCoreVersion; - } - } catch (e) { - console.error(e); - } - } -} - -app.registerExtension(new WorkflowMetadataExtension()); diff --git a/json-checker.py b/json-checker.py deleted file mode 100644 index 49ae63a3..00000000 --- a/json-checker.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env python3 -"""JSON Entry Validator - -Validates JSON entries based on content structure. - -Validation rules based on JSON content: -- {"custom_nodes": [...]}: Validates required fields (author, title, reference, files, install_type, description) -- {"models": [...]}: Validates JSON syntax only (no required fields) -- Other JSON structures: Validates JSON syntax only - -Git repository URL validation (for custom_nodes): -1. URLs must NOT end with .git -2. URLs must follow format: https://github.com/{author}/{reponame} -3. .py and .js files are exempt from this check - -Supported formats: -- Array format: [{...}, {...}] -- Object format: {"custom_nodes": [...]} or {"models": [...]} -""" - -import json -import re -import sys -from pathlib import Path -from typing import Dict, List, Tuple - - -# Required fields for each entry type -REQUIRED_FIELDS_CUSTOM_NODE = ['author', 'title', 'reference', 'files', 'install_type', 'description'] -REQUIRED_FIELDS_MODEL = [] # model-list.json doesn't require field validation - -# Pattern for valid GitHub repository URL (without .git suffix) -GITHUB_REPO_PATTERN = re.compile(r'^https://github\.com/[^/]+/[^/]+$') - - -def get_entry_context(entry: Dict) -> str: - """Get identifying information from entry for error messages - - Args: - entry: JSON entry - - Returns: - String with author and reference info - """ - parts = [] - if 'author' in entry: - parts.append(f"author={entry['author']}") - if 'reference' in entry: - parts.append(f"ref={entry['reference']}") - if 'title' in entry: - parts.append(f"title={entry['title']}") - - if parts: - return " | ".join(parts) - else: - # No identifying info - show actual entry content (truncated) - import json - entry_str = json.dumps(entry, ensure_ascii=False) - if len(entry_str) > 100: - entry_str = entry_str[:100] + "..." - return f"content={entry_str}" - - -def validate_required_fields(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]: - """Validate that all required fields are present - - Args: - entry: JSON entry to validate - entry_index: Index of entry in array (for error reporting) - required_fields: List of required field names - - Returns: - List of error descriptions (without entry prefix/context) - """ - errors = [] - - for field in required_fields: - if field not in entry: - errors.append(f"Missing required field '{field}'") - elif entry[field] is None: - errors.append(f"Field '{field}' is null") - elif isinstance(entry[field], str) and not entry[field].strip(): - errors.append(f"Field '{field}' is empty") - elif field == 'files' and not entry[field]: # Empty array - errors.append("Field 'files' is empty array") - - return errors - - -def validate_git_repo_urls(entry: Dict, entry_index: int) -> List[str]: - """Validate git repository URLs in 'files' array - - Requirements: - - Git repo URLs must NOT end with .git - - Must follow format: https://github.com/{author}/{reponame} - - .py and .js files are exempt - - Args: - entry: JSON entry to validate - entry_index: Index of entry in array (for error reporting) - - Returns: - List of error descriptions (without entry prefix/context) - """ - errors = [] - - if 'files' not in entry or not isinstance(entry['files'], list): - return errors - - for file_url in entry['files']: - if not isinstance(file_url, str): - continue - - # Skip .py and .js files - they're exempt from git repo validation - if file_url.endswith('.py') or file_url.endswith('.js'): - continue - - # Check if it's a GitHub URL (likely a git repo) - if 'github.com' in file_url: - # Error if URL ends with .git - if file_url.endswith('.git'): - errors.append(f"Git repo URL must NOT end with .git: {file_url}") - continue - - # Validate format: https://github.com/{author}/{reponame} - if not GITHUB_REPO_PATTERN.match(file_url): - errors.append(f"Invalid git repo URL format (expected https://github.com/author/reponame): {file_url}") - - return errors - - -def validate_entry(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]: - """Validate a single JSON entry - - Args: - entry: JSON entry to validate - entry_index: Index of entry in array (for error reporting) - required_fields: List of required field names - - Returns: - List of error messages (empty if valid) - """ - errors = [] - - # Check required fields - errors.extend(validate_required_fields(entry, entry_index, required_fields)) - - # Check git repository URLs - errors.extend(validate_git_repo_urls(entry, entry_index)) - - return errors - - -def validate_json_file(file_path: str) -> Tuple[bool, List[str]]: - """Validate JSON file containing entries - - Args: - file_path: Path to JSON file - - Returns: - Tuple of (is_valid, error_messages) - """ - errors = [] - - # Check file exists - path = Path(file_path) - if not path.exists(): - return False, [f"File not found: {file_path}"] - - # Load JSON - try: - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - except json.JSONDecodeError as e: - return False, [f"Invalid JSON: {e}"] - except Exception as e: - return False, [f"Error reading file: {e}"] - - # Determine required fields based on JSON content - required_fields = [] - - # Validate structure - support both array and object formats - entries_to_validate = [] - - if isinstance(data, list): - # Direct array format: [{...}, {...}] - entries_to_validate = data - elif isinstance(data, dict): - # Object format: {"custom_nodes": [...]} or {"models": [...]} - # Determine validation based on keys - if 'custom_nodes' in data and isinstance(data['custom_nodes'], list): - required_fields = REQUIRED_FIELDS_CUSTOM_NODE - entries_to_validate = data['custom_nodes'] - elif 'models' in data and isinstance(data['models'], list): - required_fields = REQUIRED_FIELDS_MODEL - entries_to_validate = data['models'] - else: - # Other JSON structures (extension-node-map.json, etc.) - just validate JSON syntax - return True, [] - else: - return False, ["JSON root must be either an array or an object containing arrays"] - - # Validate each entry - for idx, entry in enumerate(entries_to_validate, start=1): - if not isinstance(entry, dict): - # Show actual value for type errors - entry_str = json.dumps(entry, ensure_ascii=False) if not isinstance(entry, str) else repr(entry) - if len(entry_str) > 150: - entry_str = entry_str[:150] + "..." - errors.append(f"\n❌ Entry #{idx}: Must be an object, got {type(entry).__name__}") - errors.append(f" Actual value: {entry_str}") - continue - - entry_errors = validate_entry(entry, idx, required_fields) - if entry_errors: - # Group errors by entry with context - context = get_entry_context(entry) - errors.append(f"\n❌ Entry #{idx} ({context}):") - for error in entry_errors: - errors.append(f" - {error}") - - is_valid = len(errors) == 0 - return is_valid, errors - - -def main(): - """Main entry point""" - if len(sys.argv) < 2: - print("Usage: python json-checker.py ") - print("\nValidates JSON entries based on content:") - print(" - {\"custom_nodes\": [...]}: Validates required fields (author, title, reference, files, install_type, description)") - print(" - {\"models\": [...]}: Validates JSON syntax only (no required fields)") - print(" - Other JSON structures: Validates JSON syntax only") - print("\nGit repo URL validation (for custom_nodes):") - print(" - URLs must NOT end with .git") - print(" - URLs must follow: https://github.com/{author}/{reponame}") - sys.exit(1) - - file_path = sys.argv[1] - - is_valid, errors = validate_json_file(file_path) - - if is_valid: - print(f"✅ {file_path}: Validation passed") - sys.exit(0) - else: - print(f"Validating: {file_path}") - print("=" * 60) - print("❌ Validation failed!\n") - print("Errors:") - # Count actual errors (lines starting with " -") - error_count = sum(1 for e in errors if e.strip().startswith('-')) - for error in errors: - # Don't add ❌ prefix to grouped entries (they already have it) - if error.strip().startswith('❌'): - print(error) - else: - print(error) - print(f"\nTotal errors: {error_count}") - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/misc/Impact.pack b/misc/Impact.pack deleted file mode 100644 index 93fd3284..00000000 --- a/misc/Impact.pack +++ /dev/null @@ -1,444 +0,0 @@ -{ - "Impact::MAKE_BASIC_PIPE": { - "category": "", - "config": { - "1": { - "input": { - "text": { - "name": "Positive prompt" - } - } - }, - "2": { - "input": { - "text": { - "name": "Negative prompt" - } - } - } - }, - "datetime": 1705418802481, - "external": [], - "links": [ - [ - 0, - 1, - 1, - 0, - 1, - "CLIP" - ], - [ - 0, - 1, - 2, - 0, - 1, - "CLIP" - ], - [ - 0, - 0, - 3, - 0, - 1, - "MODEL" - ], - [ - 0, - 1, - 3, - 1, - 1, - "CLIP" - ], - [ - 0, - 2, - 3, - 2, - 1, - "VAE" - ], - [ - 1, - 0, - 3, - 3, - 3, - "CONDITIONING" - ], - [ - 2, - 0, - 3, - 4, - 4, - "CONDITIONING" - ] - ], - "nodes": [ - { - "flags": {}, - "index": 0, - "mode": 0, - "order": 0, - "outputs": [ - { - "links": [], - "name": "MODEL", - "shape": 3, - "slot_index": 0, - "type": "MODEL" - }, - { - "links": [], - "name": "CLIP", - "shape": 3, - "slot_index": 1, - "type": "CLIP" - }, - { - "links": [], - "name": "VAE", - "shape": 3, - "slot_index": 2, - "type": "VAE" - } - ], - "pos": [ - 550, - 360 - ], - "properties": { - "Node name for S&R": "CheckpointLoaderSimple" - }, - "size": { - "0": 315, - "1": 98 - }, - "type": "CheckpointLoaderSimple", - "widgets_values": [ - "SDXL/sd_xl_base_1.0_0.9vae.safetensors" - ] - }, - { - "flags": {}, - "index": 1, - "inputs": [ - { - "link": null, - "name": "clip", - "type": "CLIP" - } - ], - "mode": 0, - "order": 1, - "outputs": [ - { - "links": [], - "name": "CONDITIONING", - "shape": 3, - "slot_index": 0, - "type": "CONDITIONING" - } - ], - "pos": [ - 940, - 480 - ], - "properties": { - "Node name for S&R": "CLIPTextEncode" - }, - "size": { - "0": 263, - "1": 99 - }, - "title": "Positive", - "type": "CLIPTextEncode", - "widgets_values": [ - "" - ] - }, - { - "flags": {}, - "index": 2, - "inputs": [ - { - "link": null, - "name": "clip", - "type": "CLIP" - } - ], - "mode": 0, - "order": 2, - "outputs": [ - { - "links": [], - "name": "CONDITIONING", - "shape": 3, - "slot_index": 0, - "type": "CONDITIONING" - } - ], - "pos": [ - 940, - 640 - ], - "properties": { - "Node name for S&R": "CLIPTextEncode" - }, - "size": { - "0": 263, - "1": 99 - }, - "title": "Negative", - "type": "CLIPTextEncode", - "widgets_values": [ - "" - ] - }, - { - "flags": {}, - "index": 3, - "inputs": [ - { - "link": null, - "name": "model", - "type": "MODEL" - }, - { - "link": null, - "name": "clip", - "type": "CLIP" - }, - { - "link": null, - "name": "vae", - "type": "VAE" - }, - { - "link": null, - "name": "positive", - "type": "CONDITIONING" - }, - { - "link": null, - "name": "negative", - "type": "CONDITIONING" - } - ], - "mode": 0, - "order": 3, - "outputs": [ - { - "links": null, - "name": "basic_pipe", - "shape": 3, - "slot_index": 0, - "type": "BASIC_PIPE" - } - ], - "pos": [ - 1320, - 360 - ], - "properties": { - "Node name for S&R": "ToBasicPipe" - }, - "size": { - "0": 241.79998779296875, - "1": 106 - }, - "type": "ToBasicPipe" - } - ], - "packname": "Impact", - "version": "1.0" - }, - "Impact::SIMPLE_DETAILER_PIPE": { - "category": "", - "config": { - "0": { - "output": { - "0": { - "visible": false - }, - "1": { - "visible": false - } - } - }, - "2": { - "input": { - "Select to add LoRA": { - "visible": false - }, - "Select to add Wildcard": { - "visible": false - }, - "wildcard": { - "visible": false - } - } - } - }, - "datetime": 1705419147116, - "external": [], - "links": [ - [ - null, - 0, - 2, - 0, - 6, - "BASIC_PIPE" - ], - [ - 0, - 0, - 2, - 1, - 13, - "BBOX_DETECTOR" - ], - [ - 1, - 0, - 2, - 2, - 15, - "SAM_MODEL" - ] - ], - "nodes": [ - { - "flags": {}, - "index": 0, - "mode": 0, - "order": 2, - "outputs": [ - { - "links": [], - "name": "BBOX_DETECTOR", - "shape": 3, - "type": "BBOX_DETECTOR" - }, - { - "links": null, - "name": "SEGM_DETECTOR", - "shape": 3, - "type": "SEGM_DETECTOR" - } - ], - "pos": [ - 590, - 830 - ], - "properties": { - "Node name for S&R": "UltralyticsDetectorProvider" - }, - "size": { - "0": 315, - "1": 78 - }, - "type": "UltralyticsDetectorProvider", - "widgets_values": [ - "bbox/Eyeful_v1.pt" - ] - }, - { - "flags": {}, - "index": 1, - "mode": 0, - "order": 3, - "outputs": [ - { - "links": [], - "name": "SAM_MODEL", - "shape": 3, - "type": "SAM_MODEL" - } - ], - "pos": [ - 590, - 960 - ], - "properties": { - "Node name for S&R": "SAMLoader" - }, - "size": { - "0": 315, - "1": 82 - }, - "type": "SAMLoader", - "widgets_values": [ - "sam_vit_b_01ec64.pth", - "AUTO" - ] - }, - { - "flags": {}, - "index": 2, - "inputs": [ - { - "link": null, - "name": "basic_pipe", - "type": "BASIC_PIPE" - }, - { - "link": null, - "name": "bbox_detector", - "slot_index": 1, - "type": "BBOX_DETECTOR" - }, - { - "link": null, - "name": "sam_model_opt", - "slot_index": 2, - "type": "SAM_MODEL" - }, - { - "link": null, - "name": "segm_detector_opt", - "type": "SEGM_DETECTOR" - }, - { - "link": null, - "name": "detailer_hook", - "type": "DETAILER_HOOK" - } - ], - "mode": 0, - "order": 5, - "outputs": [ - { - "links": null, - "name": "detailer_pipe", - "shape": 3, - "type": "DETAILER_PIPE" - } - ], - "pos": [ - 1044, - 812 - ], - "properties": { - "Node name for S&R": "BasicPipeToDetailerPipe" - }, - "size": { - "0": 400, - "1": 204 - }, - "type": "BasicPipeToDetailerPipe", - "widgets_values": [ - "", - "Select the LoRA to add to the text", - "Select the Wildcard to add to the text" - ] - } - ], - "packname": "Impact", - "version": "1.0" - } -} \ No newline at end of file diff --git a/model-list.json b/model-list.json deleted file mode 100644 index 5fcb5f06..00000000 --- a/model-list.json +++ /dev/null @@ -1,5869 +0,0 @@ -{ - "models": [ - { - "name": "TAEF1 Decoder", - "type": "TAESD", - "base": "FLUX.1", - "save_path": "vae_approx", - "description": "(FLUX.1 Verison) To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taef1_decoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taef1_decoder.pth", - "size": "4.71MB" - }, - { - "name": "TAEF1 Encoder", - "type": "TAESD", - "base": "FLUX.1", - "save_path": "vae_approx", - "description": "(FLUX.1 Verison) To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taef1_encoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taef1_encoder.pth", - "size": "4.71MB" - }, - { - "name": "TAESD3 Decoder", - "type": "TAESD", - "base": "SD3", - "save_path": "vae_approx", - "description": "(SD3 Verison) To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taesd3_decoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taesd3_decoder.pth", - "size": "4.94MB" - }, - { - "name": "TAESD3 Encoder", - "type": "TAESD", - "base": "SD3", - "save_path": "vae_approx", - "description": "(SD3 Verison) To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taesd3_encoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taesd3_encoder.pth", - "size": "4.94MB" - }, - { - "name": "TAESDXL Decoder", - "type": "TAESD", - "base": "SDXL", - "save_path": "vae_approx", - "description": "(SDXL Verison) To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taesdxl_decoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taesdxl_decoder.pth", - "size": "4.91MB" - }, - { - "name": "TAESDXL Encoder", - "type": "TAESD", - "base": "SDXL", - "save_path": "vae_approx", - "description": "(SDXL Verison) To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taesdxl_encoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taesdxl_encoder.pth", - "size": "4.91MB" - }, - { - "name": "TAESD Decoder", - "type": "TAESD", - "base": "SD1.x", - "save_path": "vae_approx", - "description": "To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taesd_decoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth", - "size": "4.91MB" - }, - { - "name": "TAESD Encoder", - "type": "TAESD", - "base": "SD1.x", - "save_path": "vae_approx", - "description": "To view the preview in high quality while running samples in ComfyUI, you will need this model.", - "reference": "https://github.com/madebyollin/taesd", - "filename": "taesd_encoder.pth", - "url": "https://github.com/madebyollin/taesd/raw/main/taesd_encoder.pth", - "size": "4.91MB" - }, - { - "name": "RealESRGAN x2", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "RealESRGAN x2 upscaler model", - "reference": "https://huggingface.co/ai-forever/Real-ESRGAN", - "filename": "RealESRGAN_x2.pth", - "url": "https://huggingface.co/ai-forever/Real-ESRGAN/resolve/main/RealESRGAN_x2.pth", - "size": "67.1MB" - }, - { - "name": "RealESRGAN x4", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "RealESRGAN x4 upscaler model", - "reference": "https://huggingface.co/ai-forever/Real-ESRGAN", - "filename": "RealESRGAN_x4.pth", - "url": "https://huggingface.co/ai-forever/Real-ESRGAN/resolve/main/RealESRGAN_x4.pth", - "size": "67.0MB" - }, - { - "name": "ESRGAN x4", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "ESRGAN x4 upscaler model", - "reference": "https://huggingface.co/Afizi/ESRGAN_4x.pth", - "filename": "ESRGAN_4x.pth", - "url": "https://huggingface.co/Afizi/ESRGAN_4x.pth/resolve/main/ESRGAN_4x.pth", - "size": "66.9MB" - }, - { - "name": "4x_foolhardy_Remacri", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "4x_foolhardy_Remacri upscaler model", - "reference": "https://huggingface.co/FacehugmanIII/4x_foolhardy_Remacri", - "filename": "4x_foolhardy_Remacri.pth", - "url": "https://huggingface.co/FacehugmanIII/4x_foolhardy_Remacri/resolve/main/4x_foolhardy_Remacri.pth", - "size": "67.0MB" - }, - { - "name": "4x-AnimeSharp", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "4x-AnimeSharp upscaler model", - "reference": "https://huggingface.co/Kim2091/AnimeSharp/", - "filename": "4x-AnimeSharp.pth", - "url": "https://huggingface.co/Kim2091/AnimeSharp/resolve/main/4x-AnimeSharp.pth", - "size": "67.0MB" - }, - { - "name": "4x-UltraSharp", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "4x-UltraSharp upscaler model", - "reference": "https://huggingface.co/Kim2091/UltraSharp/", - "filename": "4x-UltraSharp.pth", - "url": "https://huggingface.co/Kim2091/UltraSharp/resolve/main/4x-UltraSharp.pth", - "size": "67.0MB" - }, - { - "name": "4x_NMKD-Siax_200k", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "4x_NMKD-Siax_200k upscaler model", - "reference": "https://huggingface.co/gemasai/4x_NMKD-Siax_200k", - "filename": "4x_NMKD-Siax_200k.pth", - "url": "https://huggingface.co/gemasai/4x_NMKD-Siax_200k/resolve/main/4x_NMKD-Siax_200k.pth", - "size": "67.0MB" - }, - { - "name": "8x_NMKD-Superscale_150000_G", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "8x_NMKD-Superscale_150000_G upscaler model", - "reference": "https://huggingface.co/uwg/upscaler", - "filename": "8x_NMKD-Superscale_150000_G.pth", - "url": "https://huggingface.co/uwg/upscaler/resolve/main/ESRGAN/8x_NMKD-Superscale_150000_G.pth", - "size": "67.1MB" - }, - { - "name": "8x_NMKD-Faces_160000_G", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "8x_NMKD-Faces_160000_G upscaler model", - "reference": "https://huggingface.co/gemasai/8x_NMKD-Faces_160000_G/tree/main", - "filename": "8x_NMKD-Faces_160000_G.pth", - "url": "https://huggingface.co/gemasai/8x_NMKD-Faces_160000_G/resolve/main/8x_NMKD-Faces_160000_G.pth", - "size": "67.2MB" - }, - { - "name": "LDSR(Latent Diffusion Super Resolution)", - "type": "upscale", - "base": "upscale", - "save_path": "upscale_models/ldsr", - "description": "LDSR upscale model. Through the [a/ComfyUI-Flowty-LDSR](https://github.com/flowtyone/ComfyUI-Flowty-LDSR) extension, the upscale model can be utilized.", - "reference": "https://github.com/CompVis/latent-diffusion", - "filename": "last.ckpt", - "url": "https://heibox.uni-heidelberg.de/f/578df07c8fc04ffbadf3/?dl=1", - "size": "2.04GB" - }, - { - "name": "stabilityai/stable-diffusion-x4-upscaler", - "type": "checkpoint", - "base": "upscale", - "save_path": "checkpoints/upscale", - "description": "This upscaling model is a latent text-guided diffusion model and should be used with SD_4XUpscale_Conditioning and KSampler.", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-x4-upscaler", - "filename": "x4-upscaler-ema.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-x4-upscaler/resolve/main/x4-upscaler-ema.safetensors", - "size": "3.53GB" - }, - { - "name": "Deepbump", - "type": "deepbump", - "base": "deepbump", - "save_path": "deepbump", - "description": "Checkpoint of the deepbump model to generate height and normal maps textures from an image (requires comfy_mtb)", - "reference": "https://github.com/HugoTini/DeepBump", - "filename": "deepbump256.onnx", - "url": "https://github.com/HugoTini/DeepBump/raw/master/deepbump256.onnx", - "size": "26.7MB" - }, - { - "name": "GFPGAN 1.3", - "type": "face_restore", - "base": "face_restore", - "save_path": "face_restore", - "description": "Face restoration", - "reference": "https://github.com/TencentARC/GFPGAN", - "filename": "GFPGANv1.3.pth", - "url": "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth", - "size": "348.6MB" - }, - { - "name": "GFPGAN 1.4", - "type": "face_restore", - "base": "face_restore", - "save_path": "face_restore", - "description": "Face restoration", - "reference": "https://github.com/TencentARC/GFPGAN", - "filename": "GFPGANv1.4.pth", - "url": "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth", - "size": "348.6MB" - }, - { - "name": "RestoreFormer", - "type": "face_restore", - "base": "face_restore", - "save_path": "face_restore", - "description": "Face restoration", - "reference": "https://github.com/TencentARC/GFPGAN", - "filename": "RestoreFormer.pth", - "url": "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/RestoreFormer.pth", - "size": "290.8MB" - }, - { - "name": "Stable Video Diffusion Image-to-Video", - "type": "checkpoint", - "base": "SVD", - "save_path": "checkpoints/SVD", - "description": "Stable Video Diffusion (SVD) Image-to-Video is a diffusion model that takes in a still image as a conditioning frame, and generates a video from it.\nNOTE: 14 frames @ 576x1024", - "reference": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid", - "filename": "svd.safetensors", - "url": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid/resolve/main/svd.safetensors", - "size": "9.56GB" - }, - { - "name": "stabilityai/Stable Zero123", - "type": "zero123", - "base": "zero123", - "save_path": "checkpoints/zero123", - "description": "Stable Zero123 is a model for view-conditioned image generation based on [a/Zero123](https://github.com/cvlab-columbia/zero123).", - "reference": "https://huggingface.co/stabilityai/stable-zero123", - "filename": "stable_zero123.ckpt", - "url": "https://huggingface.co/stabilityai/stable-zero123/resolve/main/stable_zero123.ckpt", - "size": "8.58GB" - }, - { - "name": "Stable Video Diffusion Image-to-Video (XT)", - "type": "checkpoint", - "base": "SVD", - "save_path": "checkpoints/SVD", - "description": "Stable Video Diffusion (SVD) Image-to-Video is a diffusion model that takes in a still image as a conditioning frame, and generates a video from it.\nNOTE: 25 frames @ 576x1024 ", - "reference": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid-xt", - "filename": "svd_xt.safetensors", - "url": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid-xt/resolve/main/svd_xt.safetensors", - "size": "9.56GB" - }, - { - "name": "negative_hand Negative Embedding", - "type": "embedding", - "base": "SD1.5", - "save_path": "embeddings/SD1.5", - "description": "If you use this embedding with negatives, you can solve the issue of damaging your hands.", - "reference": "https://civitai.com/models/56519/negativehand-negative-embedding", - "filename": "negative_hand-neg.pt", - "url": "https://civitai.com/api/download/models/60938", - "size": "25KB" - }, - { - "name": "bad_prompt Negative Embedding", - "type": "embedding", - "base": "SD1.5", - "save_path": "embeddings/SD1.5", - "description": "The idea behind this embedding was to somehow train the negative prompt as an embedding, thus unifying the basis of the negative prompt into one word or embedding.", - "reference": "https://civitai.com/models/55700/badprompt-negative-embedding", - "filename": "bad_prompt_version2-neg.pt", - "url": "https://civitai.com/api/download/models/60095", - "size": "25KB" - }, - { - "name": "Deep Negative V1.75", - "type": "embedding", - "base": "SD1.5", - "save_path": "embeddings/SD1.5", - "description": "These embedding learn what disgusting compositions and color patterns are, including faulty human anatomy, offensive color schemes, upside-down spatial structures, and more. Placing it in the negative can go a long way to avoiding these things.", - "reference": "https://civitai.com/models/4629/deep-negative-v1x", - "filename": "ng_deepnegative_v1_75t.pt", - "url": "https://civitai.com/api/download/models/5637", - "size": "226KB" - }, - { - "name": "EasyNegative", - "type": "embedding", - "base": "SD1.5", - "save_path": "embeddings/SD1.5", - "description": "This embedding should be used in your NEGATIVE prompt. Adjust the strength as desired (seems to scale well without any distortions), the strength required may vary based on positive and negative prompts.", - "reference": "https://civitai.com/models/7808/easynegative", - "filename": "easynegative.safetensors", - "url": "https://civitai.com/api/download/models/9208", - "size": "25KB" - }, - { - "name": "stabilityai/comfyui_checkpoints/stable_cascade_stage_b.safetensors", - "type": "checkpoint", - "base": "Stable Cascade", - "save_path": "checkpoints/Stable-Cascade", - "description": "Stable Cascade stage_b checkpoints", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stable_cascade_stage_b.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/comfyui_checkpoints/stable_cascade_stage_b.safetensors", - "size": "4.55GB" - }, - { - "name": "stabilityai/comfyui_checkpoints/stable_cascade_stage_c.safetensors", - "type": "checkpoint", - "base": "Stable Cascade", - "save_path": "checkpoints/Stable-Cascade", - "description": "Stable Cascade stage_c checkpoints", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stable_cascade_stage_c.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/comfyui_checkpoints/stable_cascade_stage_c.safetensors", - "size": "9.22GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_a.safetensors (VAE)", - "type": "VAE", - "base": "Stable Cascade", - "save_path": "vae/Stable-Cascade", - "description": "Stable Cascade: stage_a", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_a.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_a.safetensors", - "size": "73.7MB" - }, - { - "name": "stabilityai/Stable Cascade: effnet_encoder.safetensors (VAE)", - "type": "VAE", - "base": "Stable Cascade", - "save_path": "vae/Stable-Cascade", - "description": "Stable Cascade: effnet_encoder.\nVAE encoder for stage_c latent.", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "effnet_encoder.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/effnet_encoder.safetensors", - "size": "81.5MB" - }, - { - "name": "stabilityai/Stable Cascade: stage_b.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_b", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_b.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b.safetensors", - "size": "6.25GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_b_bf16.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_b/bf16", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_b_bf16.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_bf16.safetensors", - "size": "3.13GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_b_lite.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_b/lite", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_b_lite.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_lite.safetensors", - "size": "2.80GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_b_lite.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_b/bf16,lite", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_b_lite_bf16.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_lite_bf16.safetensors", - "size": "1.40GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_c.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_c", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_c.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c.safetensors", - "size": "14.4GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_c_bf16.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_c/bf16", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_c_bf16.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_bf16.safetensors", - "size": "7.18GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_c_lite.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_c/lite", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_c_lite.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_lite.safetensors", - "size": "4.12GB" - }, - { - "name": "stabilityai/Stable Cascade: stage_c_lite.safetensors (UNET)", - "type": "diffusion_model", - "base": "Stable Cascade", - "save_path": "diffusion_models/Stable-Cascade", - "description": "Stable Cascade: stage_c/bf16,lite", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "stage_c_lite_bf16.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_lite_bf16.safetensors", - "size": "2.06GB" - }, - { - "name": "stabilityai/Stable Cascade: text_encoder (CLIP)", - "type": "clip", - "base": "Stable Cascade", - "save_path": "text_encoders/Stable-Cascade", - "description": "Stable Cascade: text_encoder", - "reference": "https://huggingface.co/stabilityai/stable-cascade", - "filename": "model.safetensors", - "url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/text_encoder/model.safetensors", - "size": "1.39GB" - }, - { - "name": "SDXL-Turbo 1.0 (fp16)", - "type": "checkpoint", - "base": "SDXL", - "save_path": "checkpoints/SDXL-TURBO", - "description": "SDXL-Turbo 1.0 fp16", - "reference": "https://huggingface.co/stabilityai/sdxl-turbo", - "filename": "sd_xl_turbo_1.0_fp16.safetensors", - "url": "https://huggingface.co/stabilityai/sdxl-turbo/resolve/main/sd_xl_turbo_1.0_fp16.safetensors", - "size": "6.94GB" - }, - { - "name": "SDXL-Turbo 1.0", - "type": "checkpoint", - "base": "SDXL", - "save_path": "checkpoints/SDXL-TURBO", - "description": "SDXL-Turbo 1.0", - "reference": "https://huggingface.co/stabilityai/sdxl-turbo", - "filename": "sd_xl_turbo_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/sdxl-turbo/resolve/main/sd_xl_turbo_1.0.safetensors", - "size": "13.9GB" - }, - { - "name": "sd_xl_base_1.0_0.9vae.safetensors", - "type": "checkpoint", - "base": "SDXL", - "save_path": "checkpoints/SDXL", - "description": "Stable Diffusion XL base model (VAE 0.9)", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0", - "filename": "sd_xl_base_1.0_0.9vae.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0_0.9vae.safetensors", - "size": "6.94GB" - }, - { - "name": "sd_xl_base_1.0.safetensors", - "type": "checkpoint", - "base": "SDXL", - "save_path": "checkpoints/SDXL", - "description": "Stable Diffusion XL base model", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0", - "filename": "sd_xl_base_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors", - "size": "6.94GB" - }, - { - "name": "sd_xl_refiner_1.0_0.9vae.safetensors", - "type": "checkpoint", - "base": "SDXL", - "save_path": "checkpoints/SDXL", - "description": "Stable Diffusion XL refiner model (VAE 0.9)", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0", - "filename": "sd_xl_refiner_1.0_0.9vae.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0_0.9vae.safetensors", - "size": "6.08GB" - }, - { - "name": "stable-diffusion-xl-refiner-1.0", - "type": "checkpoint", - "base": "SDXL", - "save_path": "checkpoints/SDXL", - "description": "Stable Diffusion XL refiner model", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0", - "filename": "sd_xl_refiner_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0.safetensors", - "size": "6.08GB" - }, - { - "name": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1 (diffusion_models/fp16)", - "type": "diffusion_model", - "base": "SDXL", - "save_path": "diffusion_models/xl-inpaint-0.1", - "description": "Stable Diffusion XL inpainting model 0.1. You need UNETLoader instead of CheckpointLoader.", - "reference": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1", - "filename": "diffusion_pytorch_model.fp16.safetensors", - "url": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1/resolve/main/unet/diffusion_pytorch_model.fp16.safetensors", - "size": "5.14GB" - }, - { - "name": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1 (UNET)", - "type": "diffusion_model", - "base": "SDXL", - "save_path": "diffusion_models/xl-inpaint-0.1", - "description": "Stable Diffusion XL inpainting model 0.1. You need UNETLoader instead of CheckpointLoader.", - "reference": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1/resolve/main/unet/diffusion_pytorch_model.safetensors", - "size": "10.3GB" - }, - { - "name": "sd_xl_offset_example-lora_1.0.safetensors", - "type": "lora", - "base": "SDXL", - "save_path": "loras/SDXL", - "description": "Stable Diffusion XL offset LoRA", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0", - "filename": "sd_xl_offset_example-lora_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors", - "size": "49.6MB" - }, - - { - "name": "SDXL Lightning LoRA (2steps)", - "type": "lora", - "base": "SDXL", - "save_path": "loras/SDXL-Lightning", - "description": "SDXL Lightning LoRA (2steps)", - "reference": "https://huggingface.co/ByteDance/SDXL-Lightning", - "filename": "sdxl_lightning_2step_lora.safetensors", - "url": "https://huggingface.co/ByteDance/SDXL-Lightning/resolve/main/sdxl_lightning_2step_lora.safetensors", - "size": "393.9MB" - }, - { - "name": "SDXL Lightning LoRA (4steps)", - "type": "lora", - "base": "SDXL", - "save_path": "loras/SDXL-Lightning", - "description": "SDXL Lightning LoRA (4steps)", - "reference": "https://huggingface.co/ByteDance/SDXL-Lightning", - "filename": "sdxl_lightning_4step_lora.safetensors", - "url": "https://huggingface.co/ByteDance/SDXL-Lightning/resolve/main/sdxl_lightning_4step_lora.safetensors", - "size": "393.9MB" - }, - { - "name": "SDXL Lightning LoRA (8steps)", - "type": "lora", - "base": "SDXL", - "save_path": "loras/SDXL-Lightning", - "description": "SDXL Lightning LoRA (8steps)", - "reference": "https://huggingface.co/ByteDance/SDXL-Lightning", - "filename": "sdxl_lightning_8step_lora.safetensors", - "url": "https://huggingface.co/ByteDance/SDXL-Lightning/resolve/main/sdxl_lightning_8step_lora.safetensors", - "size": "393.9MB" - }, - - { - "name": "DMD2 LoRA (4steps)", - "type": "lora", - "base": "SDXL", - "save_path": "loras/DMD2", - "description": "DMD2 LoRA (4steps)", - "reference": "https://huggingface.co/tianweiy/DMD2", - "filename": "dmd2_sdxl_4step_lora.safetensors", - "url": "https://huggingface.co/tianweiy/DMD2/resolve/main/dmd2_sdxl_4step_lora.safetensors", - "size": "787MB" - }, - { - "name": "DMD2 LoRA (4steps/fp16)", - "type": "lora", - "base": "SDXL", - "save_path": "loras/DMD2", - "description": "DMD2 LoRA (4steps/fp16)", - "reference": "https://huggingface.co/tianweiy/DMD2", - "filename": "dmd2_sdxl_4step_lora_fp16.safetensors", - "url": "https://huggingface.co/tianweiy/DMD2/resolve/main/dmd2_sdxl_4step_lora_fp16.safetensors", - "size": "394MB" - }, - - { - "name": "Hyper-SD LoRA (8steps) - FLUX.1 [Dev]", - "type": "lora", - "base": "FLUX.1", - "save_path": "loras/HyperSD/FLUX.1", - "description": "Hyper-SD LoRA (8steps) - FLUX.1 [Dev]", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-FLUX.1-dev-8steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-FLUX.1-dev-8steps-lora.safetensors", - "size": "1.39GB" - }, - { - "name": "Hyper-SD LoRA (16steps) - FLUX.1 [Dev]", - "type": "lora", - "base": "FLUX.1", - "save_path": "loras/HyperSD/FLUX.1", - "description": "Hyper-SD LoRA (16steps) - FLUX.1 [Dev]", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-FLUX.1-dev-16steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-FLUX.1-dev-16steps-lora.safetensors", - "size": "1.39GB" - }, - - { - "name": "Hyper-SD LoRA (1step) - SD1.5", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/HyperSD/SD15", - "description": "Hyper-SD LoRA (1step) - SD1.5", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD15-1step-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-1step-lora.safetensors", - "size": "269MB" - }, - { - "name": "Hyper-SD LoRA (2steps) - SD1.5", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/HyperSD/SD15", - "description": "Hyper-SD LoRA (2steps) - SD1.5", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD15-2steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-2steps-lora.safetensors", - "size": "269MB" - }, - { - "name": "Hyper-SD LoRA (4steps) - SD1.5", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/HyperSD/SD15", - "description": "Hyper-SD LoRA (4steps)", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD15-4steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-4steps-lora.safetensors", - "size": "269MB" - }, - { - "name": "Hyper-SD LoRA (8steps) - SD1.5", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/HyperSD/SD15", - "description": "Hyper-SD LoRA (8steps)", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD15-8steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-8steps-lora.safetensors", - "size": "269MB" - }, - { - "name": "Hyper-SD CFG LoRA (8steps) - SD1.5", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/HyperSD/SD15", - "description": "Hyper-SD CFG LoRA (8steps)", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD15-8steps-CFG-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-8steps-CFG-lora.safetensors", - "size": "269MB" - }, - { - "name": "Hyper-SD CFG LoRA (12steps) - SD1.5", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/HyperSD/SD15", - "description": "Hyper-SD CFG LoRA (12steps)", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD15-12steps-CFG-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-12steps-CFG-lora.safetensors", - "size": "269MB" - }, - - { - "name": "Hyper-SD LoRA (1step) - SDXL", - "type": "lora", - "base": "SDXL", - "save_path": "loras/HyperSD/SDXL", - "description": "Hyper-SD LoRA (1step) - SDXL", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SDXL-1step-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-1step-lora.safetensors", - "size": "787MB" - }, - { - "name": "Hyper-SD LoRA (2steps) - SDXL", - "type": "lora", - "base": "SDXL", - "save_path": "loras/HyperSD/SDXL", - "description": "Hyper-SD LoRA (2steps) - SDXL", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SDXL-2steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-2steps-lora.safetensors", - "size": "787MB" - }, - { - "name": "Hyper-SD LoRA (4steps) - SDXL", - "type": "lora", - "base": "SDXL", - "save_path": "loras/HyperSD/SDXL", - "description": "Hyper-SD LoRA (4steps) - SDXL", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SDXL-4steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-4steps-lora.safetensors", - "size": "787MB" - }, - { - "name": "Hyper-SD LoRA (8steps) - SDXL", - "type": "lora", - "base": "SDXL", - "save_path": "loras/HyperSD/SDXL", - "description": "Hyper-SD LoRA (8steps) - SDXL", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SDXL-8steps-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-8steps-lora.safetensors", - "size": "787MB" - }, - { - "name": "Hyper-SD CFG LoRA (8steps) - SDXL", - "type": "lora", - "base": "SDXL", - "save_path": "loras/HyperSD/SDXL", - "description": "Hyper-SD CFG LoRA (8steps) - SDXL", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SDXL-8steps-CFG-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-8steps-CFG-lora.safetensors", - "size": "787MB" - }, - { - "name": "Hyper-SD CFG LoRA (12steps) - SDXL", - "type": "lora", - "base": "SDXL", - "save_path": "loras/HyperSD/SDXL", - "description": "Hyper-SD CFG LoRA (12steps) - SDXL", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SDXL-12steps-CFG-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-12steps-CFG-lora.safetensors", - "size": "787MB" - }, - - { - "name": "Hyper-SD CFG LoRA (4steps) - SD3", - "type": "lora", - "base": "SD3", - "save_path": "loras/HyperSD/SD3", - "description": "Hyper-SD CFG LoRA (4steps) - SD3", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD3-4steps-CFG-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD3-4steps-CFG-lora.safetensors", - "size": "472MB" - }, - { - "name": "Hyper-SD CFG LoRA (8steps) - SD3", - "type": "lora", - "base": "SD3", - "save_path": "loras/HyperSD/SD3", - "description": "Hyper-SD CFG LoRA (8steps) - SD3", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD3-8steps-CFG-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD3-8steps-CFG-lora.safetensors", - "size": "472MB" - }, - { - "name": "Hyper-SD CFG LoRA (16steps) - SD3", - "type": "lora", - "base": "SD3", - "save_path": "loras/HyperSD/SD3", - "description": "Hyper-SD CFG LoRA (16steps) - SD3", - "reference": "https://huggingface.co/ByteDance/Hyper-SD", - "filename": "Hyper-SD3-16steps-CFG-lora.safetensors", - "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD3-16steps-CFG-lora.safetensors", - "size": "472MB" - }, - - { - "name": "comfyanonymous/flux_text_encoders - t5xxl (fp16)", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "Text Encoders for FLUX (fp16)", - "reference": "https://huggingface.co/comfyanonymous/flux_text_encoders", - "filename": "t5xxl_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp16.safetensors", - "size": "9.79GB" - }, - { - "name": "comfyanonymous/flux_text_encoders - t5xxl (fp8_e4m3fn)", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "Text Encoders for FLUX (fp8_e4m3fn)", - "reference": "https://huggingface.co/comfyanonymous/flux_text_encoders", - "filename": "t5xxl_fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn.safetensors", - "size": "4.89GB" - }, - { - "name": "comfyanonymous/flux_text_encoders - t5xxl (fp8_e4m3fn_scaled)", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "Text Encoders for FLUX (fp16)", - "reference": "https://huggingface.co/comfyanonymous/flux_text_encoders", - "filename": "t5xxl_fp8_e4m3fn_scaled.safetensors", - "url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn_scaled.safetensors", - "size": "5.16GB" - }, - - { - "name": "comfyanonymous/cosmos_cv8x8x8_1.0.safetensors", - "type": "VAE", - "base": "Cosmos-1.0", - "save_path": "default", - "description": "VAE model for Cosmos 1.0", - "reference": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/tree/main", - "filename": "cosmos_cv8x8x8_1.0.safetensors", - "url": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/resolve/main/vae/cosmos_cv8x8x8_1.0.safetensors", - "size": "211MB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Text2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Text2World Diffusion Model (7B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-7B-Text2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Text2World.safetensors", - "size": "14.5GB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Video2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Video2World Diffusion Model (7B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-7B-Video2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Video2World.safetensors", - "size": "14.5GB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Text2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Text2World Diffusion Model (14B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-14B-Text2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Text2World.safetensors", - "size": "28.5GB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Video2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Video2World Diffusion Model (14B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-14B-Video2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Video2World.safetensors", - "size": "28.5GB" - }, - - { - "name": "google-t5/t5-base", - "type": "clip", - "base": "t5-base", - "save_path": "text_encoders/t5-base", - "description": "T5 Base: Text-To-Text Transfer Transformer. This model can be loaded via CLIPLoader for Stable Audio workflow.", - "reference": "https://huggingface.co/google-t5/t5-base", - "filename": "model.safetensors", - "url": "https://huggingface.co/google-t5/t5-base/resolve/main/model.safetensors", - "size": "892MB" - }, - { - "name": "google-t5/t5-v1_1-xxl_encoderonly-fp16", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "The encoder part of https://huggingface.co/google/t5-v1_1-xxl, used with SD3 and Flux1", - "reference": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly", - "filename": "google_t5-v1_1-xxl_encoderonly-fp16.safetensors", - "url": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly/resolve/main/model.safetensors", - "size": "10.1GB" - }, - { - "name": "google-t5/t5-v1_1-xxl_encoderonly-fp8_e4m3fn", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "The encoder part of https://huggingface.co/google/t5-v1_1-xxl, used with SD3 and Flux1", - "reference": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly", - "filename": "google_t5-v1_1-xxl_encoderonly-fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly/resolve/main/t5xxl_fp8_e4m3fn.safetensors", - "size": "4.89GB" - }, - - - { - "name": "city96/t5-v1_1-xxl-encoder-Q3_K_L.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q3_K_L quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q3_K_L.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q3_K_L.gguf", - "size": "2.46GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q3_K_M.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q3_K_M quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q3_K_M.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q3_K_M.gguf", - "size": "2.3GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q3_K_S.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q3_K_S quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q3_K_S.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q3_K_S.gguf", - "size": "2.1GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q4_K_M.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q4_K_M quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q4_K_M.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q4_K_M.gguf", - "size": "2.9GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q4_K_S.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q4_K_S quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q4_K_S.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q4_K_S.gguf", - "size": "2.74GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q5_K_M.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q5_K_M quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q5_K_M.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q5_K_M.gguf", - "size": "3.39GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q5_K_S.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q5_K_S quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q5_K_S.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q5_K_S.gguf", - "size": "3.29GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q6_K.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q6_K quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q6_K.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q6_K.gguf", - "size": "3.91GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-Q8_0.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (Q8_0 quantized)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-Q8_0.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-Q8_0.gguf", - "size": "5.06GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-f16.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (float 16)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-f16.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-f16.gguf", - "size": "9.53GB" - }, - { - "name": "city96/t5-v1_1-xxl-encoder-f32.gguf", - "type": "clip", - "base": "t5", - "save_path": "text_encoders/t5", - "description": "t5xxl Text Encoder GGUF model. (float 32)", - "reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf", - "filename": "t5-v1_1-xxl-encoder-f32.gguf", - "url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-f32.gguf", - "size": "19.1GB" - }, - - { - "name": "Comfy-Org/clip_l", - "type": "clip", - "base": "clip", - "save_path": "default", - "description": "clip_l model (for SD1.x, SD2.x, SDXL, SD3.5, FLUX.1, HunyuanVideo, ...) ", - "reference": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8", - "filename": "clip_l.safetensors", - "url": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8/resolve/main/text_encoders/clip_l.safetensors", - "size": "246MB" - }, - { - "name": "Comfy-Org/clip_g", - "type": "clip", - "base": "clip", - "save_path": "default", - "description": "clip_g model (for SDXL, SD3.5)", - "reference": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8", - "filename": "clip_g.safetensors", - "url": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8/resolve/main/text_encoders/clip_g.safetensors", - "size": "1.39GB" - }, - - { - "name": "v1-5-pruned-emaonly.ckpt", - "type": "checkpoint", - "base": "SD1.5", - "save_path": "checkpoints/SD1.5", - "description": "Stable Diffusion 1.5 base model", - "reference": "https://huggingface.co/runwayml/stable-diffusion-v1-5", - "filename": "v1-5-pruned-emaonly.ckpt", - "url": "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt", - "size": "4.27GB" - }, - { - "name": "v2-1_512-ema-pruned.safetensors", - "type": "checkpoint", - "base": "SD2", - "save_path": "checkpoints/SD2.1", - "description": "Stable Diffusion 2 base model (512)", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-2-1-base", - "filename": "v2-1_512-ema-pruned.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.safetensors", - "size": "5.21GB" - }, - { - "name": "v2-1_768-ema-pruned.safetensors", - "type": "checkpoint", - "base": "SD2", - "save_path": "checkpoints/SD2.1", - "description": "Stable Diffusion 2 base model (768)", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-2-1", - "filename": "v2-1_768-ema-pruned.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors", - "size": "5.21GB" - }, - { - "name": "AbyssOrangeMix2 (hard)", - "type": "checkpoint", - "base": "SD1.5", - "save_path": "checkpoints/SD1.5", - "description": "AbyssOrangeMix2 - hard version (anime style)", - "reference": "https://huggingface.co/WarriorMama777/OrangeMixs", - "filename": "AbyssOrangeMix2_hard.safetensors", - "url": "https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_hard.safetensors", - "size": "5.57GB" - }, - { - "name": "AbyssOrangeMix3 A1", - "type": "checkpoint", - "base": "SD1.5", - "save_path": "checkpoints/SD1.5", - "description": "AbyssOrangeMix3 - A1 (anime style)", - "reference": "https://huggingface.co/WarriorMama777/OrangeMixs", - "filename": "AOM3A1_orangemixs.safetensors", - "url": "https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A1_orangemixs.safetensors", - "size": "2.13GB" - }, - { - "name": "AbyssOrangeMix3 A3", - "type": "checkpoint", - "base": "SD1.5", - "save_path": "checkpoints/SD1.5", - "description": "AbyssOrangeMix - A3 (anime style)", - "reference": "https://huggingface.co/WarriorMama777/OrangeMixs", - "filename": "AOM3A3_orangemixs.safetensors", - "url": "https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A3_orangemixs.safetensors", - "size": "2.13GB" - }, - { - "name": "Waifu Diffusion 1.5 Beta3 (fp16)", - "type": "checkpoint", - "base": "SD2.1", - "save_path": "checkpoints/SD2.1", - "description": "Waifu Diffusion 1.5 Beta3", - "reference": "https://huggingface.co/waifu-diffusion/wd-1-5-beta3", - "filename": "wd-illusion-fp16.safetensors", - "url": "https://huggingface.co/waifu-diffusion/wd-1-5-beta3/resolve/main/wd-illusion-fp16.safetensors", - "size": "2.58GB" - }, - { - "name": "illuminatiDiffusionV1_v11 unCLIP model", - "type": "unclip", - "base": "SD2.1", - "save_path": "checkpoints/SD2.1", - "description": "Mix model (SD2.1 unCLIP + illuminatiDiffusionV1_v11)", - "reference": "https://huggingface.co/comfyanonymous/illuminatiDiffusionV1_v11_unCLIP", - "filename": "illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/illuminatiDiffusionV1_v11_unCLIP/resolve/main/illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors", - "size": "3.98GB" - }, - { - "name": "Waifu Diffusion 1.5 unCLIP model", - "type": "unclip", - "base": "SD2.1", - "save_path": "checkpoints/SD2.1", - "description": "Mix model (SD2.1 unCLIP + Waifu Diffusion 1.5)", - "reference": "https://huggingface.co/comfyanonymous/wd-1.5-beta2_unCLIP", - "filename": "wd-1-5-beta2-aesthetic-unclip-h-fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/wd-1.5-beta2_unCLIP/resolve/main/wd-1-5-beta2-aesthetic-unclip-h-fp16.safetensors", - "size": "3.98GB" - }, - { - "name": "sdxl_vae.safetensors", - "type": "VAE", - "base": "SDXL", - "save_path": "vae/SDXL", - "description": "SDXL-VAE", - "reference": "https://huggingface.co/stabilityai/sdxl-vae", - "filename": "sdxl_vae.safetensors", - "url": "https://huggingface.co/stabilityai/sdxl-vae/resolve/main/sdxl_vae.safetensors", - "size": "334.6MB" - }, - { - "name": "vae-ft-mse-840000-ema-pruned", - "type": "VAE", - "base": "SD1.5", - "save_path": "vae/SD1.5", - "description": "vae-ft-mse-840000-ema-pruned", - "reference": "https://huggingface.co/stabilityai/sd-vae-ft-mse-original", - "filename": "vae-ft-mse-840000-ema-pruned.safetensors", - "url": "https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors", - "size": "334.6MB" - }, - { - "name": "orangemix.vae", - "type": "VAE", - "base": "SD1.5", - "save_path": "vae/SD1.5", - "description": "orangemix vae model", - "reference": "https://huggingface.co/WarriorMama777/OrangeMixs", - "filename": "orangemix.vae.pt", - "url": "https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/VAEs/orangemix.vae.pt", - "size": "822.8MB" - }, - { - "name": "kl-f8-anime2", - "type": "VAE", - "base": "SD2.1", - "save_path": "vae/SD2.1", - "description": "kl-f8-anime2 vae model", - "reference": "https://huggingface.co/hakurei/waifu-diffusion-v1-4", - "filename": "kl-f8-anime2.ckpt", - "url": "https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt", - "size": "404.7MB" - }, - { - "name": "OpenAI Consistency Decoder", - "type": "VAE", - "base": "SD1.5", - "save_path": "vae/SD1.5/openai_consistency_decoder", - "description": "OpenAI Consistency Decoder. Improved decoding for stable diffusion vaes.", - "reference": "https://github.com/openai/consistencydecoder", - "filename": "decoder.pt", - "url": "https://openaipublic.azureedge.net/diff-vae/c9cebd3132dd9c42936d803e33424145a748843c8f716c0814838bdc8a2fe7cb/decoder.pt", - "size": "2.49GB" - }, - { - "name": "LCM LoRA SD1.5", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/SD1.5/lcm", - "description": "Latent Consistency LoRA for SD1.5", - "reference": "https://huggingface.co/latent-consistency/lcm-lora-sdv1-5", - "filename": "pytorch_lora_weights.safetensors", - "url": "https://huggingface.co/latent-consistency/lcm-lora-sdv1-5/resolve/main/pytorch_lora_weights.safetensors", - "size": "134.6MB" - }, - { - "name": "LCM LoRA SSD-1B", - "type": "lora", - "base": "SSD-1B", - "save_path": "loras/SSD-1B/lcm", - "description": "Latent Consistency LoRA for SSD-1B", - "reference": "https://huggingface.co/latent-consistency/lcm-lora-ssd-1b", - "filename": "pytorch_lora_weights.safetensors", - "url": "https://huggingface.co/latent-consistency/lcm-lora-ssd-1b/resolve/main/pytorch_lora_weights.safetensors", - "size": "210.0MB" - }, - { - "name": "LCM LoRA SDXL", - "type": "lora", - "base": "SDXL", - "save_path": "loras/SDXL/lcm", - "description": "Latent Consistency LoRA for SDXL", - "reference": "https://huggingface.co/latent-consistency/lcm-lora-sdxl", - "filename": "pytorch_lora_weights.safetensors", - "url": "https://huggingface.co/latent-consistency/lcm-lora-sdxl/resolve/main/pytorch_lora_weights.safetensors", - "size": "393.9MB" - }, - { - "name": "Segmind-Vega", - "type": "checkpoint", - "base": "segmind-vega", - "save_path": "checkpoints/segmind-vega", - "description": "The Segmind-Vega Model is a distilled version of the Stable Diffusion XL (SDXL), offering a remarkable 70% reduction in size and an impressive 100% speedup while retaining high-quality text-to-image generation capabilities.", - "reference": "https://huggingface.co/segmind/Segmind-Vega", - "filename": "segmind-vega.safetensors", - "url": "https://huggingface.co/segmind/Segmind-Vega/resolve/main/segmind-vega.safetensors", - "size": "3.29GB" - }, - { - "name": "Segmind-VegaRT - Latent Consistency Model (LCM) LoRA of Segmind-Vega", - "type": "lora", - "base": "segmind-vega", - "save_path": "loras/segmind-vega", - "description": "Segmind-VegaRT a distilled consistency adapter for Segmind-Vega that allows to reduce the number of inference steps to only between 2 - 8 steps.", - "reference": "https://huggingface.co/segmind/Segmind-VegaRT", - "filename": "pytorch_lora_weights.safetensors", - "url": "https://huggingface.co/segmind/Segmind-VegaRT/resolve/main/pytorch_lora_weights.safetensors", - "size": "239.2MB" - }, - { - "name": "Theovercomer8's Contrast Fix (SD2.1)", - "type": "lora", - "base": "SD2.1", - "save_path": "loras/SD2.1", - "description": "LORA: Theovercomer8's Contrast Fix (SD2.1)", - "reference": "https://civitai.com/models/8765/theovercomer8s-contrast-fix-sd15sd21-768", - "filename": "theovercomer8sContrastFix_sd21768.safetensors", - "url": "https://civitai.com/api/download/models/10350", - "size": "163MB" - }, - { - "name": "Theovercomer8's Contrast Fix (SD1.5)", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/SD1.5", - "description": "LORA: Theovercomer8's Contrast Fix (SD1.5)", - "reference": "https://civitai.com/models/8765/theovercomer8s-contrast-fix-sd15sd21-768", - "filename": "theovercomer8sContrastFix_sd15.safetensors", - "url": "https://civitai.com/api/download/models/10638", - "size": "113MB" - }, - { - "name": "T2I-Adapter (depth)", - "type": "T2I-Adapter", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter for depth", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_depth_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth", - "size": "309.5MB" - }, - { - "name": "T2I-Adapter (seg)", - "type": "T2I-Adapter", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter for seg", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_seg_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_seg_sd14v1.pth", - "size": "309.5MB" - }, - { - "name": "T2I-Adapter (sketch)", - "type": "T2I-Adapter", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter for sketch", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_sketch_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_sketch_sd14v1.pth", - "size": "308.0MB" - }, - { - "name": "T2I-Adapter (keypose)", - "type": "T2I-Adapter", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter for keypose", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_keypose_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_keypose_sd14v1.pth", - "size": "309.5MB" - }, - { - "name": "T2I-Adapter (openpose)", - "type": "T2I-Adapter", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter for openpose", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_openpose_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_openpose_sd14v1.pth", - "size": "309.5MB" - }, - { - "name": "T2I-Adapter (color)", - "type": "T2I-Adapter", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter for color", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_color_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_color_sd14v1.pth", - "size": "74.8MB" - }, - { - "name": "T2I-Adapter (canny)", - "type": "T2I-Adapter", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter for canny", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_canny_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_canny_sd14v1.pth", - "size": "308.0MB" - }, - { - "name": "T2I-Style model", - "type": "T2I-Style", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "ControlNet T2I-Adapter style model. Need to download CLIPVision model.", - "reference": "https://huggingface.co/TencentARC/T2I-Adapter", - "filename": "t2iadapter_style_sd14v1.pth", - "url": "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_style_sd14v1.pth", - "size": "154.4MB" - }, - { - "name": "T2I-Adapter XL (lineart) FP16", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for lineart", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-lineart-sdxl-1.0", - "filename": "t2i-adapter-lineart-sdxl-1.0.fp16.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-lineart-sdxl-1.0/resolve/main/diffusion_pytorch_model.fp16.safetensors", - "size": "158.1MB" - }, - { - "name": "T2I-Adapter XL (canny) FP16", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for canny", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-canny-sdxl-1.0", - "filename": "t2i-adapter-canny-sdxl-1.0.fp16.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-canny-sdxl-1.0/resolve/main/diffusion_pytorch_model.fp16.safetensors", - "size": "158.1MB" - }, - { - "name": "T2I-Adapter XL (depth-zoe) FP16", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for depth-zoe", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-depth-zoe-sdxl-1.0", - "filename": "t2i-adapter-depth-zoe-sdxl-1.0.fp16.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-depth-zoe-sdxl-1.0/resolve/main/diffusion_pytorch_model.fp16.safetensors", - "size": "158.1MB" - }, - { - "name": "T2I-Adapter XL (depth-midas) FP16", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for depth-midas", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-depth-midas-sdxl-1.0", - "filename": "t2i-adapter-depth-midas-sdxl-1.0.fp16.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-depth-midas-sdxl-1.0/resolve/main/diffusion_pytorch_model.fp16.safetensors", - "size": "158.1MB" - }, - { - "name": "T2I-Adapter XL (sketch) FP16", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for sketch", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-sketch-sdxl-1.0", - "filename": "t2i-adapter-sketch-sdxl-1.0.fp16.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-sketch-sdxl-1.0/resolve/main/diffusion_pytorch_model.fp16.safetensors", - "size": "158.1MB" - }, - { - "name": "T2I-Adapter XL (lineart)", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for lineart", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-lineart-sdxl-1.0", - "filename": "t2i-adapter-lineart-sdxl-1.0.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-lineart-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "316.1MB" - }, - { - "name": "T2I-Adapter XL (canny)", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for canny", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-canny-sdxl-1.0", - "filename": "t2i-adapter-canny-sdxl-1.0.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-canny-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "316.1MB" - }, - { - "name": "T2I-Adapter XL (depth-zoe)", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for depth-zoe", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-depth-zoe-sdxl-1.0", - "filename": "t2i-adapter-depth-zoe-sdxl-1.0.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-depth-zoe-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "316.1MB" - }, - { - "name": "T2I-Adapter XL (depth-midas)", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for depth-midas", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-depth-midas-sdxl-1.0", - "filename": "t2i-adapter-depth-midas-sdxl-1.0.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-depth-midas-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "316.1MB" - }, - { - "name": "T2I-Adapter XL (sketch)", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for sketch", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-sketch-sdxl-1.0", - "filename": "t2i-adapter-sketch-sdxl-1.0.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-sketch-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "316.1MB" - }, - { - "name": "T2I-Adapter XL (openpose)", - "type": "T2I-Adapter", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet T2I-Adapter XL for openpose", - "reference": "https://huggingface.co/TencentARC/t2i-adapter-openpose-sdxl-1.0", - "filename": "t2i-adapter-openpose-sdxl-1.0.safetensors", - "url": "https://huggingface.co/TencentARC/t2i-adapter-openpose-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "316.1MB" - }, - { - "name": "CiaraRowles/TemporalNet2", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/SD1.5", - "description": "TemporalNet was a ControlNet model designed to enhance the temporal consistency of generated outputs", - "reference": "https://huggingface.co/CiaraRowles/TemporalNet2", - "filename": "temporalnetversion2.safetensors", - "url": "https://huggingface.co/CiaraRowles/TemporalNet2/resolve/main/temporalnetversion2.safetensors", - "size": "5.71GB" - }, - { - "name": "CiaraRowles/TemporalNet1XL (1.0)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/TemporalNet1XL", - "description": "This is TemporalNet1XL, it is a re-train of the controlnet TemporalNet1 with Stable Diffusion XL.", - "reference": "https://huggingface.co/CiaraRowles/controlnet-temporalnet-sdxl-1.0", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/CiaraRowles/controlnet-temporalnet-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "5.00GB" - }, - - { - "name": "Comfy-Org/sigclip_vision_384 (patch14_384)", - "type": "clip_vision", - "base": "sigclip", - "save_path": "clip_vision", - "description": "This clip vision model is required for FLUX.1 Redux.", - "reference": "https://huggingface.co/Comfy-Org/sigclip_vision_384/tree/main", - "filename": "sigclip_vision_patch14_384.safetensors", - "url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors", - "size": "857MB" - }, - - { - "name": "CLIPVision model (stabilityai/clip_vision_g)", - "type": "clip_vision", - "base": "ViT-G", - "save_path": "clip_vision", - "description": "clip_g vision model", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "clip_vision_g.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/revision/clip_vision_g.safetensors", - "size": "3.69GB" - }, - { - "name": "CLIPVision model (openai/clip-vit-large)", - "type": "clip_vision", - "base": "ViT-L", - "save_path": "clip_vision", - "description": "CLIPVision model (needed for styles model)", - "reference": "https://huggingface.co/openai/clip-vit-large-patch14", - "filename": "clip-vit-large-patch14.safetensors", - "url": "https://huggingface.co/openai/clip-vit-large-patch14/resolve/main/model.safetensors", - "size": "1.71GB" - }, - { - "name": "CLIPVision model (Kwai-Kolors/Kolors-IP-Adapter-Plus/clip-vit-large)", - "type": "clip_vision", - "base": "ViT-L", - "save_path": "clip_vision", - "description": "CLIPVision model (needed for IP-Adapter)", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus", - "filename": "clip-vit-large-patch14-336.bin", - "url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/image_encoder/pytorch_model.bin", - "size": "1.71GB" - }, - { - "name": "CLIPVision model (IP-Adapter) CLIP-ViT-H-14-laion2B-s32B-b79K", - "type": "clip_vision", - "base": "ViT-H", - "save_path": "clip_vision", - "description": "CLIPVision model (needed for IP-Adapter)", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/image_encoder/model.safetensors", - "size": "2.53GB" - }, - { - "name": "CLIPVision model (IP-Adapter) CLIP-ViT-bigG-14-laion2B-39B-b160k", - "type": "clip_vision", - "base": "ViT-G", - "save_path": "clip_vision", - "description": "CLIPVision model (needed for IP-Adapter)", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "CLIP-ViT-bigG-14-laion2B-39B-b160k.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/image_encoder/model.safetensors", - "size": "3.69GB" - }, - { - "name": "stabilityai/control-lora-canny-rank128.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: canny rank128", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-canny-rank128.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-canny-rank128.safetensors", - "size": "395.7MB" - }, - { - "name": "stabilityai/control-lora-depth-rank128.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: depth rank128", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-depth-rank128.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-depth-rank128.safetensors", - "size": "395.7MB" - }, - { - "name": "stabilityai/control-lora-recolor-rank128.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: recolor rank128", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-recolor-rank128.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-recolor-rank128.safetensors", - "size": "395.7MB" - }, - { - "name": "stabilityai/control-lora-sketch-rank128-metadata.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: sketch rank128 metadata", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-sketch-rank128-metadata.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-sketch-rank128-metadata.safetensors", - "size": "395.7MB" - }, - { - "name": "stabilityai/control-lora-canny-rank256.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: canny rank256", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-canny-rank256.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-canny-rank256.safetensors", - "size": "774.5MB" - }, - { - "name": "stabilityai/control-lora-depth-rank256.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: depth rank256", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-depth-rank256.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-depth-rank256.safetensors", - "size": "774.4MB" - }, - { - "name": "stabilityai/control-lora-recolor-rank256.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: recolor rank256", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-recolor-rank256.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-recolor-rank256.safetensors", - "size": "774.4MB" - }, - { - "name": "stabilityai/control-lora-sketch-rank256.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Control-LoRA: sketch rank256", - "reference": "https://huggingface.co/stabilityai/control-lora", - "filename": "control-lora-sketch-rank256.safetensors", - "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors", - "size": "774.5MB" - }, - { - "name": "SDXL-controlnet: OpenPose (v2)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet openpose model for SDXL", - "reference": "https://huggingface.co/thibaud/controlnet-openpose-sdxl-1.0", - "filename": "OpenPoseXL2.safetensors", - "url": "https://huggingface.co/thibaud/controlnet-openpose-sdxl-1.0/resolve/main/OpenPoseXL2.safetensors", - "size": "5.00GB" - }, - { - "name": "controlnet-SargeZT/controlnet-sd-xl-1.0-softedge-dexined", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet softedge model for SDXL", - "reference": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-softedge-dexined", - "filename": "controlnet-sd-xl-1.0-softedge-dexined.safetensors", - "url": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-softedge-dexined/resolve/main/controlnet-sd-xl-1.0-softedge-dexined.safetensors", - "size": "5.00GB" - }, - { - "name": "controlnet-SargeZT/controlnet-sd-xl-1.0-depth-16bit-zoe", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "ControlNet depth-zoe model for SDXL", - "reference": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-depth-16bit-zoe", - "filename": "depth-zoe-xl-v1.0-controlnet.safetensors", - "url": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-depth-16bit-zoe/resolve/main/depth-zoe-xl-v1.0-controlnet.safetensors", - "size": "5.00GB" - }, - { - "name": "ControlNet-v1-1 (ip2p; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (ip2p)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11e_sd15_ip2p_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_ip2p_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (shuffle; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (shuffle)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11e_sd15_shuffle_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_shuffle_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (canny; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (canny)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_canny_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_canny_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (depth; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (depth)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11f1p_sd15_depth_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11f1p_sd15_depth_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (inpaint; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (inpaint)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_inpaint_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_inpaint_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (lineart; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (lineart)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_lineart_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_lineart_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (mlsd; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (mlsd)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_mlsd_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_mlsd_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (normalbae; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (normalbae)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_normalbae_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_normalbae_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (openpose; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (openpose)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_openpose_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_openpose_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (scribble; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (scribble)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_scribble_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_scribble_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (seg; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (seg)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_seg_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_seg_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (softedge; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (softedge)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15_softedge_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_softedge_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (anime; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (anime)", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11p_sd15s2_lineart_anime_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15s2_lineart_anime_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (tile; fp16; v11u)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (tile) / v11u", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11u_sd15_tile_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11u_sd15_tile_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-v1-1 (tile; fp16; v11f1e)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Safetensors/FP16 versions of the new ControlNet-v1-1 checkpoints (tile) / v11f1e\nYou need to this model for Tiled Resample", - "reference": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors", - "filename": "control_v11f1e_sd15_tile_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11f1e_sd15_tile_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "ControlNet-HandRefiner-pruned (inpaint-depth-hand; fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "This inpaint-depth controlnet model is specialized for the hand refiner.", - "reference": "https://huggingface.co/hr16/ControlNet-HandRefiner-pruned", - "filename": "control_sd15_inpaint_depth_hand_fp16.safetensors", - "url": "https://huggingface.co/hr16/ControlNet-HandRefiner-pruned/resolve/main/control_sd15_inpaint_depth_hand_fp16.safetensors", - "size": "722.6MB" - }, - { - "name": "control_boxdepth_LooseControlfp16 (fp16)", - "type": "controlnet", - "base": "SD1.5", - "save_path": "controlnet/1.5", - "description": "Loose ControlNet model", - "reference": "https://huggingface.co/ioclab/LooseControl_WebUICombine", - "filename": "control_boxdepth_LooseControlfp16.safetensors", - "url": "https://huggingface.co/ioclab/LooseControl_WebUICombine/resolve/main/control_boxdepth_LooseControlfp16.safetensors", - "size": "722.6MB" - }, - { - "name": "GLIGEN textbox (fp16; pruned)", - "type": "gligen", - "base": "SD1.5", - "save_path": "gligen/SD1.5", - "description": "GLIGEN textbox model", - "reference": "https://huggingface.co/comfyanonymous/GLIGEN_pruned_safetensors", - "filename": "gligen_sd14_textbox_pruned_fp16.safetensors", - "url": "https://huggingface.co/comfyanonymous/GLIGEN_pruned_safetensors/resolve/main/gligen_sd14_textbox_pruned_fp16.safetensors", - "size": "418.2MB" - }, - { - "name": "ViT-H SAM model", - "type": "sam", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM model (ViT-H)", - "reference": "https://github.com/facebookresearch/segment-anything#model-checkpoints", - "filename": "sam_vit_h_4b8939.pth", - "url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth", - "size": "2.56GB" - }, - { - "name": "ViT-L SAM model", - "type": "sam", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM model (ViT-L)", - "reference": "https://github.com/facebookresearch/segment-anything#model-checkpoints", - "filename": "sam_vit_l_0b3195.pth", - "url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth", - "size": "1.25GB" - }, - { - "name": "ViT-B SAM model", - "type": "sam", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM model (ViT-B)", - "reference": "https://github.com/facebookresearch/segment-anything#model-checkpoints", - "filename": "sam_vit_b_01ec64.pth", - "url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", - "size": "375.0MB" - }, - - { - "name": "sam2.1_hiera_tiny.pt", - "type": "sam2.1", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (tiny)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_tiny.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_tiny.pt", - "size": "149.0MB" - }, - { - "name": "sam2.1_hiera_small.pt", - "type": "sam2.1", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (small)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_small.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_small.pt", - "size": "176.0MB" - }, - { - "name": "sam2.1_hiera_base_plus.pt", - "type": "sam2.1", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (base+)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_base_plus.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_base_plus.pt", - "size": "309.0MB" - }, - { - "name": "sam2.1_hiera_large.pt", - "type": "sam2.1", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (large)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_large.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt", - "size": "857.0MB" - }, - - { - "name": "sam2_hiera_tiny.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (tiny)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_tiny.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt", - "size": "149.0MB" - }, - { - "name": "sam2_hiera_small.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (small)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_small.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_small.pt", - "size": "176.0MB" - }, - { - "name": "sam2_hiera_base_plus.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (base+)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_base_plus.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_base_plus.pt", - "size": "309.0MB" - }, - { - "name": "sam2_hiera_large.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (large)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_large.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt", - "size": "857.0MB" - }, - - { - "name": "seecoder v1.0", - "type": "seecoder", - "base": "SEECODER", - "save_path": "seecoders", - "description": "SeeCoder model", - "reference": "https://huggingface.co/shi-labs/prompt-free-diffusion/tree/main/pretrained/pfd/seecoder", - "filename": "seecoder-v1-0.safetensors", - "url": "https://huggingface.co/shi-labs/prompt-free-diffusion/resolve/main/pretrained/pfd/seecoder/seecoder-v1-0.safetensors", - "size": "1.18GB" - }, - { - "name": "seecoder pa v1.0", - "type": "seecoder", - "base": "SEECODER", - "save_path": "seecoders", - "description": "SeeCoder model", - "reference": "https://huggingface.co/shi-labs/prompt-free-diffusion/tree/main/pretrained/pfd/seecoder", - "filename": "seecoder-pa-v1-0.safetensors", - "url": "https://huggingface.co/shi-labs/prompt-free-diffusion/resolve/main/pretrained/pfd/seecoder/seecoder-pa-v1-0.safetensors", - "size": "1.19GB" - }, - { - "name": "seecoder anime v1.0", - "type": "seecoder", - "base": "SEECODER", - "save_path": "seecoders", - "description": "SeeCoder model", - "reference": "https://huggingface.co/shi-labs/prompt-free-diffusion/tree/main/pretrained/pfd/seecoder", - "filename": "seecoder-anime-v1-0.safetensors", - "url": "https://huggingface.co/shi-labs/prompt-free-diffusion/resolve/main/pretrained/pfd/seecoder/seecoder-anime-v1-0.safetensors", - "size": "1.18GB" - }, - { - "name": "face_yolov8m (bbox)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/bbox", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "face_yolov8m.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/face_yolov8m.pt", - "size": "52.0MB" - }, - { - "name": "face_yolov8n (bbox)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/bbox", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "face_yolov8n.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/face_yolov8n.pt", - "size": "6.23MB" - }, - { - "name": "face_yolov8n_v2 (bbox)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/bbox", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "face_yolov8n_v2.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/face_yolov8n_v2.pt", - "size": "6.24MB" - }, - { - "name": "face_yolov8s (bbox)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/bbox", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "face_yolov8s.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/face_yolov8s.pt", - "size": "22.5MB" - }, - { - "name": "hand_yolov8n (bbox)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/bbox", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "hand_yolov8n.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/hand_yolov8n.pt", - "size": "6.24MB" - }, - { - "name": "hand_yolov8s (bbox)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/bbox", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "hand_yolov8s.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/hand_yolov8s.pt", - "size": "22.5MB" - }, - { - "name": "person_yolov8m (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "person_yolov8m-seg.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/person_yolov8m-seg.pt", - "size": "54.8MB" - }, - { - "name": "person_yolov8n (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "person_yolov8n-seg.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/person_yolov8n-seg.pt", - "size": "6.78MB" - }, - { - "name": "person_yolov8s (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "person_yolov8s-seg.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/person_yolov8s-seg.pt", - "size": "23.9MB" - }, - { - "name": "deepfashion2_yolov8s (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://huggingface.co/Bingsu/adetailer/tree/main", - "filename": "deepfashion2_yolov8s-seg.pt", - "url": "https://huggingface.co/Bingsu/adetailer/resolve/main/deepfashion2_yolov8s-seg.pt", - "size": "23.9MB" - }, - { - "name": "face_yolov8m-seg_60.pt (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://github.com/hben35096/assets/releases/tag/yolo8", - "filename": "face_yolov8m-seg_60.pt", - "url": "https://github.com/hben35096/assets/releases/download/yolo8/face_yolov8m-seg_60.pt", - "size": "54.8MB" - }, - { - "name": "face_yolov8n-seg2_60.pt (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://github.com/hben35096/assets/releases/tag/yolo8", - "filename": "face_yolov8n-seg2_60.pt", - "url": "https://github.com/hben35096/assets/releases/download/yolo8/face_yolov8n-seg2_60.pt", - "size": "6.77MB" - }, - { - "name": "hair_yolov8n-seg_60.pt (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://github.com/hben35096/assets/releases/tag/yolo8", - "filename": "hair_yolov8n-seg_60.pt", - "url": "https://github.com/hben35096/assets/releases/download/yolo8/hair_yolov8n-seg_60.pt", - "size": "6.77MB" - }, - { - "name": "skin_yolov8m-seg_400.pt (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://github.com/hben35096/assets/releases/tag/yolo8", - "filename": "skin_yolov8m-seg_400.pt", - "url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8m-seg_400.pt", - "size": "54.9MB" - }, - { - "name": "skin_yolov8n-seg_400.pt (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://github.com/hben35096/assets/releases/tag/yolo8", - "filename": "skin_yolov8n-seg_400.pt", - "url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8n-seg_400.pt", - "size": "6.83MB" - }, - { - "name": "skin_yolov8n-seg_800.pt (segm)", - "type": "Ultralytics", - "base": "Ultralytics", - "save_path": "ultralytics/segm", - "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.", - "reference": "https://github.com/hben35096/assets/releases/tag/yolo8", - "filename": "skin_yolov8n-seg_800.pt", - "url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8n-seg_800.pt", - "size": "6.84MB" - }, - { - "name": "animatediff/mmd_sd_v14.ckpt (comfyui-animatediff) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "AnimateDiff", - "description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "mm_sd_v14.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v14.ckpt", - "size": "1.67GB" - }, - { - "name": "animatediff/mm_sd_v15.ckpt (comfyui-animatediff) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "AnimateDiff", - "description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "mm_sd_v15.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15.ckpt", - "size": "1.67GB" - }, - { - "name": "animatediff/mmd_sd_v14.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "mm_sd_v14.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v14.ckpt", - "size": "1.67GB" - }, - { - "name": "animatediff/mm_sd_v15.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "mm_sd_v15.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15.ckpt", - "size": "1.67GB" - }, - { - "name": "animatediff/mm_sd_v15_v2.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "mm_sd_v15_v2.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15_v2.ckpt", - "size": "1.82GB" - }, - { - "name": "animatediff/v3_sd15_mm.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v3_sd15_mm.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_mm.ckpt", - "size": "1.67GB" - }, - { - "name": "animatediff/mm_sdxl_v10_beta.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SDXL", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "mm_sdxl_v10_beta.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sdxl_v10_beta.ckpt", - "size": "950.1MB" - }, - { - "name": "AD_Stabilized_Motion/mm-Stabilized_high.pth (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion", - "filename": "mm-Stabilized_high.pth", - "url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_high.pth", - "size": "1.67GB" - }, - { - "name": "AD_Stabilized_Motion/mm-Stabilized_mid.pth (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion", - "filename": "mm-Stabilized_mid.pth", - "url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_mid.pth", - "size": "1.67GB" - }, - { - "name": "CiaraRowles/temporaldiff-v1-animatediff.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/CiaraRowles/TemporalDiff", - "filename": "temporaldiff-v1-animatediff.ckpt", - "url": "https://huggingface.co/CiaraRowles/TemporalDiff/resolve/main/temporaldiff-v1-animatediff.ckpt", - "size": "1.67GB" - }, - { - "name": "Leoxing/pia.ckpt", - "type": "animatediff-pia", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "AnimateDiff-PIA Model", - "reference": "https://huggingface.co/Leoxing/PIA/tree/main", - "filename": "pia.ckpt", - "url": "https://huggingface.co/Leoxing/PIA/resolve/main/pia.ckpt", - "size": "1.67GB" - }, - - { - "name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_PanLeft.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanLeft.ckpt", - "size": "77.5MB" - }, - { - "name": "animatediff/v2_lora_PanRight.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_PanRight.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanRight.ckpt", - "size": "77.5MB" - }, - { - "name": "animatediff/v2_lora_RollingAnticlockwise.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_RollingAnticlockwise.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingAnticlockwise.ckpt", - "size": "77.5MB" - }, - { - "name": "animatediff/v2_lora_RollingClockwise.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_RollingClockwise.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingClockwise.ckpt", - "size": "77.5MB" - }, - { - "name": "animatediff/v2_lora_TiltDown.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_TiltDown.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltDown.ckpt", - "size": "77.5MB" - }, - { - "name": "animatediff/v2_lora_TiltUp.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_TiltUp.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltUp.ckpt", - "size": "77.5MB" - }, - { - "name": "animatediff/v2_lora_ZoomIn.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_ZoomIn.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomIn.ckpt", - "size": "77.5MB" - }, - { - "name": "animatediff/v2_lora_ZoomOut.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "motion lora", - "base": "SD1.x", - "save_path": "animatediff_motion_lora", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v2_lora_ZoomOut.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomOut.ckpt", - "size": "77.5MB" - }, - { - "name": "LongAnimatediff/lt_long_mm_32_frames.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/Lightricks/LongAnimateDiff", - "filename": "lt_long_mm_32_frames.ckpt", - "url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_32_frames.ckpt", - "size": "1.82GB" - }, - { - "name": "LongAnimatediff/lt_long_mm_16_64_frames.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/Lightricks/LongAnimateDiff", - "filename": "lt_long_mm_16_64_frames.ckpt", - "url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames.ckpt", - "size": "1.83GB" - }, - { - "name": "LongAnimatediff/lt_long_mm_16_64_frames_v1.1.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)", - "type": "animatediff", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.", - "reference": "https://huggingface.co/Lightricks/LongAnimateDiff", - "filename": "lt_long_mm_16_64_frames_v1.1.ckpt", - "url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames_v1.1.ckpt", - "size": "1.83GB" - }, - { - "name": "animatediff/v3_sd15_sparsectrl_rgb.ckpt (ComfyUI-AnimateDiff-Evolved)", - "type": "controlnet", - "base": "SD1.x", - "save_path": "controlnet/SD1.5/animatediff", - "description": "AnimateDiff SparseCtrl RGB ControlNet model", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v3_sd15_sparsectrl_rgb.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_sparsectrl_rgb.ckpt", - "size": "1.99GB" - }, - { - "name": "animatediff/v3_sd15_sparsectrl_scribble.ckpt", - "type": "controlnet", - "base": "SD1.x", - "save_path": "controlnet/SD1.5/animatediff", - "description": "AnimateDiff SparseCtrl Scribble ControlNet model", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v3_sd15_sparsectrl_scribble.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_sparsectrl_scribble.ckpt", - "size": "1.99GB" - }, - { - "name": "animatediff/v3_sd15_adapter.ckpt", - "type": "lora", - "base": "SD1.x", - "save_path": "loras/SD1.5/animatediff", - "description": "AnimateDiff Adapter LoRA (SD1.5)", - "reference": "https://huggingface.co/guoyww/animatediff", - "filename": "v3_sd15_adapter.ckpt", - "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_adapter.ckpt", - "size": "102.1MB" - }, - { - "name": "TencentARC/motionctrl.pth", - "type": "checkpoint", - "base": "MotionCtrl", - "save_path": "checkpoints/motionctrl", - "description": "To use the ComfyUI-MotionCtrl extension, downloading this model is required.", - "reference": "https://huggingface.co/TencentARC/MotionCtrl", - "filename": "motionctrl.pth", - "url": "https://huggingface.co/TencentARC/MotionCtrl/resolve/main/motionctrl.pth", - "size": "4.02GB" - }, - { - "name": "ip-adapter_sd15.safetensors", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter_sd15.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15.safetensors", - "size": "44.6MB" - }, - { - "name": "ip-adapter_sd15_light_v11.bin", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter_sd15_light_v11.bin", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_light_v11.bin", - "size": "44.6MB" - }, - { - "name": "ip-adapter_sd15_light.safetensors [DEPRECATED]", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter_sd15_light.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_light.safetensors", - "size": "44.6MB" - }, - { - "name": "ip-adapter-plus_sd15.safetensors", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter-plus_sd15.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus_sd15.safetensors", - "size": "98.2MB" - }, - { - "name": "ip-adapter-plus-face_sd15.safetensors", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter-plus-face_sd15.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus-face_sd15.safetensors", - "size": "98.2MB" - }, - { - "name": "ip-adapter-full-face_sd15.safetensors", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter-full-face_sd15.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-full-face_sd15.safetensors", - "size": "43.6MB" - }, - { - "name": "ip-adapter_sd15_vit-G.safetensors", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter_sd15_vit-G.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_vit-G.safetensors", - "size": "46.2MB" - }, - { - "name": "ip-adapter-faceid_sd15.bin", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid_sd15.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15.bin", - "size": "96.7MB" - }, - { - "name": "ip-adapter-faceid-plusv2_sd15.bin", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Plus V2 Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-plusv2_sd15.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15.bin", - "size": "156.6MB" - }, - { - "name": "ip-adapter-faceid-plus_sd15.bin [DEPRECATED]", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Plus Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-plus_sd15.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15.bin", - "size": "156.6MB" - }, - { - "name": "ip-adapter-faceid-portrait-v11_sd15.bin", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Portrait V11 Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-portrait-v11_sd15.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait-v11_sd15.bin", - "size": "64.6MB" - }, - { - "name": "ip-adapter-faceid-portrait_sd15.bin [DEPRECATED]", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Portrait Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-portrait_sd15.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait_sd15.bin", - "size": "64.6MB" - }, - { - "name": "ip-adapter-faceid_sdxl.bin", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Model (SDXL) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid_sdxl.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sdxl.bin", - "size": "1.07GB" - }, - { - "name": "ip-adapter-faceid-plusv2_sdxl.bin", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Plus Model (SDXL) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-plusv2_sdxl.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sdxl.bin", - "size": "1.49GB" - }, - { - "name": "ip-adapter-faceid-portrait_sdxl.bin", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Portrait Model (SDXL) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-portrait_sdxl.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait_sdxl.bin", - "size": "749.8MB" - }, - { - "name": "ip-adapter-faceid-portrait_sdxl_unnorm.bin", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "IP-Adapter-FaceID Portrait Model (SDXL/unnorm) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-portrait_sdxl_unnorm.bin", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait_sdxl_unnorm.bin", - "size": "1.01GB" - }, - { - "name": "ip-adapter-faceid_sd15_lora.safetensors", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/ipadapter", - "description": "IP-Adapter-FaceID LoRA Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid_sd15_lora.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15_lora.safetensors", - "size": "51.1MB" - }, - { - "name": "ip-adapter-faceid-plus_sd15_lora.safetensors [DEPRECATED]", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/ipadapter", - "description": "IP-Adapter-FaceID Plus LoRA Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-plus_sd15_lora.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15_lora.safetensors", - "size": "51.1MB" - }, - { - "name": "ip-adapter-faceid-plusv2_sd15_lora.safetensors", - "type": "lora", - "base": "SD1.5", - "save_path": "loras/ipadapter", - "description": "IP-Adapter-FaceID-Plus V2 LoRA Model (SD1.5) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-plusv2_sd15_lora.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15_lora.safetensors", - "size": "51.1MB" - }, - { - "name": "ip-adapter-faceid_sdxl_lora.safetensors", - "type": "lora", - "base": "SDXL", - "save_path": "loras/ipadapter", - "description": "IP-Adapter-FaceID LoRA Model (SDXL) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid_sdxl_lora.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sdxl_lora.safetensors", - "size": "371.8MB" - }, - { - "name": "ip-adapter-faceid-plusv2_sdxl_lora.safetensors", - "type": "lora", - "base": "SDXL", - "save_path": "loras/ipadapter", - "description": "IP-Adapter-FaceID-Plus V2 LoRA Model (SDXL) [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter-FaceID", - "filename": "ip-adapter-faceid-plusv2_sdxl_lora.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sdxl_lora.safetensors", - "size": "371.8MB" - }, - { - "name": "ip-adapter_sdxl.safetensors", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter_sdxl.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl.safetensors", - "size": "702.6MB" - }, - { - "name": "ip-adapter_sdxl_vit-h.safetensors", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "This model requires the use of the SD1.5 encoder despite being for SDXL checkpoints [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter_sdxl_vit-h.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl_vit-h.safetensors", - "size": "698.4MB" - }, - { - "name": "ip-adapter-plus_sdxl_vit-h.safetensors", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "This model requires the use of the SD1.5 encoder despite being for SDXL checkpoints [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter-plus_sdxl_vit-h.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus_sdxl_vit-h.safetensors", - "size": "847.5MB" - }, - { - "name": "ip-adapter-plus-face_sdxl_vit-h.safetensors", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "This model requires the use of the SD1.5 encoder despite being for SDXL checkpoints [ipadapter]", - "reference": "https://huggingface.co/h94/IP-Adapter", - "filename": "ip-adapter-plus-face_sdxl_vit-h.safetensors", - "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus-face_sdxl_vit-h.safetensors", - "size": "847.5MB" - }, - { - "name": "ip_plus_composition_sd15.safetensors", - "type": "IP-Adapter", - "base": "SD1.5", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/ostris/ip-composition-adapter", - "filename": "ip_plus_composition_sd15.safetensors", - "url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sd15.safetensors", - "size": "98.2MB" - }, - { - "name": "ip_plus_composition_sdxl.safetensors", - "type": "IP-Adapter", - "base": "SDXL", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/ostris/ip-composition-adapter", - "filename": "ip_plus_composition_sdxl.safetensors", - "url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sdxl.safetensors", - "size": "847.5MB" - }, - { - "name": "Kolors-IP-Adapter-Plus.bin (Kwai-Kolors/Kolors-IP-Adapter-Plus)", - "type": "IP-Adapter", - "base": "Kolors", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus", - "filename": "Kolors-IP-Adapter-Plus.bin", - "url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/ip_adapter_plus_general.bin", - "size": "1.01GB" - }, - { - "name": "Kolors-IP-Adapter-FaceID-Plus.bin (Kwai-Kolors/Kolors-IP-Adapter-Plus)", - "type": "IP-Adapter", - "base": "Kolors", - "save_path": "ipadapter", - "description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-FaceID-Plus", - "filename": "Kolors-IP-Adapter-FaceID-Plus.bin", - "url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-FaceID-Plus/resolve/main/ipa-faceid-plus.bin", - "size": "2.39GB" - }, - - - - { - "name": "GFPGANv1.4.pth", - "type": "GFPGAN", - "base": "GFPGAN", - "save_path": "facerestore_models", - "description": "Face Restoration Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.", - "reference": "https://github.com/TencentARC/GFPGAN/releases", - "filename": "GFPGANv1.4.pth", - "url": "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth", - "size": "348.6MB" - }, - { - "name": "codeformer.pth", - "type": "CodeFormer", - "base": "CodeFormer", - "save_path": "facerestore_models", - "description": "Face Restoration Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.", - "reference": "https://github.com/sczhou/CodeFormer/releases", - "filename": "codeformer.pth", - "url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth", - "size": "376.6MB" - }, - { - "name": "detection_Resnet50_Final.pth", - "type": "facexlib", - "base": "facexlib", - "save_path": "facerestore_models", - "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.", - "reference": "https://github.com/xinntao/facexlib", - "filename": "detection_Resnet50_Final.pth", - "url": "https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth", - "size": "109.5MB" - }, - { - "name": "detection_mobilenet0.25_Final.pth", - "type": "facexlib", - "base": "facexlib", - "save_path": "facerestore_models", - "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.", - "reference": "https://github.com/xinntao/facexlib", - "filename": "detection_mobilenet0.25_Final.pth", - "url": "https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_mobilenet0.25_Final.pth", - "size": "1.79MB" - }, - { - "name": "yolov5l-face.pth", - "type": "facexlib", - "base": "facexlib", - "save_path": "facedetection", - "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.", - "reference": "https://github.com/xinntao/facexlib", - "filename": "yolov5l-face.pth", - "url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5l-face.pth", - "size": "187.0MB" - }, - { - "name": "yolov5n-face.pth", - "type": "facexlib", - "base": "facexlib", - "save_path": "facedetection", - "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.", - "reference": "https://github.com/xinntao/facexlib", - "filename": "yolov5n-face.pth", - "url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5n-face.pth", - "size": "7.15MB" - }, - { - "name": "photomaker-v1.bin", - "type": "photomaker", - "base": "SDXL", - "save_path": "photomaker", - "description": "PhotoMaker model. This model is compatible with SDXL.", - "reference": "https://huggingface.co/TencentARC/PhotoMaker", - "filename": "photomaker-v1.bin", - "url": "https://huggingface.co/TencentARC/PhotoMaker/resolve/main/photomaker-v1.bin", - "size": "934.1MB" - }, - { - "name": "photomaker-v2.bin", - "type": "photomaker", - "base": "SDXL", - "save_path": "photomaker", - "description": "PhotoMaker model. This model is compatible with SDXL.", - "reference": "https://huggingface.co/TencentARC/PhotoMaker-V2", - "filename": "photomaker-v2.bin", - "url": "https://huggingface.co/TencentARC/PhotoMaker-V2/resolve/main/photomaker-v2.bin", - "size": "1.8GB" - }, - { - "name": "1k3d68.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/antelopev2", - "description": "Antelopev2 1k3d68.onnx model for InstantId. (InstantId needs all Antelopev2 models)", - "reference": "https://github.com/cubiq/ComfyUI_InstantID#installation", - "filename": "1k3d68.onnx", - "url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/1k3d68.onnx", - "size": "143.6MB" - }, - { - "name": "2d106det.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/antelopev2", - "description": "Antelopev2 2d106det.onnx model for InstantId. (InstantId needs all Antelopev2 models)", - "reference": "https://github.com/cubiq/ComfyUI_InstantID#installation", - "filename": "2d106det.onnx", - "url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/2d106det.onnx", - "size": "5.03MB" - }, - { - "name": "genderage.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/antelopev2", - "description": "Antelopev2 genderage.onnx model for InstantId. (InstantId needs all Antelopev2 models)", - "reference": "https://github.com/cubiq/ComfyUI_InstantID#installation", - "filename": "genderage.onnx", - "url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/genderage.onnx", - "size": "1.32MB" - }, - { - "name": "glintr100.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/antelopev2", - "description": "Antelopev2 glintr100.onnx model for InstantId. (InstantId needs all Antelopev2 models)", - "reference": "https://github.com/cubiq/ComfyUI_InstantID#installation", - "filename": "glintr100.onnx", - "url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/glintr100.onnx", - "size": "260.7MB" - }, - { - "name": "scrfd_10g_bnkps.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/antelopev2", - "description": "Antelopev2 scrfd_10g_bnkps.onnx model for InstantId. (InstantId needs all Antelopev2 models)", - "reference": "https://github.com/cubiq/ComfyUI_InstantID#installation", - "filename": "scrfd_10g_bnkps.onnx", - "url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/scrfd_10g_bnkps.onnx", - "size": "16.9MB" - }, - { - "name": "ip-adapter.bin", - "type": "instantid", - "base": "SDXL", - "save_path": "instantid", - "description": "InstantId main model based on IpAdapter", - "reference": "https://huggingface.co/InstantX/InstantID", - "filename": "ip-adapter.bin", - "url": "https://huggingface.co/InstantX/InstantID/resolve/main/ip-adapter.bin", - "size": "1.69GB" - }, - { - "name": "diffusion_pytorch_model.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/instantid", - "description": "InstantId controlnet model", - "reference": "https://huggingface.co/InstantX/InstantID", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/InstantX/InstantID/resolve/main/ControlNetModel/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - { - "name": "InstanceDiffusion/fusers", - "type": "InstanceDiffusion", - "base": "SD1.5", - "save_path": "instance_models/fuser_models", - "description": "Fusers checkpoints for multi-object prompting with InstanceDiffusion.", - "reference": "https://huggingface.co/logtd/instance_diffusion", - "filename": "fusers.ckpt", - "url": "https://huggingface.co/logtd/instance_diffusion/resolve/main/fusers.ckpt", - "size": "832.1MB" - }, - { - "name": "InstanceDiffusion/position_net", - "type": "InstanceDiffusion", - "base": "SD1.5", - "save_path": "instance_models/positionnet_models", - "description": "PositionNet checkpoints for multi-object prompting with InstanceDiffusion.", - "reference": "https://huggingface.co/logtd/instance_diffusion", - "filename": "position_net.ckpt", - "url": "https://huggingface.co/logtd/instance_diffusion/resolve/main/position_net.ckpt", - "size": "643.2MB" - }, - { - "name": "InstanceDiffusion/scaleu", - "type": "InstanceDiffusion", - "base": "SD1.5", - "save_path": "instance_models/scaleu_models", - "description": "ScaleU checkpoints for multi-object prompting with InstanceDiffusion.", - "reference": "https://huggingface.co/logtd/instance_diffusion", - "filename": "scaleu.ckpt", - "url": "https://huggingface.co/logtd/instance_diffusion/resolve/main/scaleu.ckpt", - "size": "53.1KB" - }, - { - "name": "1k3d68.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/buffalo_l", - "description": "Buffalo_l 1k3d68.onnx model for IpAdapterPlus", - "reference": "https://github.com/cubiq/ComfyUI_IPAdapter_plus?tab=readme-ov-file#faceid", - "filename": "1k3d68.onnx", - "url": "https://huggingface.co/public-data/insightface/resolve/main/models/buffalo_l/1k3d68.onnx", - "size": "143.6MB" - }, - { - "name": "2d106det.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/buffalo_l", - "description": "Buffalo_l 2d106det.onnx model for IpAdapterPlus", - "reference": "https://github.com/cubiq/ComfyUI_IPAdapter_plus?tab=readme-ov-file#faceid", - "filename": "2d106det.onnx", - "url": "https://huggingface.co/public-data/insightface/resolve/main/models/buffalo_l/2d106det.onnx", - "size": "5.03MB" - }, - { - "name": "det_10g.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/buffalo_l", - "description": "Buffalo_l det_10g.onnx model for IpAdapterPlus", - "reference": "https://github.com/cubiq/ComfyUI_IPAdapter_plus?tab=readme-ov-file#faceid", - "filename": "det_10g.onnx", - "url": "https://huggingface.co/public-data/insightface/resolve/main/models/buffalo_l/det_10g.onnx", - "size": "16.9MB" - }, - { - "name": "genderage.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/buffalo_l", - "description": "Buffalo_l genderage.onnx model for IpAdapterPlus", - "reference": "https://github.com/cubiq/ComfyUI_IPAdapter_plus?tab=readme-ov-file#faceid", - "filename": "genderage.onnx", - "url": "https://huggingface.co/public-data/insightface/resolve/main/models/buffalo_l/genderage.onnx", - "size": "1.32MB" - }, - { - "name": "w600k_r50.onnx", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface/models/buffalo_l", - "description": "Buffalo_l w600k_r50.onnx model for IpAdapterPlus", - "reference": "https://github.com/cubiq/ComfyUI_IPAdapter_plus?tab=readme-ov-file#faceid", - "filename": "w600k_r50.onnx", - "url": "https://huggingface.co/public-data/insightface/resolve/main/models/buffalo_l/w600k_r50.onnx", - "size": "174.4MB" - }, - { - "name": "BLIP ImageCaption (COCO) w/ ViT-B and CapFilt-L", - "type": "BLIP_MODEL", - "base": "blip_model", - "save_path": "blip", - "description": "BLIP ImageCaption (COCO) w/ ViT-B and CapFilt-L", - "reference": "https://github.com/salesforce/BLIP", - "filename": "model_base_capfilt_large.pth", - "url": "https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_capfilt_large.pth", - "size": "2.12GB" - }, - { - "name": "GroundingDINO SwinT OGC - Model", - "type": "GroundingDINO", - "base": "DINO", - "save_path": "groundingdino", - "description": "GroundingDINO SwinT OGC Model", - "reference": "https://huggingface.co/ShilongLiu/GroundingDINO", - "filename": "groundingdino_swint_ogc.pth", - "url": "https://huggingface.co/ShilongLiu/GroundingDINO/resolve/main/groundingdino_swint_ogc.pth", - "size": "694.0MB" - }, - { - "name": "GroundingDINO SwinT OGC - CFG File", - "type": "GroundingDINO", - "base": "DINO", - "save_path": "groundingdino", - "description": "GroundingDINO SwinT OGC CFG File", - "reference": "https://huggingface.co/ShilongLiu/GroundingDINO/resolve/main/GroundingDINO_SwinT_OGC.cfg.py", - "filename": "GroundingDINO_SwinT_OGC.cfg.py", - "url": "https://huggingface.co/ShilongLiu/GroundingDINO/raw/main/GroundingDINO_SwinT_OGC.cfg.py", - "size": "1.01KB" - }, - { - "name": "MobileSAM", - "type": "sam", - "base": "SAM", - "save_path": "sams", - "description": "MobileSAM", - "reference": "https://github.com/ChaoningZhang/MobileSAM/", - "filename": "mobile_sam.pt", - "url": "https://github.com/ChaoningZhang/MobileSAM/blob/master/weights/mobile_sam.pt", - "size": "38.8MB" - }, - { - "name": "DynamiCrafter 1024 bf16 safetensors", - "type": "checkpoint", - "base": "DynamiCrafter", - "save_path": "checkpoints/dynamicrafter", - "description": "DynamiCrafter image2video model 1024x575", - "reference": "https://huggingface.co/Kijai/DynamiCrafter_pruned/", - "filename": "dynamicrafter_1024_v1_bf16.safetensors", - "url": "https://huggingface.co/Kijai/DynamiCrafter_pruned/resolve/main/dynamicrafter_1024_v1_bf16.safetensors", - "size": "5.22GB" - }, - { - "name": "DynamiCrafter 512 interpolation bf16 safetensors", - "type": "checkpoint", - "base": "DynamiCrafter", - "save_path": "checkpoints/dynamicrafter", - "description": "DynamiCrafter image2video interpolation model 512", - "reference": "https://huggingface.co/Kijai/DynamiCrafter_pruned/", - "filename": "dynamicrafter_512_interp_v1_bf16.safetensors", - "url": "https://huggingface.co/Kijai/DynamiCrafter_pruned/resolve/main/dynamicrafter_512_interp_v1_bf16.safetensors", - "size": "5.22GB" - }, - { - "name": "monster-labs - Controlnet QR Code Monster v1 For SDXL", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "monster-labs - Controlnet QR Code Monster v1 For SDXL", - "reference": "https://huggingface.co/monster-labs/control_v1p_sdxl_qrcode_monster", - "filename": "control_v1p_sdxl_qrcode_monster.safetensors", - "url": "https://huggingface.co/monster-labs/control_v1p_sdxl_qrcode_monster/resolve/main/diffusion_pytorch_model.safetensors", - "size": "5.00GB" - }, - { - "name": "Depth-FM-v1 fp16 safetensors", - "type": "checkpoint", - "base": "Depth-FM", - "save_path": "checkpoints/depthfm", - "description": "Depth-FM monocular depth estimation model", - "reference": "https://huggingface.co/Kijai/depth-fm-pruned", - "filename": "depthfm-v1_fp16.safetensors", - "url": "https://huggingface.co/Kijai/depth-fm-pruned/resolve/main/depthfm-v1_fp16.safetensors", - "size": "1.73GB" - }, - { - "name": "Depth-FM-v1 fp32 safetensors", - "type": "checkpoint", - "base": "Depth-FM", - "save_path": "checkpoints/depthfm", - "description": "Depth-FM monocular depth estimation model", - "reference": "https://huggingface.co/Kijai/depth-fm-pruned", - "filename": "depthfm-v1_fp32.safetensors", - "url": "https://huggingface.co/Kijai/depth-fm-pruned/resolve/main/depthfm-v1_fp32.safetensors", - "size": "3.46GB" - }, - { - "name": "SUPIR-v0F.ckpt", - "type": "checkpoint", - "base": "SUPIR", - "save_path": "checkpoints/SUPIR", - "description": "SUPIR checkpoint model", - "reference": "https://huggingface.co/camenduru/SUPIR/tree/main", - "filename": "SUPIR-v0F.ckpt", - "url": "https://huggingface.co/camenduru/SUPIR/resolve/main/SUPIR-v0F.ckpt", - "size": "5.33GB" - }, - { - "name": "SUPIR-v0Q.ckpt", - "type": "checkpoint", - "base": "SUPIR", - "save_path": "checkpoints/SUPIR", - "description": "SUPIR checkpoint model", - "reference": "https://huggingface.co/camenduru/SUPIR/tree/main", - "filename": "SUPIR-v0Q.ckpt", - "url": "https://huggingface.co/camenduru/SUPIR/resolve/main/SUPIR-v0Q.ckpt", - "size": "5.33GB" - }, - { - "name": "Kijai/SUPIR-v0F_fp16.safetensors (pruned)", - "type": "checkpoint", - "base": "SUPIR", - "save_path": "checkpoints/SUPIR", - "description": "SUPIR checkpoint model", - "reference": "https://huggingface.co/Kijai/SUPIR_pruned/tree/main", - "filename": "SUPIR-v0F_fp16.safetensors", - "url": "https://huggingface.co/Kijai/SUPIR_pruned/resolve/main/SUPIR-v0F_fp16.safetensors", - "size": "2.66GB" - }, - { - "name": "Kijai/SUPIR-v0Q_fp16.safetensors (pruned)", - "type": "checkpoint", - "base": "SUPIR", - "save_path": "checkpoints/SUPIR", - "description": "SUPIR checkpoint model", - "reference": "https://huggingface.co/Kijai/SUPIR_pruned/tree/main", - "filename": "SUPIR-v0Q_fp16.safetensors", - "url": "https://huggingface.co/Kijai/SUPIR_pruned/resolve/main/SUPIR-v0Q_fp16.safetensors", - "size": "2.66GB" - }, - { - "name": "RAM", - "type": "RAM", - "base": "RAM", - "save_path": "rams", - "description": "RAM Recognize Anything Model", - "reference": "https://huggingface.co/xinyu1205/recognize_anything_model", - "filename": "ram_swin_large_14m.pth", - "url": "https://huggingface.co/xinyu1205/recognize_anything_model/resolve/main/ram_swin_large_14m.pth", - "size": "5.63GB" - }, - { - "name": "RAM++", - "type": "RAM", - "base": "RAM", - "save_path": "rams", - "description": "RAM++ Recognize Anything Model", - "reference": "https://huggingface.co/xinyu1205/recognize-anything-plus-model", - "filename": "ram_plus_swin_large_14m.pth", - "url": "https://huggingface.co/xinyu1205/recognize-anything-plus-model/resolve/main/ram_plus_swin_large_14m.pth", - "size": "3.01GB" - }, - { - "name": "tag2text", - "type": "RAM", - "base": "RAM", - "save_path": "rams", - "description": "tag2text Recognize Anything Model", - "reference": "https://huggingface.co/xinyu1205/recognize_anything_model", - "filename": "tag2text_swin_14m.pth", - "url": "https://huggingface.co/xinyu1205/recognize_anything_model/resolve/main/tag2text_swin_14m.pth", - "size": "4.48GB" - }, - { - "name": "Zero123 3D object Model", - "type": "zero123", - "base": "zero123", - "save_path": "checkpoints/zero123", - "description": "model that been trained on 10M+ 3D objects from Objaverse-XL, used for generated rotated CamView", - "reference": "https://objaverse.allenai.org/docs/zero123-xl/", - "filename": "zero123-xl.ckpt", - "url": "https://huggingface.co/kealiu/zero123-xl/resolve/main/zero123-xl.ckpt", - "size": "15.5GB" - }, - { - "name": "Zero123 3D object Model", - "type": "zero123", - "base": "zero123", - "save_path": "checkpoints/zero123", - "description": "Stable Zero123 is a model for view-conditioned image generation based on [a/Zero123](https://github.com/cvlab-columbia/zero123).", - "reference": "https://huggingface.co/stabilityai/stable-zero123", - "filename": "stable_zero123.ckpt", - "url": "https://huggingface.co/stabilityai/stable-zero123/resolve/main/stable_zero123.ckpt", - "size": "8.58GB" - }, - { - "name": "Zero123 3D object Model", - "type": "zero123", - "base": "zero123", - "save_path": "checkpoints/zero123", - "description": "Zero123 original checkpoints in 105000 steps.", - "reference": "https://huggingface.co/cvlab/zero123-weights", - "filename": "zero123-105000.ckpt", - "url": "https://huggingface.co/cvlab/zero123-weights/resolve/main/105000.ckpt", - "size": "15.5GB" - }, - { - "name": "Zero123 3D object Model", - "type": "zero123", - "base": "zero123", - "save_path": "checkpoints/zero123", - "description": "Zero123 original checkpoints in 165000 steps.", - "reference": "https://huggingface.co/cvlab/zero123-weights", - "filename": "zero123-165000.ckpt", - "url": "https://huggingface.co/cvlab/zero123-weights/resolve/main/165000.ckpt", - "size": "15.5GB" - }, - { - "name": "InstantID/ip-adapter", - "type": "instantid", - "base": "SDXL", - "save_path": "instantid/SDXL", - "description": "ip-adapter model for cubiq/InstantID", - "reference": "https://huggingface.co/InstantX/InstantID", - "filename": "ip-adapter.bin", - "url": "https://huggingface.co/InstantX/InstantID/resolve/main/ip-adapter.bin", - "size": "1.69GB" - }, - { - "name": "InstantID/ControlNet", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/instantid", - "description": "instantid controlnet model for cubiq/InstantID", - "reference": "https://huggingface.co/InstantX/InstantID", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/InstantX/InstantID/resolve/main/ControlNetModel/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - { - "name": "MonsterMMORPG/insightface (for InstantID)", - "type": "insightface", - "base": "SDXL", - "save_path": "insightface/models", - "description": "MonsterMMORPG insightface model for cubiq/InstantID", - "reference": "https://huggingface.co/MonsterMMORPG/tools/tree/main", - "filename": "antelopev2.zip", - "url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/antelopev2.zip", - "size": "360.7MB" - }, - { - "name": "IC-Light/fc", - "type": "IC-Light", - "base": "SD1.5", - "save_path": "diffusion_models/IC-Light", - "description": "The default relighting model, conditioned on text and foreground", - "reference": "https://huggingface.co/lllyasviel/ic-light", - "filename": "iclight_sd15_fc.safetensors", - "url": "https://huggingface.co/lllyasviel/ic-light/resolve/main/iclight_sd15_fc.safetensors", - "size": "1.72GB" - }, - { - "name": "IC-Light/fbc", - "type": "IC-Light", - "base": "SD1.5", - "save_path": "diffusion_models/IC-Light", - "description": "Relighting model conditioned with text, foreground, and background", - "reference": "https://huggingface.co/lllyasviel/ic-light", - "filename": "iclight_sd15_fbc.safetensors", - "url": "https://huggingface.co/lllyasviel/ic-light/resolve/main/iclight_sd15_fbc.safetensors", - "size": "1.72GB" - }, - { - "name": "IC-Light/fcon", - "type": "IC-Light", - "base": "SD1.5", - "save_path": "diffusion_models/IC-Light", - "description": "Same as iclight_sd15_fc.safetensors, but trained with offset noise", - "reference": "https://huggingface.co/lllyasviel/ic-light", - "filename": "iclight_sd15_fcon.safetensors", - "url": "https://huggingface.co/lllyasviel/ic-light/resolve/main/iclight_sd15_fcon.safetensors", - "size": "1.72GB" - }, - { - "name": "TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic v2 (fp16)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Controlnet SDXL Tile model realistic version.", - "reference": "https://huggingface.co/TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic", - "filename": "TTPLANET_Controlnet_Tile_realistic_v2_fp16.safetensors", - "url": "https://huggingface.co/TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic/resolve/main/TTPLANET_Controlnet_Tile_realistic_v2_fp16.safetensors", - "size": "2.50GB" - }, - { - "name": "TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic v2 (rank256)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "Controlnet SDXL Tile model realistic version.", - "reference": "https://huggingface.co/TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic", - "filename": "TTPLANET_Controlnet_Tile_realistic_v2_rank256.safetensors", - "url": "https://huggingface.co/TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic/resolve/main/TTPLANET_Controlnet_Tile_realistic_v2_rank256.safetensors", - "size": "774.4MB" - }, - { - "name": "ViperYX/RGT_x2.pth", - "type": "RGT", - "base": "RGT", - "save_path": "RGT/RGT", - "description": "RGT x2 upscale model for ComfyUI-RGT", - "reference": "https://huggingface.co/ViperYX/RGT/tree/main", - "filename": "RGT_x2.pth", - "url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT/RGT_x2.pth", - "size": "179.8MB" - }, - { - "name": "ViperYX/RGT_x3.pth", - "type": "RGT", - "base": "RGT", - "save_path": "RGT/RGT", - "description": "RGT x3 upscale model for ComfyUI-RGT", - "reference": "https://huggingface.co/ViperYX/RGT/tree/main", - "filename": "RGT_x3.pth", - "url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT/RGT_x3.pth", - "size": "180.5MB" - }, - { - "name": "ViperYX/RGT_x4.pth", - "type": "RGT", - "base": "RGT", - "save_path": "RGT/RGT", - "description": "RGT_S x4 upscale model for ComfyUI-RGT", - "reference": "https://huggingface.co/ViperYX/RGT/tree/main", - "filename": "RGT_x4.pth", - "url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT/RGT_x4.pth", - "size": "180.4MB" - }, - { - "name": "ViperYX/RGT_S_x2.pth", - "type": "RGT", - "base": "RGT", - "save_path": "RGT/RGT_S", - "description": "RGT_S x2 upscale model for ComfyUI-RGT", - "reference": "https://huggingface.co/ViperYX/RGT/tree/main", - "filename": "RGT_S_x2.pth", - "url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT_S/RGT_S_x2.pth", - "size": "135.4MB" - }, - { - "name": "ViperYX/RGT_S_x3.pth", - "type": "RGT", - "base": "RGT", - "save_path": "RGT/RGT_S", - "description": "RGT_S x3 upscale model for ComfyUI-RGT", - "reference": "https://huggingface.co/ViperYX/RGT/tree/main", - "filename": "RGT_S_x3.pth", - "url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT_S/RGT_S_x3.pth", - "size": "136.1MB" - }, - { - "name": "ViperYX/RGT_S_x4.pth", - "type": "RGT", - "base": "RGT", - "save_path": "RGT/RGT_S", - "description": "RGT_S x4 upscale model for ComfyUI-RGT", - "reference": "https://huggingface.co/ViperYX/RGT/tree/main", - "filename": "RGT_S_x4.pth", - "url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT_S/RGT_S_x4.pth", - "size": "136.0MB" - }, - - { - "name": "InstantX/FLUX.1-dev Controlnet (Union)", - "type": "controlnet", - "base": "FLUX.1", - "save_path": "controlnet/FLUX.1/InstantX-FLUX1-Dev-Union", - "description": "FLUX.1 [Dev] Union Controlnet. Supports Canny, Depth, Pose, Tile, Blur, Gray Low Quality.", - "reference": "https://huggingface.co/InstantX/FLUX.1-dev-Controlnet-Union", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/InstantX/FLUX.1-dev-Controlnet-Union/resolve/main/diffusion_pytorch_model.safetensors", - "size": "6.6GB" - }, - - - { - "name": "InstantX/FLUX.1-dev-IP-Adapter", - "type": "IP-Adapter", - "base": "FLUX.1", - "save_path": "ipadapter-flux", - "description": "FLUX.1-dev-IP-Adapter", - "reference": "https://huggingface.co/InstantX/FLUX.1-dev-IP-Adapter", - "filename": "ip-adapter.bin", - "url": "https://huggingface.co/InstantX/FLUX.1-dev-IP-Adapter/resolve/main/ip-adapter.bin", - "size": "5.29GB" - }, - { - "name": "Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro", - "type": "controlnet", - "base": "FLUX.1", - "save_path": "controlnet/FLUX.1/Shakker-Labs-ControlNet-Union-Pro", - "description": "FLUX.1 [Dev] Union Controlnet. Supports Canny, Tile, Depth, Blur, Pose, Gray, Low Quality", - "reference": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro/resolve/main/diffusion_pytorch_model.safetensors", - "size": "6.6GB" - }, - { - "name": "Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro (fp8_e4m3fn) by Kijai", - "type": "controlnet", - "base": "FLUX.1", - "save_path": "controlnet/FLUX.1", - "description": "FLUX.1 [Dev] Union Controlnet. Supports Canny, Tile, Depth, Blur, Pose, Gray, Low Quality\nVersion quantized to fp8_e4m3fn by Kijai", - "reference": "https://huggingface.co/Kijai/flux-fp8", - "filename": "flux_shakker_labs_union_pro-fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux_shakker_labs_union_pro-fp8_e4m3fn.safetensors", - "size": "3.3GB" - }, - - { - "name": "jasperai/FLUX.1-dev-Controlnet-Upscaler", - "type": "controlnet", - "base": "FLUX.1", - "save_path": "controlnet/FLUX.1/jasperai-dev-Upscaler", - "description": "This is Flux.1-dev ControlNet for low resolution images developed by Jasper research team.", - "reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Upscaler", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Upscaler/resolve/main/diffusion_pytorch_model.safetensors", - "size": "3.58GB" - }, - { - "name": "jasperai/FLUX.1-dev-Controlnet-Depth", - "type": "controlnet", - "base": "FLUX.1", - "save_path": "controlnet/FLUX.1/jasperai-dev-Depth", - "description": "This is Flux.1-dev ControlNet for Depth map developed by Jasper research team.", - "reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Depth", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Depth/resolve/main/diffusion_pytorch_model.safetensors", - "size": "3.58GB" - }, - { - "name": "jasperai/Flux.1-dev-Controlnet-Surface-Normals", - "type": "controlnet", - "base": "FLUX.1", - "save_path": "controlnet/FLUX.1/jasperai-dev-Surface-Normals", - "description": "This is Flux.1-dev ControlNet for Surface Normals map developed by Jasper research team.", - "reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals/resolve/main/diffusion_pytorch_model.safetensors", - "size": "3.58GB" - }, - - { - "name": "xinsir/ControlNet++: All-in-one ControlNet", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-union-sdxl-1.0", - "description": "All-in-one ControlNet for image generations and editing!", - "reference": "https://huggingface.co/xinsir/controlnet-union-sdxl-1.0", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-union-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/ControlNet++: All-in-one ControlNet (ProMax model)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-union-sdxl-1.0", - "description": "All-in-one ControlNet for image generations and editing! (ProMax model)", - "reference": "https://huggingface.co/xinsir/controlnet-union-sdxl-1.0", - "filename": "diffusion_pytorch_model_promax.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-union-sdxl-1.0/resolve/main/diffusion_pytorch_model_promax.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/Controlnet-Scribble-Sdxl-1.0", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-scribble-sdxl-1.0", - "description": "Controlnet SDXL Scribble model.", - "reference": "https://huggingface.co/xinsir/controlnet-scribble-sdxl-1.0", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-scribble-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/Controlnet-Canny-Sdxl-1.0 (V2)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-canny-sdxl-1.0", - "description": "Controlnet SDXL Canny model.", - "reference": "https://huggingface.co/xinsir/controlnet-canny-sdxl-1.0", - "filename": "diffusion_pytorch_model_V2.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-canny-sdxl-1.0/resolve/main/diffusion_pytorch_model_V2.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/Controlnet-Openpose-Sdxl-1.0", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-openpose-sdxl-1.0", - "description": "Controlnet SDXL Openpose model.", - "reference": "https://huggingface.co/xinsir/controlnet-openpose-sdxl-1.0", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-openpose-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/Controlnet-Openpose-Sdxl-1.0 (Ver. twins)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-openpose-sdxl-1.0", - "description": "Controlnet SDXL Openpose model. (Ver. twins)", - "reference": "https://huggingface.co/xinsir/controlnet-openpose-sdxl-1.0", - "filename": "diffusion_pytorch_model_twins.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-openpose-sdxl-1.0/resolve/main/diffusion_pytorch_model_twins.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/Controlnet-Scribble-Sdxl-1.0-Anime", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-scribble-sdxl-1.0-anime", - "description": "Controlnet SDXL Scribble model. (Ver. anime)", - "reference": "https://huggingface.co/xinsir/anime-painter", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/xinsir/anime-painter/resolve/main/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/ControlNet Depth SDXL, support zoe, midias", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-depth-sdxl-1.0", - "description": "Controlnet SDXL Depth model.", - "reference": "https://huggingface.co/xinsir/controlnet-depth-sdxl-1.0", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-depth-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - { - "name": "xinsir/ControlNet Tile SDXL", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL/controlnet-tile-sdxl-1.0", - "description": "Controlnet SDXL Tile model.", - "reference": "https://huggingface.co/xinsir/controlnet-tile-sdxl-1.0", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/xinsir/controlnet-tile-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors", - "size": "2.50GB" - }, - - { - "name": "InstantX/SD3-Controlnet-Canny", - "type": "controlnet", - "base": "SD3", - "save_path": "controlnet/SD3/InstantX-Controlnet-Canny", - "description": "Controlnet SD3 Canny model.", - "reference": "https://huggingface.co/InstantX/SD3-Controlnet-Canny", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/InstantX/SD3-Controlnet-Canny/resolve/main/diffusion_pytorch_model.safetensors", - "size": "1.19GB" - }, - { - "name": "InstantX/SD3-Controlnet-Pose", - "type": "controlnet", - "base": "SD3", - "save_path": "controlnet/SD3/InstantX-Controlnet-Pose", - "description": "Controlnet SD3 Pose model.", - "reference": "https://huggingface.co/InstantX/SD3-Controlnet-Pose", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/InstantX/SD3-Controlnet-Pose/resolve/main/diffusion_pytorch_model.safetensors", - "size": "1.19GB" - }, - { - "name": "InstantX/SD3-Controlnet-Tile", - "type": "controlnet", - "base": "SD3", - "save_path": "controlnet/SD3/InstantX-Controlnet-Tile", - "description": "Controlnet SD3 Tile model.", - "reference": "https://huggingface.co/InstantX/SD3-Controlnet-Tile", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/InstantX/SD3-Controlnet-Tile/resolve/main/diffusion_pytorch_model.safetensors", - "size": "1.19GB" - }, - - - { - "name": "stabilityai/SD3.5-Large-Controlnet-Blur", - "type": "controlnet", - "base": "SD3.5", - "save_path": "controlnet/SD3.5", - "description": "Blur Controlnet model for SD3.5 Large", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets", - "filename": "sd3.5_large_controlnet_blur.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets/resolve/main/sd3.5_large_controlnet_blur.safetensors", - "size": "8.65GB" - }, - { - "name": "stabilityai/SD3.5-Large-Controlnet-Canny", - "type": "controlnet", - "base": "SD3.5", - "save_path": "controlnet/SD3.5", - "description": "Canny Controlnet model for SD3.5 Large", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets", - "filename": "sd3.5_large_controlnet_canny.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets/resolve/main/sd3.5_large_controlnet_canny.safetensors", - "size": "8.65GB" - }, - { - "name": "stabilityai/SD3.5-Large-Controlnet-Depth", - "type": "controlnet", - "base": "SD3.5", - "save_path": "controlnet/SD3.5", - "description": "Depth Controlnet model for SD3.5 Large", - "reference": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets", - "filename": "sd3.5_large_controlnet_depth.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets/resolve/main/sd3.5_large_controlnet_depth.safetensors", - "size": "8.65GB" - }, - - { - "name": "Kijai/ToonCrafter model checkpoint (interpolation fp16)", - "type": "checkpoint", - "base": "ToonCrafter", - "save_path": "checkpoints/ToonCrafter", - "description": "ToonCrafter checkpoint model for ComfyUI-DynamiCrafterWrapper", - "reference": "https://huggingface.co/Kijai/DynamiCrafter_pruned", - "filename": "tooncrafter_512_interp-fp16.safetensors", - "url": "https://huggingface.co/Kijai/DynamiCrafter_pruned/resolve/main/tooncrafter_512_interp-fp16.safetensors", - "size": "5.25GB" - }, - { - "name": "CN-anytest_v4-marged.safetensors", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet. A model for style transfer.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v4-marged.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v4-marged.safetensors", - "size": "2.50GB" - }, - { - "name": "CN-anytest_v4-marged_am_dim256.safetensors (dim256/Animagine)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim256) for Animagine. A model for style transfer.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v4-marged_am_dim256.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v4-marged_am_dim256.safetensors", - "size": "774.4MB" - }, - { - "name": "CN-anytest_v4-marged_am_dim128.safetensors (dim128/Animagine)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim128) for Animagine. A model for style transfer.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v4-marged_am_dim128.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v4-marged_am_dim128.safetensors", - "size": "395.7MB" - }, - { - "name": "CN-anytest_v4-marged_pn_dim256.safetensors (dim256/Pony)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim256) for Pony. A model for style transfer.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v4-marged_pn_dim256.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v4-marged_pn_dim256.safetensors", - "size": "774.4MB" - }, - { - "name": "CN-anytest_v4-marged_pn_dim128.safetensors (dim128/Pony)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim128) for Pony. A model for style transfer.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v4-marged_pn_dim128.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v4-marged_pn_dim128.safetensors", - "size": "395.7MB" - }, - { - "name": "CN-anytest_v3-50000_fp16.safetensors (fp16)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet. A strict control model.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v3-50000_fp16.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v3-50000_fp16.safetensors", - "size": "2.50GB" - }, - { - "name": "CN-anytest_v3-50000_am_dim256.safetensors (dim256/Animagine)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim256) for Animagine. A strict control model.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v3-50000_am_dim256.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v3-50000_am_dim256.safetensors", - "size": "774.4MB" - }, - { - "name": "CN-anytest_v3-50000_am_dim128.safetensors (dim128/Animagine)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim128) for Animagine. A strict control model.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v3-50000_am_dim128.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v3-50000_am_dim128.safetensors", - "size": "395.7MB" - }, - { - "name": "CN-anytest_v3-50000_pn_dim256.safetensors (dim256/Pony)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim256) for Pony. A strict control model.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v3-50000_pn_dim256.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v3-50000_pn_dim256.safetensors", - "size": "774.4MB" - }, - { - "name": "CN-anytest_v3-50000_pn_dim128.safetensors (dim128/Pony)", - "type": "controlnet", - "base": "SDXL", - "save_path": "controlnet/SDXL", - "description": "AnyTest Controlnet Lora (dim128) for Pony. A strict control model.", - "reference": "https://huggingface.co/2vXpSwA7/iroiro-lora/tree/main", - "filename": "CN-anytest_v3-50000_pn_dim128.safetensors", - "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/test_controlnet2/CN-anytest_v3-50000_pn_dim128.safetensors", - "size": "395.7MB" - }, - { - "name": "kijai/DepthAnythingV2 (vitb/fp16)", - "type": "depthanything", - "base": "depthanything", - "save_path": "depthanything", - "description": "DepthAnythingV2 model", - "reference": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/tree/main", - "filename": "depth_anything_v2_vitb_fp16.safetensors", - "url": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/resolve/main/depth_anything_v2_vitb_fp16.safetensors", - "size": "195.0MB" - }, - { - "name": "kijai/DepthAnythingV2 (vitb/fp32)", - "type": "depthanything", - "base": "depthanything", - "save_path": "depthanything", - "description": "DepthAnythingV2 model", - "reference": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/tree/main", - "filename": "depth_anything_v2_vitb_fp32.safetensors", - "url": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/resolve/main/depth_anything_v2_vitb_fp32.safetensors", - "size": "389.9MB" - }, - { - "name": "kijai/DepthAnythingV2 (vitl/fp16)", - "type": "depthanything", - "base": "depthanything", - "save_path": "depthanything", - "description": "DepthAnythingV2 model", - "reference": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/tree/main", - "filename": "depth_anything_v2_vitl_fp16.safetensors", - "url": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/resolve/main/depth_anything_v2_vitl_fp16.safetensors", - "size": "670.7MB" - }, - { - "name": "kijai/DepthAnythingV2 (vitl/fp32)", - "type": "depthanything", - "base": "depthanything", - "save_path": "depthanything", - "description": "DepthAnythingV2 model", - "reference": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/tree/main", - "filename": "depth_anything_v2_vitl_fp32.safetensors", - "url": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/resolve/main/depth_anything_v2_vitl_fp32.safetensors", - "size": "1.34GB" - }, - { - "name": "kijai/DepthAnythingV2 (vits/fp16)", - "type": "depthanything", - "base": "depthanything", - "save_path": "depthanything", - "description": "DepthAnythingV2 model", - "reference": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/tree/main", - "filename": "depth_anything_v2_vits_fp16.safetensors", - "url": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/resolve/main/depth_anything_v2_vits_fp16.safetensors", - "size": "49.6MB" - }, - { - "name": "kijai/DepthAnythingV2 (vitb/fp32)", - "type": "depthanything", - "base": "depthanything", - "save_path": "depthanything", - "description": "DepthAnythingV2 model", - "reference": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/tree/main", - "filename": "depth_anything_v2_vits_fp32.safetensors", - "url": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/resolve/main/depth_anything_v2_vits_fp32.safetensors", - "size": "99.2MB" - }, - - { - "name": "PixArt-Sigma-XL-2-1024-MS.pth (checkpoint)", - "type": "checkpoint", - "base": "pixart-sigma", - "save_path": "checkpoints/PixArt-Sigma", - "description": "PixArt-Sigma Checkpoint model", - "reference": "https://huggingface.co/PixArt-alpha/PixArt-Sigma/tree/main", - "filename": "PixArt-Sigma-XL-2-1024-MS.pth", - "url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma/resolve/main/PixArt-Sigma-XL-2-1024-MS.pth", - "size": "2.47GB" - }, - - { - "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": "hunyuan_dit_1.2.safetensors", - "type": "checkpoint", - "base": "Hunyuan-DiT", - "save_path": "checkpoints/hunyuan_dit_comfyui", - "description": "Different versions of HunyuanDIT packaged for ComfyUI use.", - "reference": "https://huggingface.co/comfyanonymous/hunyuan_dit_comfyui", - "filename": "hunyuan_dit_1.2.safetensors", - "url": "https://huggingface.co/comfyanonymous/hunyuan_dit_comfyui/resolve/main/hunyuan_dit_1.2.safetensors", - "size": "8.24GB" - }, - { - "name": "hunyuan_dit_1.1.safetensors", - "type": "checkpoint", - "base": "Hunyuan-DiT", - "save_path": "checkpoints/hunyuan_dit_comfyui", - "description": "Different versions of HunyuanDIT packaged for ComfyUI use.", - "reference": "https://huggingface.co/comfyanonymous/hunyuan_dit_comfyui", - "filename": "hunyuan_dit_1.1.safetensors", - "url": "https://huggingface.co/comfyanonymous/hunyuan_dit_comfyui/resolve/main/hunyuan_dit_1.1.safetensors", - "size": "8.24GB" - }, - { - "name": "hunyuan_dit_1.0.safetensors", - "type": "checkpoint", - "base": "Hunyuan-DiT", - "save_path": "checkpoints/hunyuan_dit_comfyui", - "description": "Different versions of HunyuanDIT packaged for ComfyUI use.", - "reference": "https://huggingface.co/comfyanonymous/hunyuan_dit_comfyui", - "filename": "hunyuan_dit_1.0.safetensors", - "url": "https://huggingface.co/comfyanonymous/hunyuan_dit_comfyui/resolve/main/hunyuan_dit_1.0.safetensors", - "size": "8.24GB" - }, - - { - "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": "default", - "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": "Comfy-Org/hunyuan_video_image_to_video_720p_bf16.safetensors", - "type": "diffusion_model", - "base": "Hunyuan Video", - "save_path": "diffusion_models/hunyuan_video", - "description": "Huyuan Video Image2Video diffusion model. repackaged version.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "filename": "hunyuan_video_image_to_video_720p_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_image_to_video_720p_bf16.safetensors", - "size": "25.6GB" - }, - - { - "name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors", - "type": "clip", - "base": "LLaVA-Llama-3", - "save_path": "text_encoders", - "description": "llava_llama3_fp8_scaled text encoder model. This is required for using Hunyuan Video.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "filename": "llava_llama3_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/text_encoders/llava_llama3_fp8_scaled.safetensors", - "size": "9.09GB" - }, - { - "name": "Comfy-Org/llava_llama3_fp16.safetensors", - "type": "clip", - "base": "LLaVA-Llama-3", - "save_path": "text_encoders", - "description": "llava_llama3_fp16 text encoder model. This is required for using Hunyuan Video.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "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": "Comfy-Org/llava_llama3_vision.safetensors", - "type": "clip_vision", - "base": "LLaVA-Llama-3", - "save_path": "text_encoders", - "description": "llava_llama3_vision clip vison model. This is required for using Hunyuan Video Image2Video.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "filename": "llava_llama3_vision.safetensors", - "url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/clip_vision/llava_llama3_vision.safetensors", - "size": "649MB" - }, - - { - "name": "Comfy-Org/omnigen2_fp16.safetensors", - "type": "diffusion_model", - "base": "OmniGen2", - "save_path": "default", - "description": "OmniGen2 diffusion model. This is required for using OmniGen2.", - "reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged", - "filename": "omnigen2_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/omnigen2_fp16.safetensors", - "size": "7.93GB" - }, - { - "name": "Comfy-Org/qwen_2.5_vl_fp16.safetensors", - "type": "clip", - "base": "qwen-2.5", - "save_path": "default", - "description": "text encoder for OmniGen2", - "reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged", - "filename": "qwen_2.5_vl_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/text_encoders/qwen_2.5_vl_fp16.safetensors", - "size": "7.51GB" - }, - - { - "name": "FLUX.1 [Schnell] Diffusion model", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Schnell] Diffusion model (a.k.a. FLUX.1 turbo model)[w/Due to the large size of the model, it is recommended to download it through a browser if possible.]", - "reference": "https://huggingface.co/black-forest-labs/FLUX.1-schnell", - "filename": "flux1-schnell.safetensors", - "url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/flux1-schnell.safetensors", - "size": "23.8GB" - }, - - { - "name": "FLUX.1 VAE model", - "type": "VAE", - "base": "FLUX.1", - "save_path": "vae/FLUX1", - "description": "FLUX.1 VAE model\nNOTE: This VAE model can also be used for image generation with OmniGen2.", - "reference": "https://huggingface.co/black-forest-labs/FLUX.1-schnell", - "filename": "ae.safetensors", - "url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/ae.safetensors", - "size": "335MB" - }, - - { - "name": "kijai/FLUX.1 [schnell] Diffusion model (float8_e4m3fn)", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Schnell] Diffusion model (float8_e4m3fn)", - "reference": "https://huggingface.co/Kijai/flux-fp8", - "filename": "flux1-schnell-fp8.safetensors", - "url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux1-schnell-fp8.safetensors", - "size": "11.9GB" - }, - - { - "name": "FLUX.1 [Dev] Diffusion model (scaled fp8)", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (scaled fp8)[w/Due to the large size of the model, it is recommended to download it through a browser if possible.]", - "reference": "https://huggingface.co/comfyanonymous/flux_dev_scaled_fp8_test", - "filename": "flux_dev_fp8_scaled_diffusion_model.safetensors", - "url": "https://huggingface.co/comfyanonymous/flux_dev_scaled_fp8_test/resolve/main/flux_dev_fp8_scaled_diffusion_model.safetensors", - "size": "11.9GB" - }, - { - "name": "kijai/FLUX.1 [dev] Diffusion model (float8_e4m3fn)", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [dev] Diffusion model (float8_e4m3fn)", - "reference": "https://huggingface.co/Kijai/flux-fp8", - "filename": "flux1-dev-fp8.safetensors", - "url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux1-dev-fp8.safetensors", - "size": "11.9GB" - }, - - { - "name": "Comfy Org/FLUX.1 [dev] Checkpoint model (fp8)", - "type": "checkpoint", - "base": "FLUX.1", - "save_path": "checkpoints/FLUX1", - "description": "FLUX.1 [dev] Checkpoint model (fp8)", - "reference": "https://huggingface.co/Comfy-Org/flux1-dev/tree/main", - "filename": "flux1-dev-fp8.safetensors", - "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", - "size": "17.2GB" - }, - { - "name": "Comfy Org/FLUX.1 [schnell] Checkpoint model (fp8)", - "type": "checkpoint", - "base": "FLUX.1", - "save_path": "checkpoints/FLUX1", - "description": "FLUX.1 [schnell] Checkpoint model (fp8)", - "reference": "https://huggingface.co/Comfy-Org/flux1-dev/tree/main", - "filename": "flux1-schnell-fp8.safetensors", - "url": "https://huggingface.co/Comfy-Org/flux1-schnell/resolve/main/flux1-schnell-fp8.safetensors", - "size": "17.2GB" - }, - - { - "name": "city96/flux1-dev-F16.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (f16/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-F16.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-F16.gguf", - "size": "23.8GB" - }, - { - "name": "city96/flux1-dev-Q2_K.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q2_K/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q2_K.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q2_K.gguf", - "size": "4.03GB" - }, - { - "name": "city96/flux1-dev-Q3_K_S.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q3_K_S/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q3_K_S.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q3_K_S.gguf", - "size": "5.23GB" - }, - { - "name": "city96/flux1-dev-Q4_0.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q4_0/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q4_0.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q4_0.gguf", - "size": "6.79GB" - }, - { - "name": "city96/flux1-dev-Q4_1.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q4_1/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q4_1.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q4_1.gguf", - "size": "7.53GB" - }, - { - "name": "city96/flux1-dev-Q4_K_S.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q4_K_S/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q4_K_S.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q4_K_S.gguf", - "size": "6.81GB" - }, - { - "name": "city96/flux1-dev-Q5_0.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q5_0/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q5_0.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q5_0.gguf", - "size": "8.27GB" - }, - { - "name": "city96/flux1-dev-Q5_1.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q5_1/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q5_1.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q5_1.gguf", - "size": "9.01GB" - }, - { - "name": "city96/flux1-dev-Q5_K_S.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q5_K_S/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q5_K_S.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q5_K_S.gguf", - "size": "8.29GB" - }, - { - "name": "city96/flux1-dev-Q6_K.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q6_K/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q6_K.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q6_K.gguf", - "size": "9.86GB" - }, - { - "name": "city96/flux1-dev-Q8_0.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q8_0/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-dev-gguf", - "filename": "flux1-dev-Q8_0.gguf", - "url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q8_0.gguf", - "size": "12.7GB" - }, - - { - "name": "city96/flux1-schnell-F16.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (f16/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-F16.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-F16.gguf", - "size": "23.8GB" - }, - { - "name": "city96/flux1-schnell-Q2_K.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q2_K/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q2_K.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q2_K.gguf", - "size": "4.01GB" - }, - { - "name": "city96/flux1-schnell-Q3_K_S.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q3_K_S/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q3_K_S.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q3_K_S.gguf", - "size": "5.21GB" - }, - { - "name": "city96/flux1-schnell-Q4_0.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q4_0/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q4_0.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q4_0.gguf", - "size": "6.77GB" - }, - { - "name": "city96/flux1-schnell-Q4_1.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q4_1/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q4_1.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q4_1.gguf", - "size": "7.51GB" - }, - { - "name": "city96/flux1-schnell-Q4_K_S.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q4_K_S/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q4_K_S.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q4_K_S.gguf", - "size": "6.78GB" - }, - { - "name": "city96/flux1-schnell-Q5_0.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q5_0/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q5_0.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q5_0.gguf", - "size": "8.25GB" - }, - { - "name": "city96/flux1-schnell-Q5_1.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q5_1/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q5_1.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q5_1.gguf", - "size": "8.99GB" - }, - { - "name": "city96/flux1-schnell-Q5_K_S.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q5_K_S/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q5_K_S.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q5_K_S.gguf", - "size": "8.26GB" - }, - { - "name": "city96/flux1-schnell-Q6_K.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q6_K/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q6_K.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q6_K.gguf", - "size": "9.83GB" - }, - { - "name": "city96/flux1-schnell-Q8_0.gguf", - "type": "diffusion_model", - "base": "FLUX.1", - "save_path": "diffusion_models/FLUX1", - "description": "FLUX.1 [Dev] Diffusion model (Q8_0/.gguf)", - "reference": "https://huggingface.co/city96/FLUX.1-schnell-gguf", - "filename": "flux1-schnell-Q8_0.gguf", - "url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q8_0.gguf", - "size": "12.7GB" - }, - - { - "name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]", - "type": "clip", - "base": "clip", - "save_path": "text_encoders/long_clip", - "description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)", - "reference": "https://huggingface.co/zer0int", - "filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors", - "url": "https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/resolve/main/ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors", - "size": "931MB" - }, - { - "name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]", - "type": "clip", - "base": "clip", - "save_path": "text_encoders/long_clip", - "description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)", - "reference": "https://huggingface.co/zer0int", - "filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors", - "url": "https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/resolve/main/ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors", - "size": "323MB" - }, - - { - "name": "Depth Pro model", - "type": "depth-pro", - "base": "depth-pro", - "save_path": "depth/ml-depth-pro", - "description": "Depth pro model for [a/ComfyUI-Depth-Pro](https://github.com/spacepxl/ComfyUI-Depth-Pro)", - "reference": "https://huggingface.co/spacepxl/ml-depth-pro", - "filename": "depth_pro.fp16.safetensors", - "url": "https://huggingface.co/spacepxl/ml-depth-pro/resolve/main/depth_pro.fp16.safetensors", - "size": "1.9GB" - }, - - { - "name": "kijai/lotus depth d model v1.1 (fp16)", - "type": "diffusion_model", - "base": "lotus", - "save_path": "diffusion_models", - "description": "lotus depth d model v1.1 (fp16). This model can be used in ComfyUI-Lotus custom nodes.", - "reference": "https://huggingface.co/Kijai/lotus-comfyui", - "filename": "lotus-depth-d-v-1-1-fp16.safetensors", - "url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-d-v-1-1-fp16.safetensors", - "size": "1.74GB" - }, - { - "name": "kijai/lotus depth g model v1.0 (fp16)", - "type": "diffusion_model", - "base": "lotus", - "save_path": "diffusion_models", - "description": "lotus depth g model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.", - "reference": "https://huggingface.co/Kijai/lotus-comfyui", - "filename": "lotus-depth-g-v1-0-fp16.safetensors", - "url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-g-v1-0-fp16.safetensors", - "size": "1.74GB" - }, - { - "name": "kijai/lotus depth g model v1.0", - "type": "diffusion_model", - "base": "lotus", - "save_path": "diffusion_models", - "description": "lotus depth g model v1.0. This model can be used in ComfyUI-Lotus custom nodes.", - "reference": "https://huggingface.co/Kijai/lotus-comfyui", - "filename": "lotus-depth-g-v1-0.safetensors", - "url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-g-v1-0.safetensors", - "size": "3.47GB" - }, - { - "name": "kijai/lotus normal d model v1.0 (fp16)", - "type": "diffusion_model", - "base": "lotus", - "save_path": "diffusion_models", - "description": "lotus normal d model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.", - "reference": "https://huggingface.co/Kijai/lotus-comfyui", - "filename": "lotus-normal-d-v1-0-fp16.safetensors", - "url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-d-v1-0-fp16.safetensors", - "size": "1.74GB" - }, - { - "name": "kijai/lotus normal d model v1.0", - "type": "diffusion_model", - "base": "lotus", - "save_path": "diffusion_models", - "description": "lotus normal d model v1.0. This model can be used in ComfyUI-Lotus custom nodes.", - "reference": "https://huggingface.co/Kijai/lotus-comfyui", - "filename": "lotus-normal-d-v1-0.safetensors", - "url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-d-v1-0.safetensors", - "size": "3.47GB" - }, - { - "name": "kijai/lotus normal g model v1.0 (fp16)", - "type": "diffusion_model", - "base": "lotus", - "save_path": "diffusion_models", - "description": "lotus normal g model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.", - "reference": "https://huggingface.co/Kijai/lotus-comfyui", - "filename": "lotus-normal-g-v1-0-fp16.safetensors", - "url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-g-v1-0-fp16.safetensors", - "size": "1.74GB" - }, - { - "name": "kijai/lotus normal g model v1.0", - "type": "diffusion_model", - "base": "lotus", - "save_path": "diffusion_models", - "description": "lotus normal g model v1.0. This model can be used in ComfyUI-Lotus custom nodes.", - "reference": "https://huggingface.co/Kijai/lotus-comfyui", - "filename": "lotus-normal-g-v1-0.safetensors", - "url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-g-v1-0.safetensors", - "size": "3.47GB" - }, - - { - "name": "Kolors UNet model", - "type": "diffusion_model", - "base": "Kolors", - "save_path": "diffusion_models/kolors", - "description": "Kolors UNet model", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/unet/diffusion_pytorch_model.safetensors", - "size": "10.3GB" - }, - { - "name": "Kolors UNet model (fp16)", - "type": "diffusion_model", - "base": "Kolors", - "save_path": "diffusion_models/kolors", - "description": "Kolors UNet model", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors", - "filename": "diffusion_pytorch_model.fp16.safetensors", - "url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/unet/diffusion_pytorch_model.fp16.safetensors", - "size": "5.16GB" - }, - { - "name": "Kijai/ChatGLM3 (4bit)", - "type": "LLM", - "base": "ChatGLM3", - "save_path": "LLM", - "description": "This is required for Kolors", - "reference": "https://huggingface.co/Kijai/ChatGLM3-safetensors/tree/main", - "filename": "chatglm3-4bit.safetensors", - "url": "https://huggingface.co/Kijai/ChatGLM3-safetensors/resolve/main/chatglm3-4bit.safetensors", - "size": "3.92GB" - }, - { - "name": "Kijai/ChatGLM3 (8bit)", - "type": "LLM", - "base": "ChatGLM3", - "save_path": "LLM", - "description": "This is required for Kolors", - "reference": "https://huggingface.co/Kijai/ChatGLM3-safetensors/tree/main", - "filename": "chatglm3-8bit.safetensors", - "url": "https://huggingface.co/Kijai/ChatGLM3-safetensors/resolve/main/chatglm3-8bit.safetensors", - "size": "3.92GB" - }, - { - "name": "Kijai/ChatGLM3 (16bit)", - "type": "LLM", - "base": "ChatGLM3", - "save_path": "LLM", - "description": "This is required for Kolors", - "reference": "https://huggingface.co/Kijai/ChatGLM3-safetensors/tree/main", - "filename": "chatglm3-fp16.safetensors", - "url": "https://huggingface.co/Kijai/ChatGLM3-safetensors/resolve/main/chatglm3-fp16.safetensors", - "size": "12.52GB" - }, - - { - "name": "pulid_flux_v0.9.1.safetensors", - "type": "PuLID", - "base": "FLUX.1", - "save_path": "pulid", - "description": "This is required for PuLID (FLUX)", - "reference": "https://huggingface.co/guozinan/PuLID", - "filename": "pulid_flux_v0.9.1.safetensors", - "url": "https://huggingface.co/guozinan/PuLID/resolve/main/pulid_flux_v0.9.1.safetensors", - "size": "1.14GB" - }, - { - "name": "pulid_v1.1.safetensors", - "type": "PuLID", - "base": "SDXL", - "save_path": "pulid", - "description": "This is required for PuLID (SDXL)", - "reference": "https://huggingface.co/guozinan/PuLID", - "filename": "pulid_v1.1.safetensors", - "url": "https://huggingface.co/guozinan/PuLID/resolve/main/pulid_v1.1.safetensors", - "size": "984MB" - }, - - { - "name": "kijai/MoGe_ViT_L_fp16.safetensors", - "type": "MoGe", - "base": "MoGe", - "save_path": "MoGe", - "description": "Safetensors versions of [a/https://github.com/microsoft/MoGe](https://github.com/microsoft/MoGe)", - "reference": "https://huggingface.co/Kijai/MoGe_safetensors", - "filename": "MoGe_ViT_L_fp16.safetensors", - "url": "https://huggingface.co/Kijai/MoGe_safetensors/resolve/main/MoGe_ViT_L_fp16.safetensors", - "size": "628MB" - }, - { - "name": "kijai/MoGe_ViT_L_fp16.safetensors", - "type": "MoGe", - "base": "MoGe", - "save_path": "MoGe", - "description": "Safetensors versions of [a/https://github.com/microsoft/MoGe](https://github.com/microsoft/MoGe)", - "reference": "https://huggingface.co/Kijai/MoGe_safetensors", - "filename": "MoGe_ViT_L_fp16.safetensors", - "url": "https://huggingface.co/Kijai/MoGe_safetensors/resolve/main/MoGe_ViT_L_fp16.safetensors", - "size": "1.26GB" - }, - - { - "name": "LTX-Video 2B v0.9 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.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.safetensors", - "size": "9.37GB" - }, - { - "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": "LTX-Video 2B v0.9.5 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.5.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.5.safetensors", - "size": "6.34GB" - }, - - { - "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" - }, - - { - "name": "XLabs-AI/realism_lora.safetensors", - "type": "lora", - "base": "FLUX.1", - "save_path": "xlabs/loras", - "description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs", - "reference": "https://huggingface.co/XLabs-AI/flux-lora-collection", - "filename": "realism_lora.safetensors", - "url": "https://huggingface.co/XLabs-AI/flux-lora-collection/resolve/main/realism_lora.safetensors", - "size": "44.8MB" - }, - { - "name": "XLabs-AI/art_lora.safetensors", - "type": "lora", - "base": "FLUX.1", - "save_path": "xlabs/loras", - "description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs", - "reference": "https://huggingface.co/XLabs-AI/flux-lora-collection", - "filename": "art_lora.safetensors", - "url": "https://huggingface.co/XLabs-AI/flux-lora-collection/resolve/main/scenery_lora.safetensors", - "size": "44.8MB" - }, - { - "name": "XLabs-AI/mjv6_lora.safetensors", - "type": "lora", - "base": "FLUX.1", - "save_path": "xlabs/loras", - "description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs", - "reference": "https://huggingface.co/XLabs-AI/flux-lora-collection", - "filename": "mjv6_lora.safetensors", - "url": "https://huggingface.co/XLabs-AI/flux-lora-collection/resolve/main/mjv6_lora.safetensors", - "size": "44.8MB" - }, - - { - "name": "XLabs-AI/flux-ip-adapter", - "type": "lora", - "base": "FLUX.1", - "save_path": "xlabs/ipadapters", - "description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs", - "reference": "https://huggingface.co/XLabs-AI/flux-ip-adapter", - "filename": "ip_adapter.safetensors", - "url": "https://huggingface.co/XLabs-AI/flux-ip-adapter/resolve/main/ip_adapter.safetensors", - "size": "982MB" - }, - - { - "name": "efficient_sam_s_cpu.jit [ComfyUI-YoloWorld-EfficientSAM]", - "type": "efficient_sam", - "base": "efficient_sam", - "save_path": "yolo_world", - "description": "Install efficient_sam_s_cpu.jit into ComfyUI-YoloWorld-EfficientSAM", - "reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main", - "filename": "efficient_sam_s_cpu.jit", - "url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_cpu.jit", - "size": "106.0MB" - }, - { - "name": "efficient_sam_s_gpu.jit [ComfyUI-YoloWorld-EfficientSAM]", - "type": "efficient_sam", - "base": "efficient_sam", - "save_path": "yolo_world", - "description": "Install efficient_sam_s_gpu.jit into ComfyUI-YoloWorld-EfficientSAM", - "reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main", - "filename": "efficient_sam_s_gpu.jit", - "url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_gpu.jit", - "size": "106.0MB" - }, - - { - "name": "TencentARC/CustomNet V1", - "type": "CustomNet", - "base": "CustomNet", - "save_path": "checkpoints/customnet", - "description": "CustomNet pretrained model for ComfyUI_CustomNet", - "reference": "https://huggingface.co/TencentARC/CustomNet/tree/main", - "filename": "customnet_v1.pt", - "url": "https://huggingface.co/TencentARC/CustomNet/resolve/main/customnet_v1.pt", - "size": "5.71GB" - }, - { - "name": "TencentARC/CustomNet Inpaint V1", - "type": "CustomNet", - "base": "CustomNet", - "save_path": "checkpoints/customnet", - "description": "CustomNet Inpaint pretrained model for ComfyUI_CustomNet", - "reference": "https://huggingface.co/TencentARC/CustomNet/tree/main", - "filename": "customnet_inpaint_v1.pt", - "url": "https://huggingface.co/TencentARC/CustomNet/resolve/main/customnet_inpaint_v1.pt", - "size": "5.71GB" - }, - - { - "name": "deepseek-ai/Janus-Pro-1B", - "type": "Janus-Pro", - "base": "Janus-Pro", - "save_path": "Janus-Pro", - "description": "[SNAPSHOT] Janus-Pro-1B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]", - "reference": "https://huggingface.co/deepseek-ai/Janus-Pro-1B", - "filename": "", - "url": "deepseek-ai/Janus-Pro-1B", - "size": "7.8GB" - }, - { - "name": "deepseek-ai/Janus-Pro-7B", - "type": "Janus-Pro", - "base": "Janus-Pro", - "save_path": "Janus-Pro", - "description": "[SNAPSHOT] Janus-Pro-7B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]", - "reference": "https://huggingface.co/deepseek-ai/Janus-Pro-7B", - "filename": "", - "url": "deepseek-ai/Janus-Pro-7B", - "size": "14.85GB" - }, - { - "name": "kolors/vae/diffusion_pytorch_model.fp16.safetensors", - "type": "VAE", - "base": "Kolors", - "save_path": "vae/kolors", - "description": "Kolors VAE", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors", - "filename": "diffusion_pytorch_model.fp16.safetensors", - "url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.fp16.safetensors", - "size": "167MB" - }, - { - "name": "kolors/vae/diffusion_pytorch_model.safetensors", - "type": "VAE", - "base": "Kolors", - "save_path": "vae/kolors", - "description": "Kolors VAE", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.safetensors", - "size": "335MB" - }, - - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_bf16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_e4m3fn)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (fp8_e4m3fn)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors", - "size": "16.4GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_scaled)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (fp8_scaled)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_scaled.safetensors", - "size": "16.4GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_bf16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_e4m3fn)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (fp8_e4m3fn)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors", - "size": "16.4GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_scaled)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (fp8_scaled)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_scaled.safetensors", - "size": "16.4GB" - }, - - { - "name": "Comfy-Org/Wan2.1 t2v 1.3B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 1.3B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_1.3B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_bf16.safetensors", - "size": "2.84GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 1.3B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 1.3B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_1.3B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors", - "size": "2.84GB" - }, - - { - "name": "Comfy-Org/Wan2.1 t2v 14B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_bf16.safetensors", - "size": "28.6GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 14B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp16.safetensors", - "size": "28.6GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 14B (fp8_e4m3fn)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (fp8_e4m3fn)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_e4m3fn.safetensors", - "size": "14.3GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 14B (fp8_scaled)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (fp8_scaled)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_scaled.safetensors", - "size": "14.3GB" - }, - { - "name": "Comfy-Org/Wan2.1 VAE", - "type": "vae", - "base": "Wan2.1", - "save_path": "vae", - "description": "Wan2.1 VAE model", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan_2.1_vae.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors", - "size": "254MB" - }, - { - "name": "Comfy-Org/clip_vision_h.safetensors", - "type": "clip_vision", - "base": "clip_vision_h", - "save_path": "clip_vision", - "description": "clip_vision_h model for Wan2.1", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "clip_vision_h.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors", - "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", - "type": "clip", - "base": "umt5_xxl", - "save_path": "text_encoders", - "description": "umt5_xxl_fp16 text encoder for Wan2.1", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "umt5_xxl_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp16.safetensors", - "size": "11.4GB" - }, - { - "name": "Comfy-Org/umt5_xxl_fp8_e4m3fn_scaled.safetensors", - "type": "clip", - "base": "umt5_xxl", - "save_path": "text_encoders", - "description": "umt5_xxl_fp8_e4m3fn_scaled text encoder for Wan2.1", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors", - "size": "6.74GB" - }, - - { - "name": "lllyasviel/FramePackI2V_HY", - "type": "FramePackI2V", - "base": "FramePackI2V", - "save_path": "diffusers/lllyasviel", - "description": "[SNAPSHOT] This is the f1k1_x_g9_f1k1f2k2f16k4_td FramePack for HY. [w/You cannot download this item on ComfyUI-Manager versions below V3.18]", - "reference": "https://huggingface.co/lllyasviel/FramePackI2V_HY", - "filename": "", - "url": "lllyasviel/FramePackI2V_HY", - "size": "25.75GB" - }, - - { - "name": "LTX-2 19B Dev FP8", - "type": "checkpoint", - "base": "LTX-2", - "save_path": "checkpoints/LTX-2", - "description": "LTX-2 19B Dev FP8 model.", - "reference": "https://huggingface.co/Lightricks/LTX-2", - "filename": "ltx-2-19b-dev-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-dev-fp8.safetensors", - "size": "27.1GB" - }, - { - "name": "LTX-2 19B Distilled FP8", - "type": "checkpoint", - "base": "LTX-2", - "save_path": "checkpoints/LTX-2", - "description": "LTX-2 19B Distilled FP8 model.", - "reference": "https://huggingface.co/Lightricks/LTX-2", - "filename": "ltx-2-19b-distilled-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled-fp8.safetensors", - "size": "27.1GB" - }, - { - "name": "LTX-2 19B Dev", - "type": "checkpoint", - "base": "LTX-2", - "save_path": "checkpoints/LTX-2", - "description": "LTX-2 19B Dev model.", - "reference": "https://huggingface.co/Lightricks/LTX-2", - "filename": "ltx-2-19b-dev.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-dev.safetensors", - "size": "43.3GB" - }, - { - "name": "LTX-2 19B Distilled", - "type": "checkpoint", - "base": "LTX-2", - "save_path": "checkpoints/LTX-2", - "description": "LTX-2 19B Distilled model.", - "reference": "https://huggingface.co/Lightricks/LTX-2", - "filename": "ltx-2-19b-distilled.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled.safetensors", - "size": "43.3GB" - }, - { - "name": "LTX-2 Spatial Upscaler", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "Spatial upscaler model for LTX-2. This model enhances the spatial resolution of generated videos.", - "reference": "https://huggingface.co/Lightricks/LTX-2", - "filename": "ltx-2-spatial-upscaler-x2-1.0.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-spatial-upscaler-x2-1.0.safetensors", - "size": "996MB" - }, - { - "name": "LTX-2 Temporal Upscaler", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "Temporal upscaler model for LTX-2. This model enhances the temporal resolution of generated videos.", - "reference": "https://huggingface.co/Lightricks/LTX-2", - "filename": "ltx-2-temporal-upscaler-x2-1.0.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-temporal-upscaler-x2-1.0.safetensors", - "size": "262MB" - }, - { - "name": "LTX-2 19B Distilled LoRA", - "type": "lora", - "base": "LTX-2", - "save_path": "loras", - "description": "A LoRA adapter that transforms the standard LTX-2 19B model into a distilled version when loaded.", - "reference": "https://huggingface.co/Lightricks/LTX-2", - "filename": "ltx-2-19b-distilled-lora-384.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled-lora-384.safetensors", - "size": "7.67GB" - }, - { - "name": "LTX-2 19B IC LoRA - Canny Control", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for canny control on LTX-2 19B IC model. Intended for advanced edge control and guided generation.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Canny-Control", - "filename": "ltx-2-19b-ic-lora-canny-control.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Canny-Control/resolve/main/ltx-2-19b-ic-lora-canny-control.safetensors", - "size": "654MB" - }, - { - "name": "LTX-2 19B IC LoRA - Depth Control", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for depth control on LTX-2 19B IC model. Adds depth-aware generation guidance.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Depth-Control", - "filename": "ltx-2-19b-ic-lora-depth-control.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Depth-Control/resolve/main/ltx-2-19b-ic-lora-depth-control.safetensors", - "size": "654MB" - }, - { - "name": "LTX-2 19B IC LoRA - Detailer", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA detailer for LTX-2 19B IC. Improves fine details and sharpness in generated outputs.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Detailer", - "filename": "ltx-2-19b-ic-lora-detailer.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Detailer/resolve/main/ltx-2-19b-ic-lora-detailer.safetensors", - "size": "2.62GB" - }, - { - "name": "LTX-2 19B IC LoRA - Pose Control", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for pose control on LTX-2 19B IC model. Enables pose-guided image/video generation.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Pose-Control", - "filename": "ltx-2-19b-ic-lora-pose-control.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Pose-Control/resolve/main/ltx-2-19b-ic-lora-pose-control.safetensors", - "size": "654MB" - }, - { - "name": "LTX-2 19B LoRA - Camera Control Dolly In", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for dolly-in camera control with LTX-2 19B. Simulates camera moving closer to subject.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-In", - "filename": "ltx-2-19b-lora-camera-control-dolly-in.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-In/resolve/main/ltx-2-19b-lora-camera-control-dolly-in.safetensors", - "size": "327MB" - }, - { - "name": "LTX-2 19B LoRA - Camera Control Dolly Left", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for dolly-left camera control with LTX-2 19B. Simulates camera moving left.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Left", - "filename": "ltx-2-19b-lora-camera-control-dolly-left.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Left/resolve/main/ltx-2-19b-lora-camera-control-dolly-left.safetensors", - "size": "327MB" - }, - { - "name": "LTX-2 19B LoRA - Camera Control Dolly Out", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for dolly-out camera control with LTX-2 19B. Simulates camera moving away from subject.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Out", - "filename": "ltx-2-19b-lora-camera-control-dolly-out.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Out/resolve/main/ltx-2-19b-lora-camera-control-dolly-out.safetensors", - "size": "327MB" - }, - { - "name": "LTX-2 19B LoRA - Camera Control Dolly Right", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for dolly-right camera control with LTX-2 19B. Simulates camera moving right.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Right", - "filename": "ltx-2-19b-lora-camera-control-dolly-right.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Right/resolve/main/ltx-2-19b-lora-camera-control-dolly-right.safetensors", - "size": "327MB" - }, - { - "name": "LTX-2 19B LoRA - Camera Control Jib Down", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for jib-down camera control with LTX-2 19B. Simulates vertical camera movement downwards.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Down", - "filename": "ltx-2-19b-lora-camera-control-jib-down.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Down/resolve/main/ltx-2-19b-lora-camera-control-jib-down.safetensors", - "size": "2.21GB" - }, - { - "name": "LTX-2 19B LoRA - Camera Control Jib Up", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for jib-up camera control with LTX-2 19B. Simulates vertical camera movement upwards.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Up", - "filename": "ltx-2-19b-lora-camera-control-jib-up.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Up/resolve/main/ltx-2-19b-lora-camera-control-jib-up.safetensors", - "size": "2.21GB" - }, - { - "name": "LTX-2 19B LoRA - Camera Control Static", - "type": "lora", - "base": "LTX-2", - "save_path": "loras/LTX-2", - "description": "LoRA for static camera control with LTX-2 19B. Simulates stationary/static camera view.", - "reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Static", - "filename": "ltx-2-19b-lora-camera-control-static.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Static/resolve/main/ltx-2-19b-lora-camera-control-static.safetensors", - "size": "2.21GB" - }, - { - "name": "LTX-Video Spatial Upscaler v0.9.7", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "Spatial upscaler model for LTX-Video. This model enhances the spatial resolution of generated videos.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-spatial-upscaler-0.9.7.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-spatial-upscaler-0.9.7.safetensors", - "size": "505MB" - }, - { - "name": "LTX-Video Temporal Upscaler v0.9.7", - "type": "upscale", - "base": "upscale", - "save_path": "default", - "description": "Temporal upscaler model for LTX-Video. This model enhances the temporal resolution and smoothness of generated videos.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-temporal-upscaler-0.9.7.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-temporal-upscaler-0.9.7.safetensors", - "size": "524MB" - }, - { - "name": "LTX-Video 13B v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "High-resolution quality LTX-Video 13B model.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-dev.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev.safetensors", - "size": "28.6GB" - }, - { - "name": "LTX-Video 13B FP8 v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Quantized version of the LTX-Video 13B model, optimized for lower VRAM usage while maintaining high quality.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-dev-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev-fp8.safetensors", - "size": "15.7GB" - }, - { - "name": "LTX-Video 13B Distilled v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Distilled version of the LTX-Video 13B model, providing improved efficiency while maintaining high-resolution quality.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-distilled.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled.safetensors", - "size": "28.6GB" - }, - { - "name": "LTX-Video 13B Distilled FP8 v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Quantized distilled version of the LTX-Video 13B model, optimized for even lower VRAM usage while maintaining quality.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-distilled-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-fp8.safetensors", - "size": "15.7GB" - }, - { - "name": "LTX-Video 2B Distilled v0.9.8", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "LTX-Video 2B distilled model v0.9.8 with improved prompt understanding and detail generation.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-2b-0.9.8-distilled.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-2b-0.9.8-distilled.safetensors", - "size": "6.34GB" - }, - { - "name": "LTX-Video 2B Distilled FP8 v0.9.8", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Quantized LTX-Video 2B distilled model v0.9.8 with improved prompt understanding and detail generation, optimized for lower VRAM usage.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-2b-0.9.8-distilled-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-2b-0.9.8-distilled-fp8.safetensors", - "size": "4.46GB" - }, - { - "name": "LTX-Video 13B Distilled v0.9.8", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "LTX-Video 13B distilled model v0.9.8 with improved prompt understanding and detail generation.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.8-distilled.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.8-distilled.safetensors", - "size": "28.6GB" - }, - { - "name": "LTX-Video 13B Distilled FP8 v0.9.8", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Quantized LTX-Video 13B distilled model v0.9.8 with improved prompt understanding and detail generation, optimized for lower VRAM usage.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.8-distilled-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.8-distilled-fp8.safetensors", - "size": "15.7GB" - }, - { - "name": "LTX-Video 13B Distilled LoRA v0.9.7", - "type": "lora", - "base": "LTX-Video", - "save_path": "loras", - "description": "A LoRA adapter that transforms the standard LTX-Video 13B model into a distilled version when loaded.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-distilled-lora128.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-lora128.safetensors", - "size": "1.33GB" - }, - { - "name": "LTX-Video ICLoRA Depth 13B v0.9.7", - "type": "lora", - "base": "LTX-Video", - "save_path": "loras", - "description": "In-Context LoRA (IC LoRA) for depth-controlled video-to-video generation with precise depth conditioning.", - "reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-depth-13b-0.9.7", - "filename": "ltxv-097-ic-lora-depth-control-comfyui.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-depth-13b-0.9.7/resolve/main/ltxv-097-ic-lora-depth-control-comfyui.safetensors", - "size": "81.9MB" - }, - { - "name": "LTX-Video ICLoRA Pose 13B v0.9.7", - "type": "lora", - "base": "LTX-Video", - "save_path": "loras", - "description": "In-Context LoRA (IC LoRA) for pose-controlled video-to-video generation with precise pose conditioning.", - "reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-pose-13b-0.9.7", - "filename": "ltxv-097-ic-lora-pose-control-comfyui.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-pose-13b-0.9.7/resolve/main/ltxv-097-ic-lora-pose-control-comfyui.safetensors", - "size": "151MB" - }, - { - "name": "LTX-Video ICLoRA Canny 13B v0.9.7", - "type": "lora", - "base": "LTX-Video", - "save_path": "loras", - "description": "In-Context LoRA (IC LoRA) for canny edge-controlled video-to-video generation with precise edge conditioning.", - "reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-canny-13b-0.9.7", - "filename": "ltxv-097-ic-lora-canny-control-comfyui.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-canny-13b-0.9.7/resolve/main/ltxv-097-ic-lora-canny-control-comfyui.safetensors", - "size": "81.9MB" - }, - { - "name": "LTX-Video ICLoRA Detailer 13B v0.9.8", - "type": "lora", - "base": "LTX-Video", - "save_path": "loras", - "description": "A video detailer model on top of LTXV_13B_098_DEV trained on custom data using In-Context LoRA (IC LoRA) method.", - "reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-detailer-13b-0.9.8", - "filename": "ltxv-098-ic-lora-detailer-comfyui.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-detailer-13b-0.9.8/resolve/main/ltxv-098-ic-lora-detailer-comfyui.safetensors", - "size": "1.31GB" - }, - { - "name": "Latent Bridge Matching for Image Relighting", - "type": "diffusion_model", - "base": "LBM", - "save_path": "diffusion_models/LBM", - "description": "Latent Bridge Matching (LBM) Relighting model", - "reference": "https://huggingface.co/jasperai/LBM_relighting", - "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" - } - ] -} \ No newline at end of file diff --git a/node_db/.DS_Store b/node_db/.DS_Store new file mode 100644 index 00000000..12c6eab3 Binary files /dev/null and b/node_db/.DS_Store differ diff --git a/node_db/README.md b/node_db/README.md deleted file mode 100644 index 378845c5..00000000 --- a/node_db/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# ComfyUI-Manager: Node Database (node_db) - -This directory contains the JSON database files that power ComfyUI-Manager's legacy node registry system. While the manager is gradually transitioning to the online Custom Node Registry (CNR), these local JSON files continue to provide important metadata about custom nodes, models, and their integrations. - -## Directory Structure - -The node_db directory is organized into several subdirectories, each serving a specific purpose: - -- **dev/**: Development channel files with latest additions and experimental nodes -- **legacy/**: Historical/legacy nodes that may require special handling -- **new/**: New nodes that have passed initial verification but are still being evaluated -- **forked/**: Forks of existing nodes with modifications -- **tutorial/**: Example and tutorial nodes designed for learning purposes - -## Core Database Files - -Each subdirectory contains a standard set of JSON files: - -- **custom-node-list.json**: Primary database of custom nodes with metadata -- **extension-node-map.json**: Maps between extensions and individual nodes they provide -- **model-list.json**: Catalog of models that can be downloaded through the manager -- **alter-list.json**: Alternative implementations of nodes for compatibility or functionality -- **github-stats.json**: GitHub repository statistics for node popularity metrics - -## Database Schema - -### custom-node-list.json -```json -{ - "custom_nodes": [ - { - "title": "Node display name", - "name": "Repository name", - "reference": "Original repository if forked", - "files": ["GitHub URL or other source location"], - "install_type": "git", - "description": "Description of the node's functionality", - "pip": ["optional pip dependencies"], - "js": ["optional JavaScript files"], - "tags": ["categorization tags"] - } - ] -} -``` - -### extension-node-map.json -```json -{ - "extension-id": [ - ["list", "of", "node", "classes"], - { - "author": "Author name", - "description": "Extension description", - "nodename_pattern": "Optional regex pattern for node name matching" - } - ] -} -``` - -## Transition to Custom Node Registry (CNR) - -This local database system is being progressively replaced by the online Custom Node Registry (CNR), which provides: -- Real-time updates without manual JSON maintenance -- Improved versioning support -- Better security validation -- Enhanced metadata - -The Manager supports both systems simultaneously during the transition period. - -## Implementation Details - -- The database follows a channel-based architecture for different sources -- Multiple database modes are supported: Channel, Local, and Remote -- The system supports differential updates to minimize bandwidth usage -- Security levels are enforced for different node installations based on source - -## Usage in the Application - -The Manager's backend uses these database files to: - -1. Provide browsable lists of available nodes and models -2. Resolve dependencies for installation -3. Track updates and new versions -4. Map node classes to their source repositories -5. Assess risk levels for installation security - -## Maintenance Scripts - -Each subdirectory contains a `scan.sh` script that assists with: -- Scanning repositories for new nodes -- Updating metadata -- Validating database integrity -- Generating proper JSON structures - -This database system enables a flexible, secure, and comprehensive management system for the ComfyUI ecosystem while the transition to CNR continues. \ No newline at end of file diff --git a/node_db/dev/custom-node-list.json b/node_db/dev/custom-node-list.json deleted file mode 100644 index 77f572c2..00000000 --- a/node_db/dev/custom-node-list.json +++ /dev/null @@ -1,11387 +0,0 @@ -{ - "custom_nodes": [ - { - "author": "xWris3", - "title": "jus_multifruit_comfyui", - "reference": "https://github.com/xWris3/jus_multifruit_comfyui", - "files": [ - "https://github.com/xWris3/jus_multifruit_comfyui" - ], - "install_type": "git-clone", - "description": "Collection of custom nodes." - }, - { - "author": "synthetai", - "title": "ComfyUI-ToolBox [NAME CONFLICT]", - "reference": "https://github.com/synthetai/ComfyUI-ToolBox", - "files": [ - "https://github.com/synthetai/ComfyUI-ToolBox" - ], - "install_type": "git-clone", - "description": "A collection of utility nodes for ComfyUI, including audio/video processing, file uploads, and AI image generation." - }, - { - "author": "davidjshin1", - "title": "ComfyUI-NanobananaPro [NAME CONFLICT]", - "reference": "https://github.com/davidjshin1/ComfyUI-NanobananaPro", - "files": [ - "https://github.com/davidjshin1/ComfyUI-NanobananaPro" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node providing NanoBananaPro image generation functionality, likely a variant or extension of the Nano Banana Gemini-based image generation nodes. (Description by CC)" - }, - { - "author": "suburban123", - "title": "ComfyUI-DAP [NAME CONFLICT]", - "reference": "https://github.com/suburban123/ComfyUI-DAP", - "files": [ - "https://github.com/suburban123/ComfyUI-DAP" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for depth estimation using the DAP (Depth Anything Plus) model, with standard and advanced estimation modes and a dedicated model loader. (Description by CC)" - }, - { - "author": "thomashollier", - "title": "comfyUI-DAP [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/thomashollier/comfyUI-DAP", - "files": [ - "https://github.com/thomashollier/comfyUI-DAP" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for DAP (Depth Any Panoramas), DA-2 (Depth Anything in Any Direction), and PanDA (Panoramic Depth Anything) panoramic depth estimation[w/This nodepack has a path traversal vulnerability]" - }, - { - "author": "visualbruno", - "title": "ComfyUI-Tools [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/visualbruno/ComfyUI-Tools", - "files": [ - "https://github.com/visualbruno/ComfyUI-Tools" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node providing tools for FBX to SMPL renaming operations. (Description by CC)[w/This nodepack has a path traversal vulnerability]" - }, - { - "author": "DarioFT", - "title": "ComfyUI-Qwen3-TTS [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/DarioFT/ComfyUI-Qwen3-TTS", - "files": [ - "https://github.com/DarioFT/ComfyUI-Qwen3-TTS" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node suite for Qwen3-TTS, supporting 1.7B and 0.6B models, Custom Voice, Voice Design, Voice Cloning and Fine-Tuning.[w/This nodepack has a path traversal vulnerability]" - }, - { - "author": "ai-joe-git", - "title": "ComfyUI-Qwen3-TTS [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/ai-joe-git/ComfyUI-Qwen3-TTS", - "files": [ - "https://github.com/ai-joe-git/ComfyUI-Qwen3-TTS" - ], - "install_type": "git-clone", - "description": "High-quality Text-to-Speech nodes for ComfyUI using Qwen3-TTS models. Supports voice cloning, voice design, and custom voices with Intel Arc XPU optimization.\nNOTE: The files in the repo are not organized.[w/This nodepack has a path traversal vulnerability]" - }, - { - "author": "amenoyoya", - "title": "ComfyUI-Qwen3-TTS [NAME CONFLICT]", - "reference": "https://github.com/amenoyoya/ComfyUI-Qwen3-TTS", - "files": [ - "https://github.com/amenoyoya/ComfyUI-Qwen3-TTS" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node package for Qwen3-TTS, providing high-quality Text-to-Speech (TTS) with Custom Voice and Voice Cloning capabilities." - }, - { - "author": "NaomiVK", - "title": "comfyui-qwen3-tts [NAME CONFLICT]", - "reference": "https://github.com/NaomiVK/comfyui-qwen3-tts", - "files": [ - "https://github.com/NaomiVK/comfyui-qwen3-tts" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes integrating Qwen3-TTS for voice cloning, voice design from natural language descriptions, preset speakers, and multilingual speech synthesis. (Description by CC)" - }, - { - "author": "Meisoftcoltd", - "title": "ComfyUI-UVR5 [NAME CONFLICT]", - "reference": "https://github.com/Meisoftcoltd/ComfyUI-UVR5", - "files": [ - "https://github.com/Meisoftcoltd/ComfyUI-UVR5" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes integrating UVR5 (Ultimate Vocal Remover) for audio/video extraction and MDX-Net-based vocal separation, supporting batch processing and GPU-accelerated audio quality. (Description by CC)" - }, - { - "author": "mcaishao123", - "title": "ComfyUI-lut [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/mcaishao123/ComfyUI-lut", - "files": [ - "https://github.com/mcaishao123/ComfyUI-lut" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for loading and applying Adobe Lightroom .lrtemplate presets as image filters. Supports exposure, contrast, tone curves, HSL, split toning, vignette, grain, and more.[w/This nodepack has a path traversal vulnerability]" - }, - { - "author": "iloveuav", - "title": "ComfyUI-AudioTools [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/iloveuav/ComfyUI-AudioTools", - "files": [ - "https://github.com/iloveuav/ComfyUI-AudioTools" - ], - "install_type": "git-clone", - "description": "A ComfyUI audio extension providing HTTP upload/preview endpoints and nodes for reading/writing audio in workflows, enabling automation pipelines like N8N to integrate audio into ComfyUI. (Description by CC)\nNOTE: The files in the repo are not organized.[w/This nodepack has a path traversal vulnerability]" - }, - { - "author": "benjiyaya", - "title": "ComfyUI-Spectrum [WIP/NAME CONFLICT]", - "reference": "https://github.com/benjiyaya/ComfyUI-Spectrum", - "files": [ - "https://github.com/benjiyaya/ComfyUI-Spectrum" - ], - "install_type": "git-clone", - "description": "Training-free diffusion sampling acceleration by forecasting denoiser outputs with Chebyshev polynomials and ridge regression, works with any ComfyUI sampler and backbone.\nNOTE: The files in the repo are not organized." - }, - { - "author": "jinxishe", - "title": "ComfyUI-AudioX [NAME CONFLICT]", - "reference": "https://github.com/jinxishe/ComfyUI-AudioX", - "files": [ - "https://github.com/jinxishe/ComfyUI-AudioX" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for AudioX/AudioX-MAF video-to-audio generation" - }, - { - "author": "Ahmed791996", - "title": "SplatViwer_comfyUI", - "reference": "https://github.com/Ahmed791996/SplatViwer_comfyUI", - "files": [ - "https://github.com/Ahmed791996/SplatViwer_comfyUI" - ], - "install_type": "git-clone", - "description": "ComfyUI plugin for Gaussian splatting visualization. (Description by CC)" - }, - { - "author": "Apache0ne", - "title": "ComfyUI-SaveLoadConditioning", - "reference": "https://github.com/Apache0ne/ComfyUI-SaveLoadConditioning", - "files": [ - "https://github.com/Apache0ne/ComfyUI-SaveLoadConditioning" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for saving and loading conditioning data. (Description by CC)" - }, - { - "author": "Krish-701", - "title": "Comfyui_nodes_v01", - "reference": "https://github.com/Krish-701/Comfyui_nodes_v01", - "files": [ - "https://github.com/Krish-701/Comfyui_nodes_v01" - ], - "install_type": "git-clone", - "description": "Provides RKMultiImage custom nodes for ComfyUI. (Description by CC)" - }, - { - "author": "Momediada97", - "title": "[WIP] ComfyUI-ImageIfNotBlack", - "reference": "https://github.com/Momediada97/ComfyUI-ImageIfNotBlack", - "files": [ - "https://github.com/Momediada97/ComfyUI-ImageIfNotBlack" - ], - "install_type": "git-clone", - "description": "A lightweight custom node for ComfyUI that detects whether an input image is completely black.\nNOTE: The files in the repo are not organized." - }, - { - "author": "PauldeLavallaz", - "title": "comfyui-fabric-lipsync", - "reference": "https://github.com/PauldeLavallaz/comfyui-fabric-lipsync", - "files": [ - "https://github.com/PauldeLavallaz/comfyui-fabric-lipsync" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for lip-sync and text-to-video generation. (Description by CC)" - }, - { - "author": "StartHua", - "title": "Comfyui_yolo26 [WIP]", - "reference": "https://github.com/StartHua/Comfyui_yolo26", - "files": [ - "https://github.com/StartHua/Comfyui_yolo26" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for local YOLO object detection and pose estimation with mask generation and customizable body part selection.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Veritas-bit", - "title": "ComfyUI-StyleSelector [WIP]", - "reference": "https://github.com/Veritas-bit/ComfyUI-StyleSelector", - "files": [ - "https://github.com/Veritas-bit/ComfyUI-StyleSelector" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for style selection with styles folder configuration. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "ethanfel", - "title": "ComfyUI-CFG-CTRL [WIP]", - "reference": "https://github.com/ethanfel/ComfyUI-CFG-CTRL", - "files": [ - "https://github.com/ethanfel/ComfyUI-CFG-CTRL" - ], - "install_type": "git-clone", - "description": "A ComfyUI node implementing SMC-CFG (Sliding Mode Control CFG) that replaces standard linear CFG with a nonlinear sliding mode controller for stable guidance at any CFG scale. NOT WORKING - Work in progress. (Description by CC)" - }, - { - "author": "AiSatan", - "title": "ComfyUI_CSM [NAME CONFLICT]", - "reference": "https://github.com/AiSatan/ComfyUI_CSM", - "files": [ - "https://github.com/AiSatan/ComfyUI_CSM" - ], - "install_type": "git-clone", - "description": "A ComfyUI node for the CSM model featuring text-to-speech, voice cloning, and automatic model downloading from Hugging Face." - }, - { - "author": "unobtuse", - "title": "comfyui-topaz-ai-upscale", - "reference": "https://github.com/unobtuse/comfyui-topaz-ai-upscale", - "files": [ - "https://github.com/unobtuse/comfyui-topaz-ai-upscale" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for upscaling images using Topaz Photo AI's Autopilot engine via the CLI." - }, - { - "author": "unobtuse", - "title": "comfyui-topaz-gigapixel", - "reference": "https://github.com/unobtuse/comfyui-topaz-gigapixel", - "files": [ - "https://github.com/unobtuse/comfyui-topaz-gigapixel" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for upscaling images using Topaz Gigapixel AI's CLI." - }, - { - "author": "mozhaa", - "title": "ComfyUI-Latent-Transform", - "reference": "https://github.com/mozhaa/ComfyUI-Latent-Transform", - "files": [ - "https://github.com/mozhaa/ComfyUI-Latent-Transform" - ], - "install_type": "git-clone", - "description": "Latent space transformation nodes providing mathematical operations like add, blend, blur, gaussian noise, multiply, sharpen, and wave effects. (Description by CC)" - }, - { - "author": "Lumiyumi", - "title": "comfyui_stringcombineX", - "reference": "https://github.com/Lumiyumi/comfyui_stringcombineX", - "files": [ - "https://github.com/Lumiyumi/comfyui_stringcombineX" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for string combination with extensible class structure for custom variations. (Description by CC)" - }, - { - "author": "ayylmao1773", - "title": "ComfyUI-switch-aspect", - "reference": "https://github.com/ayylmao1773/ComfyUI-switch-aspect", - "files": [ - "https://github.com/ayylmao1773/ComfyUI-switch-aspect" - ], - "install_type": "git-clone", - "description": "ComfyUI node for automatic portrait/landscape aspect ratio switching based on input dimensions." - }, - { - "author": "tester4488", - "title": "mc_audio", - "reference": "https://github.com/tester4488/mc_audio", - "files": [ - "https://github.com/tester4488/mc_audio" - ], - "install_type": "git-clone", - "description": "mc audio info" - }, - - { - "author": "LiJT", - "title": "ComfyUI-JT_Tools", - "reference": "https://github.com/LiJT/ComfyUI-JT_Tools", - "files": [ - "https://github.com/LiJT/ComfyUI-JT_Tools" - ], - "install_type": "git-clone", - "description": "ComfyUI-JT_Tools custom nodes." - }, - { - "author": "ThanaritKanjanametawatAU", - "title": "comfyui_runninghub_nanobanana", - "reference": "https://github.com/ThanaritKanjanametawatAU/comfyui_runninghub_nanobanana", - "files": [ - "https://github.com/ThanaritKanjanametawatAU/comfyui_runninghub_nanobanana" - ], - "install_type": "git-clone", - "description": "RunningHub Nano Banana Pro API node for ComfyUI" - }, - { - "author": "DazzleNodes", - "title": "ComfyUI-DazzleSwitch", - "reference": "https://github.com/DazzleNodes/ComfyUI-DazzleSwitch", - "files": [ - "https://github.com/DazzleNodes/ComfyUI-DazzleSwitch" - ], - "install_type": "git-clone", - "description": "Smart switch node for ComfyUI with dropdown-based input selection and INT override for cascading workflows. Route any data type through a named dropdown instead of moving noodles." - }, - { - "author": "Echoflare", - "title": "ComfyUI-Reverse-Proxy-Fix", - "reference": "https://github.com/Echoflare/ComfyUI-Reverse-Proxy-Fix", - "files": [ - "https://github.com/Echoflare/ComfyUI-Reverse-Proxy-Fix" - ], - "install_type": "git-clone", - "description": "A custom node for fixing the \"405 Method Not Allowed\" issue on certain endpoints when reverse proxying ComfyUI." - }, - { - "author": "Ronnasayd", - "title": "comfyui-nodes", - "reference": "https://github.com/Ronnasayd/comfyui-nodes", - "files": [ - "https://github.com/Ronnasayd/comfyui-nodes" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI" - }, - { - "author": "vjumpkung", - "title": "comfyui-mosaic-blur [NAME CONFLICT]", - "reference": "https://github.com/vjumpkung/comfyui-mosaic-blur", - "files": [ - "https://github.com/vjumpkung/comfyui-mosaic-blur" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for applying mosaic blur effects. (Description by CC)" - }, - { - "author": "danieljanata", - "title": "ComfyUI-phone_compression_ultimate [WIP]", - "reference": "https://github.com/danieljanata/ComfyUI-phone_compression_ultimate", - "files": [ - "https://github.com/danieljanata/ComfyUI-phone_compression_ultimate" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that simulates realistic smartphone camera compression and processing artifacts.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Norsnow", - "title": "comfyui-image-ratio-preset [WIP]", - "reference": "https://github.com/Norsnow/comfyui-image-ratio-preset", - "files": [ - "https://github.com/Norsnow/comfyui-image-ratio-preset" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node for quickly setting image aspect ratios with presets and custom dimensions, directly generating latent tensors.\nNOTE: The files in the repo are not organized." - }, - { - "author": "svyatojdismas", - "title": "ComfyUI-StDismas [WIP]", - "reference": "https://github.com/svyatojdismas/ComfyUI-StDismas", - "files": [ - "https://github.com/svyatojdismas/ComfyUI-StDismas" - ], - "install_type": "git-clone", - "description": "Custom nodes for ComfyUI. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "OtwakO", - "title": "ComfyUI-UsefulTools", - "reference": "https://github.com/OtwakO/ComfyUI-UsefulTools", - "files": [ - "https://github.com/OtwakO/ComfyUI-UsefulTools" - ], - "install_type": "git-clone", - "description": "Extract tags from your original prompts by giving it a substring or an array of substrings." - }, - { - "author": "vito0131", - "title": "ComfyUI_SmartContentCrop [WIP]", - "reference": "https://github.com/vito0131/ComfyUI_SmartContentCrop", - "files": [ - "https://github.com/vito0131/ComfyUI_SmartContentCrop" - ], - "install_type": "git-clone", - "description": "Smart content cropping with circle processing capabilities. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "Vollond", - "title": "ComfyUI-LatentCutPlus", - "reference": "https://github.com/Vollond/ComfyUI-LatentCutPlus", - "files": [ - "https://github.com/Vollond/ComfyUI-LatentCutPlus" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node: LatentCutPlus (slice latents with amount=-1 to end)" - }, - { - "author": "gaonprime-labs", - "title": "nordy-custom-node-ComfyUI-NormalOverlapBandBlend", - "reference": "https://github.com/gaonprime-labs/nordy-custom-node-ComfyUI-NormalOverlapBandBlend", - "files": [ - "https://github.com/gaonprime-labs/nordy-custom-node-ComfyUI-NormalOverlapBandBlend" - ], - "install_type": "git-clone", - "description": "Normal map blending node for overlapping band effects in ComfyUI. (Description by CC)" - }, - { - "author": "muriellee1x", - "title": "ComfyUI-Mysterious-node3", - "reference": "https://github.com/muriellee1x/ComfyUI-Mysterious-node3", - "files": [ - "https://github.com/muriellee1x/ComfyUI-Mysterious-node3" - ], - "install_type": "git-clone", - "description": "Custom node VideoTimeRemapSpeedPresets for ComfyUI. (Description by CC)" - }, - { - "author": "muriellee1x", - "title": "ComfyUI-Mysterious-node2 [WIP]", - "reference": "https://github.com/muriellee1x/ComfyUI-Mysterious-node2", - "files": [ - "https://github.com/muriellee1x/ComfyUI-Mysterious-node2" - ], - "install_type": "git-clone", - "description": "Professional green/blue screen chroma keying nodes inspired by After Effects Keylight plugin, supporting automatic/manual key color detection and advanced spillage suppression. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "Mexes1978", - "title": "Film Photography Prompt Styler [WIP]", - "reference": "https://github.com/Mexes1978/film_prompt_styler", - "files": [ - "https://github.com/Mexes1978/film_prompt_styler" - ], - "install_type": "git-clone", - "description": "Photography history education tool node with 8 core categories including film stocks, lenses, lighting, genres, print styles, eras, technical processes, and photographer styles for intentional artistic choices.\nNOTE: The files in the repo are not organized." - }, - { - "author": "RaeZhLiu", - "title": "comfy_ark", - "reference": "https://github.com/RaeZhLiu/comfy_ark", - "files": [ - "https://github.com/RaeZhLiu/comfy_ark" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for Ark functionality. (Description by CC)" - }, - { - "author": "harishcmgit", - "title": "comfyui_sp [WIP]", - "reference": "https://github.com/harishcmgit/comfyui_sp", - "files": [ - "https://github.com/harishcmgit/comfyui_sp" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for seamless pattern generation. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "Omega-L", - "title": "comfyUI-prompts", - "reference": "https://github.com/Omega-L/comfyUI-prompts", - "files": [ - "https://github.com/Omega-L/comfyUI-prompts" - ], - "install_type": "git-clone", - "description": "ComfyUI Frontend Vue Basic is custom node that demonstrate how to use vue as frontend framework along with primevue and vue-i18n, cooperating with ComfyUI API. Needs ComfyUI Frontend 1.25.0 or later." - }, - { - "author": "vjumpkung", - "title": "comfyui-mosaic-blur [NAME CONFLICT]", - "reference": "https://github.com/vjumpkung/comfyui-mosaic-blur", - "files": [ - "https://github.com/vjumpkung/comfyui-mosaic-blur" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for applying mosaic blur effects. (Description by CC)" - }, - { - "author": "richardctw", - "title": "ComfyUI-ImageFillBackgroundColor", - "reference": "https://github.com/richardctw/ComfyUI-ImageFillBackgroundColor", - "files": [ - "https://github.com/richardctw/ComfyUI-ImageFillBackgroundColor" - ], - "install_type": "git-clone", - "description": "NODES: ImageFillBackgroundColor" - }, - { - "author": "zxq790909-maker", - "title": "ComfyUI-wangyi-OLED [WIP]", - "reference": "https://github.com/zxq790909-maker/ComfyUI-wangyi-OLED", - "files": [ - "https://github.com/zxq790909-maker/ComfyUI-wangyi-OLED" - ], - "install_type": "git-clone", - "description": "An extension that highlights the currently running node in ComfyUI with a strong neon outline and breathing effect for easier workflow navigation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "starsFriday", - "title": "ComfyUI-Qwen3-TTS [NAME CONFLICT/WIP]", - "reference": "https://github.com/starsFriday/ComfyUI-Qwen3-TTS", - "files": [ - "https://github.com/starsFriday/ComfyUI-Qwen3-TTS" - ], - "install_type": "git-clone", - "description": "Sound-related nodes for the Qwen3-TTS project\nNOTE: The files in the repo are not organized." - }, - - { - "author": "RicoMeng", - "title": "CartoonGenComfyui-PromtGenerator", - "reference": "https://github.com/RicoMeng/CartoonGenComfyui-PromtGenerator", - "files": [ - "https://github.com/RicoMeng/CartoonGenComfyui-PromtGenerator" - ], - "install_type": "git-clone", - "description": "Istanbul 80s Prompt Generator Node for ComfyUI. (Description by CC)" - }, - { - "author": "N3uR0TiCV0iD", - "title": "ComfyUI-ModelListTools", - "reference": "https://github.com/N3uR0TiCV0iD/ComfyUI-ModelListTools", - "files": [ - "https://github.com/N3uR0TiCV0iD/ComfyUI-ModelListTools" - ], - "install_type": "git-clone", - "description": "NODES: CLIPModelSelectionNode, CLIPVisionModelSelectionNode, CheckpointSelectionNode, DiffusionModelSelectionNode, FilteredCLIPModelsNode, ... (13 total)" - }, - - { - "author": "pollockjj", - "title": "ComfyUI-StabilityTest", - "reference": "https://github.com/pollockjj/ComfyUI-StabilityTest", - "files": [ - "https://github.com/pollockjj/ComfyUI-StabilityTest" - ], - "install_type": "git-clone", - "description": "ComfyUI stability testing node. (Description by CC)" - }, - { - "author": "Tr1dae", - "title": "[WIP] ComfyUI-MobileSAM", - "reference": "https://github.com/Tr1dae/ComfyUI-MobileSAM", - "files": [ - "https://github.com/Tr1dae/ComfyUI-MobileSAM" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node for text-guided image segmentation using GroundingDINO and MobileSAM to segment objects in images using natural language prompts.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Vov1ch", - "title": "ComfyUI_GLMImage", - "reference": "https://github.com/Vov1ch/ComfyUI_GLMImage", - "files": [ - "https://github.com/Vov1ch/ComfyUI_GLMImage" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for GLM image generation, image-to-image translation, and flexible input handling. (Description by CC)" - }, - { - "author": "DailyMok", - "title": "ComfyUI-PromptMixerNode", - "reference": "https://github.com/DailyMok/ComfyUI-PromptMixerNode", - "files": [ - "https://github.com/DailyMok/ComfyUI-PromptMixerNode" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for prompt mixing with PromptMixerDaily node. (Description by CC)" - }, - { - "author": "shin131002", - "title": "[WIP] ComfyUI-Prompt-Preset-Selector", - "reference": "https://github.com/shin131002/ComfyUI-Prompt-Preset-Selector", - "files": [ - "https://github.com/shin131002/ComfyUI-Prompt-Preset-Selector" - ], - "install_type": "git-clone", - "description": "Flexible preset selector with YAML support, advanced keyword filtering, and hierarchical key search\nNOTE: The files in the repo are not organized." - }, - { - "author": "fogyisland", - "title": "Comfy_Show_StringText [WIP]", - "reference": "https://github.com/fogyisland/Comfy_Show_StringText", - "files": [ - "https://github.com/fogyisland/Comfy_Show_StringText" - ], - "install_type": "git-clone", - "description": "ComfyUI node for text display that implements string data output. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "Leecoahs", - "title": "ComfyUI_LeeNodes", - "reference": "https://github.com/Leecoahs/ComfyUI_LeeNodes", - "files": [ - "https://github.com/Leecoahs/ComfyUI_LeeNodes" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for image processing including grayscale combining, overlaying, texture inpainting. (Description by CC)" - }, - { - "author": "tdrminglin", - "title": "Comfyui-hymotionbridge", - "reference": "https://github.com/tdrminglin/Comfyui-hymotionbridge", - "files": [ - "https://github.com/tdrminglin/Comfyui-hymotionbridge" - ], - "install_type": "git-clone", - "description": "NODES: HYMotionToNLFBridge, HYMotionToSCAILBridge" - }, - { - "author": "NakanoSanku", - "title": "ComfyUI-Gemini [NAME CONFLICT]", - "reference": "https://github.com/NakanoSanku/ComfyUI-Gemini", - "files": [ - "https://github.com/NakanoSanku/ComfyUI-Gemini" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes for Google Gemini AI with text generation and image generation capabilities using the Python SDK." - }, - { - "author": "Saganaki22", - "title": "ComfyUI-NovaSR", - "reference": "https://github.com/Saganaki22/ComfyUI-NovaSR", - "files": [ - "https://github.com/Saganaki22/ComfyUI-NovaSR" - ], - "install_type": "git-clone", - "description": "ComfyUI node for NovaSR - Ultra-fast audio super resolution (3600x realtime, 50KB model)" - }, - { - "author": "GokuHiki", - "title": "ComfyUI-Goku-Tools", - "reference": "https://github.com/GokuHiki/ComfyUI-Goku-Tools", - "files": [ - "https://github.com/GokuHiki/ComfyUI-Goku-Tools" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes including FirstNonFalse utility. (Description by CC)" - }, - { - "author": "endman100", - "title": "ComfyUI_tensor_script [WIP]", - "reference": "https://github.com/endman100/ComfyUI_tensor_script", - "files": [ - "https://github.com/endman100/ComfyUI_tensor_script" - ], - "install_type": "git-clone", - "description": "Utility nodes for tensor manipulation including squeeze, unsqueeze, and shape operations. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "nekotxt", - "title": "ComfyUI-NTX-support-nodes [WIP]", - "reference": "https://github.com/nekotxt/ComfyUI-NTX-support-nodes", - "files": [ - "https://github.com/nekotxt/ComfyUI-NTX-support-nodes" - ], - "install_type": "git-clone", - "description": "Utility nodes to support the creation of pipes\nNOTE: The files in the repo are not organized." - }, - { - "author": "killshotttttt", - "title": "ComfyUI-killshotttttt [WIP]", - "reference": "https://github.com/killshotttttt/ComfyUI-killshotttttt", - "files": [ - "https://github.com/killshotttttt/ComfyUI-killshotttttt" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that integrates with the Meshy AI API for Image-to-Image transformations using nano-banana models.\nNOTE: The files in the repo are not organized." - }, - { - "author": "bozkut", - "title": "ComfyUI-Prompt-Expander [NAME CONFLICT]", - "reference": "https://github.com/bozkut/ComfyUI-Prompt-Expander", - "files": [ - "https://github.com/bozkut/ComfyUI-Prompt-Expander" - ], - "install_type": "git-clone", - "description": "An LLM-powered prompt expansion node for ComfyUI. Transform simple prompts into detailed, high-quality image generation prompts." - }, - { - "author": "my-xz-org", - "title": "comfyui_xz_nodes", - "reference": "https://github.com/my-xz-org/comfyui_xz_nodes", - "files": [ - "https://github.com/my-xz-org/comfyui_xz_nodes" - ], - "install_type": "git-clone", - "description": "NODES: XZImageToText" - }, - { - "author": "tackcrypto1031", - "title": "[WIP] tk_comfyui_SimpleSize", - "reference": "https://github.com/tackcrypto1031/tk_comfyui_SimpleSize", - "files": [ - "https://github.com/tackcrypto1031/tk_comfyui_SimpleSize" - ], - "install_type": "git-clone", - "description": "A professional and intelligent aspect ratio and resolution selector for ComfyUI.\nNOTE: The files in the repo are not organized." - }, - { - "author": "hashms0a", - "title": "ComfyUI-Qwen-Multi-Angle-Camera-Nodes [WIP]", - "reference": "https://github.com/hashms0a/ComfyUI-Qwen-Multi-Angle-Camera-Nodes", - "files": [ - "https://github.com/hashms0a/ComfyUI-Qwen-Multi-Angle-Camera-Nodes" - ], - "install_type": "git-clone", - "description": "Comprehensive custom nodes for controlling camera angles with Qwen-Image-Edit-2511-Multiple-Angles-LoRA, supporting all 96 camera positions with basic/advanced selection, orbital animation, elevation sweep, and pre-defined animation paths.\nNOTE: The files in the repo are not organized." - }, - { - "author": "jceme", - "title": "Comfy_Extensions [WIP]", - "reference": "https://github.com/jceme/Comfy_Extensions", - "files": [ - "https://github.com/jceme/Comfy_Extensions" - ], - "install_type": "git-clone", - "description": "ComfyUI extension pack offering image dimension handling, aspect ratio presets, FPS utilities, inpainting, and prompt customization. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "saltchicken", - "title": "ComfyUI-Interactive-Cropper", - "reference": "https://github.com/saltchicken/ComfyUI-Interactive-Cropper", - "files": [ - "https://github.com/saltchicken/ComfyUI-Interactive-Cropper" - ], - "install_type": "git-clone", - "description": "Interactive image cropping node with visual interface. (Description by CC)" - }, - { - "author": "shenymce", - "title": "ComfyUI-JsonViewer [WIP]", - "reference": "https://github.com/shenymce/ComfyUI-JsonViewer", - "files": [ - "https://github.com/shenymce/ComfyUI-JsonViewer" - ], - "install_type": "git-clone", - "description": "OpenPose JSON data visualization for displaying pose keypoint data exported from ComfyUI workflow nodes. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "qimi-dev", - "title": "ComfyUI-Qimi-Tiler", - "reference": "https://github.com/qimi-dev/ComfyUI-Qimi-Tiler", - "files": [ - "https://github.com/qimi-dev/ComfyUI-Qimi-Tiler" - ], - "install_type": "git-clone", - "description": "Image tiling utility nodes for splitting and combining image tiles in ComfyUI. (Description by CC)" - }, - { - "author": "maTORIx", - "title": "ComfyUI-KeypointsToImage", - "reference": "https://github.com/maTORIx/ComfyUI-KeypointsToImage", - "files": [ - "https://github.com/maTORIx/ComfyUI-KeypointsToImage" - ], - "install_type": "git-clone", - "description": "Node for converting keypoints to images. (Description by CC)" - }, - - { - "author": "IO-AtelierTech", - "title": "comfyui-video-utils [NAME CONFLICT]", - "reference": "https://github.com/IO-AtelierTech/comfyui-video-utils", - "files": [ - "https://github.com/IO-AtelierTech/comfyui-video-utils" - ], - "install_type": "git-clone", - "description": "Provides utility nodes for video operations, particularly for FFmpeg integration and workflow connectivity." - }, - { - "author": "PozzettiAndrea", - "title": "ComfyUI-TRELLIS2 [NAME CONFLICT]", - "reference": "https://github.com/PozzettiAndrea/ComfyUI-TRELLIS2", - "files": [ - "https://github.com/PozzettiAndrea/ComfyUI-TRELLIS2" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for TRELLIS.2 - Microsoft's image-to-3D generation model for creating high-quality 3D meshes with PBR materials." - }, - { - "author": "colorAi", - "title": "comfyui-prompt-manager [NAME CONFLICT]", - "reference": "https://github.com/colorAi/comfyui-prompt-manager", - "files": [ - "https://github.com/colorAi/comfyui-prompt-manager" - ], - "install_type": "git-clone", - "description": "A modern, powerful, and easy-to-use Prompt Manager extension for ComfyUI." - }, - { - "author": "Yukinoshita-Yukinoe", - "title": "ComfyUI-SenseVoice [NAME CONFLICT]", - "reference": "https://github.com/Yukinoshita-Yukinoe/ComfyUI-SenseVoice", - "files": [ - "https://github.com/Yukinoshita-Yukinoe/ComfyUI-SenseVoice" - ], - "install_type": "git-clone", - "description": "Audio processing node providing SenseVoice speech recognition, audio splitting, and SRT generation for ComfyUI. (Description by CC)" - }, - { - "author": "satyam-fp", - "title": "ComfyUI-GeminiImage [NAME CONFLICT]", - "reference": "https://github.com/satyam-fp/ComfyUI-GeminiImage", - "files": [ - "https://github.com/satyam-fp/ComfyUI-GeminiImage" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node package that integrates Google Gemini API for AI-powered image generation and enhancement." - }, - { - "author": "ai-joe-git", - "title": "ComfyUI-Chatterbox [NAME CONFLICT]", - "reference": "https://github.com/ai-joe-git/ComfyUI-Chatterbox", - "files": [ - "https://github.com/ai-joe-git/ComfyUI-Chatterbox" - ], - "install_type": "git-clone", - "description": "Chatterbox TTS integration for ComfyUI - Voice cloning with 3 model variants." - }, - { - "author": "Sergey004", - "title": "ComfyUI-Telegram-Sender [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/Sergey004/ComfyUI-Telegram-Sender", - "files": [ - "https://github.com/Sergey004/ComfyUI-Telegram-Sender" - ], - "install_type": "git-clone", - "description": "Does the same thing as comfyui_image_metadata_extension but is slightly more modern, sending the output to Telegram and downloading unnecessary metadata at the same time.[w/This nodepack contains a path traversal vulnerability.]" - }, - { - "author": "cedarconnor", - "title": "ComfyUI-DAP [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/cedarconnor/ComfyUI-DAP", - "files": [ - "https://github.com/cedarconnor/ComfyUI-DAP" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for DAP (Depth Any Panoramas) - panoramic depth estimation[w/This nodepack contains a path traversal vulnerability.]" - }, - { - "author": "AhiruNeko", - "title": "ComfyUI-MiniTools [UNSAFE/NAME CONFLICT]", - "reference": "https://github.com/AhiruNeko/ComfyUI-MiniTools", - "files": [ - "https://github.com/AhiruNeko/ComfyUI-MiniTools" - ], - "install_type": "git-clone", - "description": "A ComfyUI plugins[w/This nodepack contains a path traversal vulnerability.]" - }, - { - "author": "861289009", - "title": "comfyui_video_node [WIP]", - "reference": "https://github.com/861289009/comfyui_video_node", - "files": [ - "https://github.com/861289009/comfyui_video_node" - ], - "install_type": "git-clone", - "description": "A ComfyUI node for creating smooth fade transitions between two video segments using hex color effects. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "w3rc", - "title": "lpips-similarity-comfyui", - "reference": "https://github.com/w3rc/lpips-similarity-comfyui", - "files": [ - "https://github.com/w3rc/lpips-similarity-comfyui" - ], - "install_type": "git-clone", - "description": "NODES: GetSimilarity, LPIPSSimilarity" - }, - { - "author": "StevenBaby", - "title": "comfyui-tools", - "reference": "https://github.com/StevenBaby/comfyui-tools", - "files": [ - "https://github.com/StevenBaby/comfyui-tools" - ], - "install_type": "git-clone", - "description": "IntParameterNode: A Node contain 4 int parameters to quick switch input to other nodes." - }, - { - "author": "zhu798542746", - "title": "comfyui_model [UNSAFE]", - "reference": "https://github.com/zhu798542746/comfyui_model", - "files": [ - "https://github.com/zhu798542746/comfyui_model" - ], - "install_type": "git-clone", - "description": "A powerful tool for managing and exporting ComfyUI plugins and models with an elegant side panel interface. (Description by CC) [w/This node pack has a vulnerability that allows it to access (or exfiltrate) data from custom nodes installed remotely.]" - }, - { - "author": "simonri", - "title": "ComfyUI-SimonNodes", - "reference": "https://github.com/simonri/ComfyUI-SimonNodes", - "files": [ - "https://github.com/simonri/ComfyUI-SimonNodes" - ], - "install_type": "git-clone", - "description": "Provides seed upscaling and image cropping utilities for ComfyUI workflows. (Description by CC)" - }, - { - "author": "j-pyxal", - "title": "ComfyUI-Lattice-Manim [WIP]", - "reference": "https://github.com/j-pyxal/ComfyUI-Lattice-Manim", - "files": [ - "https://github.com/j-pyxal/ComfyUI-Lattice-Manim" - ], - "install_type": "git-clone", - "description": "A ComfyUI extension integrating Manim for animation rendering.\nNOTE: The files in the repo are not organized." - }, - { - "author": "kanttouchthis", - "title": "ComfyUI-SDNQ [NAME CONFLICT]", - "reference": "https://github.com/kanttouchthis/ComfyUI-SDNQ", - "files": [ - "https://github.com/kanttouchthis/ComfyUI-SDNQ" - ], - "install_type": "git-clone", - "description": "SDNQ support for ComfyUI." - }, - { - "author": "SparknightLLC", - "title": "ComfyUI-GraphConstantFolder", - "reference": "https://github.com/SparknightLLC/ComfyUI-GraphConstantFolder", - "files": [ - "https://github.com/SparknightLLC/ComfyUI-GraphConstantFolder" - ], - "install_type": "git-clone", - "description": "Improves performance of large workflows by rewriting the submitted prompt graph before validation to constant-fold switch/selector nodes and optionally prune now-unreachable branches." - }, - { - "author": "bryanlholland1", - "title": "comfyui-app-bridge [WIP]", - "reference": "https://github.com/bryanlholland1/comfyui-app-bridge", - "files": [ - "https://github.com/bryanlholland1/comfyui-app-bridge" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for sending images to the companion iOS/macOS/visionOS app\nNOTE: The files in the repo are not organized." - }, - { - "author": "HalfADog", - "title": "ComfyUI-Mask2JSON", - "reference": "https://github.com/HalfADog/ComfyUI-Mask2JSON", - "files": [ - "https://github.com/HalfADog/ComfyUI-Mask2JSON" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for converting masks to contour JSON format with visualization capabilities. (Description by CC)" - }, - { - "author": "flywhale-666", - "title": "ComfyUI_pixel_snapping [WIP]", - "reference": "https://github.com/flywhale-666/ComfyUI_pixel_snapping", - "files": [ - "https://github.com/flywhale-666/ComfyUI_pixel_snapping" - ], - "install_type": "git-clone", - "description": "SIFT-based image alignment, intelligent mask cropping and restoration for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "fangg2000", - "title": "comfyui_fgtools [WIP]", - "reference": "https://github.com/fangg2000/comfyui_fgtools", - "files": [ - "https://github.com/fangg2000/comfyui_fgtools" - ], - "install_type": "git-clone", - "description": "Personal utility tools for ComfyUI. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "HailXD", - "title": "comfyui-random-artist", - "reference": "https://github.com/HailXD/comfyui-random-artist", - "files": [ - "https://github.com/HailXD/comfyui-random-artist" - ], - "install_type": "git-clone", - "description": "Generates random artist styles for ComfyUI workflows. (Description by CC)" - }, - { - "author": "wandaijin", - "title": "ComfyUI-PaddleOCR [NAME CONFLICT]", - "reference": "https://github.com/wandaijin/ComfyUI-PaddleOCR", - "files": [ - "https://github.com/wandaijin/ComfyUI-PaddleOCR" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI" - }, - { - "author": "supaidauen", - "title": "ComfyUI-supaidauen [WIP]", - "reference": "https://github.com/supaidauen/ComfyUI-supaidauen", - "files": [ - "https://github.com/supaidauen/ComfyUI-supaidauen" - ], - "install_type": "git-clone", - "description": "Collection of custom ComfyUI nodes including VRAM management, image processing, latent manipulation, character I/O, and advanced sampling utilities. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "huhu-tiger", - "title": "ComfyUI-RemoteResource", - "reference": "https://github.com/huhu-tiger/ComfyUI-RemoteResource", - "files": [ - "https://github.com/huhu-tiger/ComfyUI-RemoteResource" - ], - "install_type": "git-clone", - "description": "ComfyUI node for loading images from remote sources. (Description by CC)" - }, - { - "author": "nomadop", - "title": "ComfyUI-Video-Matting [NAME CONFLICT]", - "reference": "https://github.com/nomadop/ComfyUI-Video-Matting", - "files": [ - "https://github.com/nomadop/ComfyUI-Video-Matting" - ], - "install_type": "git-clone", - "description": "Modular video matting nodes supporting multiple models (RVM, MODNet, U2Net, RMBG-2.0) and video object segmentation with Cutie and edge refinement via ViTMatte." - }, - { - "author": "agavesunset", - "title": "Comfyui_SiliconFlow_AgaveSunset", - "reference": "https://github.com/agavesunset/Comfyui_SiliconFlow_AgaveSunset", - "files": [ - "https://github.com/agavesunset/Comfyui_SiliconFlow_AgaveSunset" - ], - "install_type": "git-clone", - "description": "NODES: SiliconFlowLoader_AS, SiliconFlowSampler_AS" - }, - { - "author": "IIEleven11", - "title": "[WIP] ComfyUI-Dataset_Maker", - "reference": "https://github.com/IIEleven11/ComfyUI-Dataset_Maker", - "files": [ - "https://github.com/IIEleven11/ComfyUI-Dataset_Maker" - ], - "install_type": "git-clone", - "description": "Custom node pack that automates dataset creation by generating images for concept lists, each with a specific LoRA. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "bhaveek424", - "title": "ComfyUI-HMNodes", - "reference": "https://github.com/bhaveek424/ComfyUI-HMNodes", - "files": [ - "https://github.com/bhaveek424/ComfyUI-HMNodes" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for audio processing, image enhancement, and AI-powered prompting including FFT analysis, automatic white balance, lens effects, and realism optimization. (Description by CC)" - }, - { - "author": "raohammad", - "title": "ComfyUI-VTUtilNodes [WIP]", - "reference": "https://github.com/raohammad/ComfyUI-VTUtilNodes", - "files": [ - "https://github.com/raohammad/ComfyUI-VTUtilNodes" - ], - "install_type": "git-clone", - "description": "A collection of utility custom nodes for ComfyUI.\nNOTE: The files in the repo are not organized." - }, - { - "author": "yamanacn", - "title": "ComfyUI-ImageMask-Random-Sync-Picker", - "reference": "https://github.com/yamanacn/ComfyUI-ImageMask-Random-Sync-Picker", - "files": [ - "https://github.com/yamanacn/ComfyUI-ImageMask-Random-Sync-Picker" - ], - "install_type": "git-clone", - "description": "Node for randomly selecting and synchronizing image masks with selectable options. (Description by CC)" - }, - { - "author": "tdrminglin", - "title": "ComfyUI_SceneSplitter", - "reference": "https://github.com/tdrminglin/ComfyUI_SceneSplitter", - "files": [ - "https://github.com/tdrminglin/ComfyUI_SceneSplitter" - ], - "install_type": "git-clone", - "description": "Scene detection and splitting nodes for ComfyUI enabling frame-level scene detection and start frame tracking. (Description by CC)" - }, - { - "author": "starsFriday", - "title": "ComfyUI-KLingAI-OmniVideo [WIP]", - "reference": "https://github.com/starsFriday/ComfyUI-KLingAI-OmniVideo", - "files": [ - "https://github.com/starsFriday/ComfyUI-KLingAI-OmniVideo" - ], - "install_type": "git-clone", - "description": "Five API nodes for KLingAI's OmniVideo (O1) usage\nNOTE: The files in the repo are not organized." - }, - { - "author": "Hifunyo", - "title": "comfyui_google_ai", - "reference": "https://github.com/Hifunyo/comfyui_google_ai", - "files": [ - "https://github.com/Hifunyo/comfyui_google_ai" - ], - "install_type": "git-clone", - "description": "ComfyUI integration node for Google AI image generation capabilities. (Description by CC)" - }, - { - "author": "nschpy", - "title": "ComfyUI_MovisAdapter [UNSAFE]", - "reference": "https://github.com/nschpy/ComfyUI_MovisAdapter", - "files": [ - "https://github.com/nschpy/ComfyUI_MovisAdapter" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "devzeroLL", - "title": "comfyui-lxj-Node", - "reference": "https://github.com/devzeroLL/comfyui-lxj-Node", - "files": [ - "https://github.com/devzeroLL/comfyui-lxj-Node" - ], - "install_type": "git-clone", - "description": "NODES: lxj_ImageBatch14, lxj_TextBatch14" - }, - { - "author": "hgh086", - "title": "Comfyui-HghImage", - "reference": "https://github.com/hgh086/Comfyui-HghImage", - "files": [ - "https://github.com/hgh086/Comfyui-HghImage" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for image comparison functionality. (Description by CC)" - }, - { - "author": "Ginolazy", - "title": "ComfyUI-FluxKontextImageCompensate [WIP]", - "reference": "https://github.com/Ginolazy/ComfyUI-FluxKontextImageCompensate", - "files": [ - "https://github.com/Ginolazy/ComfyUI-FluxKontextImageCompensate" - ], - "install_type": "git-clone", - "description": "A focused ComfyUI plugin to handle the vertical stretching issue introduced by the Flux Kontext model.\nNOTE: The files in the repo are not organized." - }, - { - "author": "IO-AtelierTech", - "title": "comfyui-genai-connectors [WIP]", - "reference": "https://github.com/IO-AtelierTech/comfyui-genai-connectors", - "files": [ - "https://github.com/IO-AtelierTech/comfyui-genai-connectors" - ], - "install_type": "git-clone", - "description": "The ComfyUI-fal-Connector is a tool designed to provide an integration between ComfyUI and fal. This extension allows users to execute their ComfyUI workflows directly on [a/fal.ai](https://fal.ai/). This enables users to leverage the computational power and resources provided by fal.ai for running their ComfyUI workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "PozzettiAndrea", - "title": "ComfyUI-MVDUST3R [UNSAFE]", - "reference": "https://github.com/PozzettiAndrea/ComfyUI-MVDUST3R", - "files": [ - "https://github.com/PozzettiAndrea/ComfyUI-MVDUST3R" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for MVDUST3R multi-view 3D reconstruction[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "ProjectAtlantis-dev", - "title": "comfyui-atlantis-json [UNSAFE]", - "reference": "https://github.com/ProjectAtlantis-dev/comfyui-atlantis-json", - "files": [ - "https://github.com/ProjectAtlantis-dev/comfyui-atlantis-json" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes for JSON processing and transcription workflows, including text-to-JSON conversion, SRT subtitle parsing, and file saving. (Description by CC)[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "ShammiG", - "title": "ComfyUI_Text_Tools_SG [UNSAFE]", - "reference": "https://github.com/ShammiG/ComfyUI_Text_Tools_SG", - "files": [ - "https://github.com/ShammiG/ComfyUI_Text_Tools_SG" - ], - "install_type": "git-clone", - "description": "Text Editor node with Markdown editing plus quick shortcuts, Text Viewer node, with extra features plus Text Merge, Text Save and Load Text from anywhere nodes.[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "Smyshnikof", - "title": "ComfyUI-PresetDownloadManager [UNSAFE]", - "reference": "https://github.com/Smyshnikof/ComfyUI-PresetDownloadManager", - "files": [ - "https://github.com/Smyshnikof/ComfyUI-PresetDownloadManager" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node for managing and downloading models from HuggingFace with preset support[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "gulajawalegit", - "title": "ComfyUI-Telegram-Sender [UNSAFE]", - "reference": "https://github.com/gulajawalegit/ComfyUI-Telegram-Sender", - "files": [ - "https://github.com/gulajawalegit/ComfyUI-Telegram-Sender" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for sending generated videos to Telegram, enabling direct output sharing to messaging platforms. (Description by CC)[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "Laolilzp", - "title": "Laoli3D [UNSAFE]", - "reference": "https://github.com/Laolilzp/Laoli3D", - "files": [ - "https://github.com/Laolilzp/Laoli3D" - ], - "install_type": "git-clone", - "description": "ComfyUI 3D pose editor enabling visual character manipulation and ControlNet image generation for precise AI figure control without prompt description. (Description by CC)[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "Goldlionren", - "title": "ComfyUI_Bridge_fabric [UNSAFE]", - "reference": "https://github.com/Goldlionren/ComfyUI_Bridge_fabric", - "files": [ - "https://github.com/Goldlionren/ComfyUI_Bridge_fabric" - ], - "install_type": "git-clone", - "description": "AI Compute Fabric bridge for ComfyUI enabling distributed CLIP, VAE, and ControlNet inference across local and remote machines. (Description by CC)[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "CypherNaught-0x", - "title": "ComfyUI-StarVector [WIP]", - "reference": "https://github.com/CypherNaught-0x/ComfyUI-StarVector", - "files": [ - "https://github.com/CypherNaught-0x/ComfyUI-StarVector" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for SVG generation using StarVector models\nNOTE: The files in the repo are not organized." - }, - { - "author": "Clivey1234", - "title": "ComfyUI_FBX_Import [UNSAFE]", - "reference": "https://github.com/Clivey1234/ComfyUI_FBX_Import", - "files": [ - "https://github.com/Clivey1234/ComfyUI_FBX_Import" - ], - "install_type": "git-clone", - "description": "Convert FBX animations into ControlNet OpenPose images for driving AI video generation with motion from any animation source. (Description by CC)[w/This nodepack has a vulnerability that allows arbitrary code execution remotely.]" - }, - { - "author": "TobiasGlaubach", - "title": "ComfyUI-TG_PyCode [UNSAFE]", - "reference": "https://github.com/TobiasGlaubach/ComfyUI-TG_PyCode", - "files": [ - "https://github.com/TobiasGlaubach/ComfyUI-TG_PyCode" - ], - "install_type": "git-clone", - "description": "ComfyUI node library with an editor and nodes to run Python code within ComfyUI workflows.[w/This nodepack has a vulnerability that allows arbitrary code execution remotely.]" - }, - { - "author": "jchiotaka", - "title": "ComfyUI-ClarityAI-Upscaler", - "reference": "https://github.com/jchiotaka/ComfyUI-ClarityAI-Upscaler", - "files": [ - "https://github.com/jchiotaka/ComfyUI-ClarityAI-Upscaler" - ], - "install_type": "git-clone", - "description": "ComfyUI upscaler nodes including ClarityCreativeUpscaler, ClarityCrystalUpscaler, and ClarityFluxUpscaler. (Description by CC)" - }, - { - "author": "tpc2233", - "title": "ComfyUI-TP-IMtalker [WIP]", - "reference": "https://github.com/tpc2233/ComfyUI-TP-IMtalker", - "files": [ - "https://github.com/tpc2233/ComfyUI-TP-IMtalker" - ], - "install_type": "git-clone", - "description": "Comfy UI nodes for IMtalker to run native weights.)\nNOTE: The files in the repo are not organized." - }, - { - "author": "yuyu0218yu", - "title": "comfyui-NXCM-tool [UNSAFE]", - "reference": "https://github.com/yuyu0218yu/comfyui-NXCM-tool", - "files": [ - "https://github.com/yuyu0218yu/comfyui-NXCM-tool" - ], - "install_type": "git-clone", - "description": "Next-generation creative media encryption toolkit for ComfyUI providing image and video encryption with AES-256-CTR + HMAC-SHA256, metadata preservation, and batch processing support. (Description by CC) [w/hardcoded encryption key]" - }, - { - "author": "SergeyKarleev", - "title": "[WIP] comfyui-textutils", - "reference": "https://github.com/SergeyKarleev/comfyui-textutils", - "files": [ - "https://github.com/SergeyKarleev/comfyui-textutils" - ], - "install_type": "git-clone", - "description": "Small utility nodes for ComfyUI text workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "love530love", - "title": "[WIP] ComfyUI-TorchMonitor", - "reference": "https://github.com/love530love/ComfyUI-TorchMonitor", - "files": [ - "https://github.com/love530love/ComfyUI-TorchMonitor" - ], - "install_type": "git-clone", - "description": "Fixed-position real-time monitor for ComfyUI displaying CPU, RAM, VRAM, and GPU temperature metrics with zero configuration and single-file installation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Enferlain", - "title": "ComfyUI-SamplerCustom-3Decimals", - "reference": "https://github.com/Enferlain/ComfyUI-SamplerCustom-3Decimals", - "files": [ - "https://github.com/Enferlain/ComfyUI-SamplerCustom-3Decimals" - ], - "install_type": "git-clone", - "description": "ComfyUI sampler node with custom 3 decimals precision for sampling parameters. (Description by CC)" - }, - { - "author": "lfelipegg", - "title": "[WIP] lfgg_custom_nodes_comfyui", - "reference": "https://github.com/lfelipegg/lfgg_custom_nodes_comfyui", - "files": [ - "https://github.com/lfelipegg/lfgg_custom_nodes_comfyui" - ], - "install_type": "git-clone", - "description": "LFGG custom nodes for ComfyUI providing resolution-first utilities, latent sizing by aspect ratio, and divisibility-aware image processing.\nNOTE: The files in the repo are not organized." - }, - { - "author": "saltchicken", - "title": "ComfyUI-Selector", - "reference": "https://github.com/saltchicken/ComfyUI-Selector", - "files": [ - "https://github.com/saltchicken/ComfyUI-Selector" - ], - "install_type": "git-clone", - "description": "ComfyUI node providing simple selector functionality. (Description by CC)" - }, - { - "author": "saltchicken", - "title": "ComfyUI-Video-Utils", - "reference": "https://github.com/saltchicken/ComfyUI-Video-Utils", - "files": [ - "https://github.com/saltchicken/ComfyUI-Video-Utils" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for video processing including FinalFrameSelector and VideoMerge functionality. (Description by CC)" - }, - { - "author": "EricRorich", - "title": "[WIP] ComfyUI-MegaTran-cutom-node", - "reference": "https://github.com/EricRorich/ComfyUI-MegaTran-cutom-node", - "files": [ - "https://github.com/EricRorich/ComfyUI-MegaTran-cutom-node" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node applying image transformations (scaling, rotation, translation) around a pivot point with optional canvas expansion and pivot visualization. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "OhSeongHyeon", - "title": "comfyui-random-image-size", - "reference": "https://github.com/OhSeongHyeon/comfyui-random-image-size", - "files": [ - "https://github.com/OhSeongHyeon/comfyui-random-image-size" - ], - "install_type": "git-clone", - "description": "ComfyUI Random Image Size" - }, - { - "author": "Enferlain", - "title": "ComfyUI-Model-Comparison [WIP]", - "reference": "https://github.com/Enferlain/ComfyUI-Model-Comparison", - "files": [ - "https://github.com/Enferlain/ComfyUI-Model-Comparison" - ], - "install_type": "git-clone", - "description": "ComfyUI node for comparing multiple models side-by-side. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "AMTPorn", - "title": "comfyui_amt", - "reference": "https://github.com/AMTPorn/comfyui_amt", - "files": [ - "https://github.com/AMTPorn/comfyui_amt" - ], - "install_type": "git-clone", - "description": "NODES: AMTStringDeduplication" - }, - { - "author": "gajjar4", - "title": "ComfyUI-Qwen-Image-i2L [UNSAFE]", - "reference": "https://github.com/gajjar4/ComfyUI-Qwen-Image-i2L", - "files": [ - "https://github.com/gajjar4/ComfyUI-Qwen-Image-i2L" - ], - "install_type": "git-clone", - "description": "A fully optimized ComfyUI custom node for Qwen-Image-i2L (Image-to-LoRA) that extracts style, composition, or details from images and saves them as lightweight LoRA files with intelligent VRAM optimization. (Description by CC)[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "edvardtoth", - "title": "ComfyUI-ETNodes", - "reference": "https://github.com/edvardtoth/ComfyUI-ETNodes", - "files": [ - "https://github.com/edvardtoth/ComfyUI-ETNodes" - ], - "install_type": "git-clone", - "description": "NODES: ETNodes-Color-Selector, ETNodes-Gemini-API-Image, ETNodes-Gemini-API-Text, ETNodes-List-Items, ETNodes-List-Selector, ETNodes-Text-Previe" - }, - { - "author": "jtydhr88", - "title": "ComfyUI-PolotnoCanvasEditor [UNSAFE]", - "reference": "https://github.com/jtydhr88/ComfyUI-PolotnoCanvasEditor", - "files": [ - "https://github.com/jtydhr88/ComfyUI-PolotnoCanvasEditor" - ], - "install_type": "git-clone", - "description": "Integrates Polotno Canvas Editor into ComfyUI for advanced image editing and design.[w/This nodepack contains a path traversal vulnerability.]" - }, - { - "author": "Taremin", - "title": "comfyui-remove-print", - "reference": "https://github.com/Taremin/comfyui-remove-print", - "files": [ - "https://github.com/Taremin/comfyui-remove-print" - ], - "install_type": "git-clone", - "description": "ComfyUI extension for removing or suppressing print statements in node output. (Description by CC)" - }, - { - "author": "JiangAogo", - "title": "ComfyUI-Gemini-API [WIP]", - "reference": "https://github.com/JiangAogo/ComfyUI-Gemini-API", - "files": [ - "https://github.com/JiangAogo/ComfyUI-Gemini-API" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for Google Gemini API integration, supporting both text generation (LLM) and image generation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "AprEcho", - "title": "ComfyUI-RandomSeed", - "reference": "https://github.com/AprEcho/ComfyUI-RandomSeed", - "files": [ - "https://github.com/AprEcho/ComfyUI-RandomSeed" - ], - "install_type": "git-clone", - "description": "Generates random seed values for ComfyUI workflows. (Description by CC)" - }, - { - "author": "Suzu008", - "title": "ComfyUI-ImageCritic", - "reference": "https://github.com/Suzu008/ComfyUI-ImageCritic", - "files": [ - "https://github.com/Suzu008/ComfyUI-ImageCritic" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for image analysis and evaluation. (Description by CC)" - }, - { - "author": "Spicely", - "title": "[WIP] ComfyUI-Luma", - "reference": "https://github.com/Spicely/ComfyUI-Luma", - "files": [ - "https://github.com/Spicely/ComfyUI-Luma" - ], - "install_type": "git-clone", - "description": "ComfyUI utility providing video and audio processing capabilities including text watermarking, audio-video separation, and audio-to-subtitle conversion. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "SSYCloud", - "title": "comfyui-ssy-syncapi [WIP]", - "reference": "https://github.com/SSYCloud/comfyui-ssy-syncapi", - "files": [ - "https://github.com/SSYCloud/comfyui-ssy-syncapi" - ], - "install_type": "git-clone", - "description": "Powerful ComfyUI custom node collection providing 4 dedicated nodes to access SSY Cloud synchronous image generation and processing models including Google Gemini, ByteDance Doubao, OpenAI, and image enhancement APIs. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "LMietkiewicz", - "title": "HiggsfieldAPI_Node", - "reference": "https://github.com/LMietkiewicz/HiggsfieldAPI_Node", - "files": [ - "https://github.com/LMietkiewicz/HiggsfieldAPI_Node" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for Higgsfield API integration. (Description by CC)" - }, - { - "author": "starsFriday", - "title": "ComfyUI-LongCat-Image [WIP]", - "reference": "https://github.com/starsFriday/ComfyUI-LongCat-Image", - "files": [ - "https://github.com/starsFriday/ComfyUI-LongCat-Image" - ], - "install_type": "git-clone", - "description": "ComfyUI wrapper nodes for the LongCat-Image text-to-image and image-editing pipelines with Python 3.12 support.\nNOTE: The files in the repo are not organized." - }, - { - "author": "logicalor", - "title": "comfyui_mv_adapter [WIP]", - "reference": "https://github.com/logicalor/comfyui_mv_adapter", - "files": [ - "https://github.com/logicalor/comfyui_mv_adapter" - ], - "install_type": "git-clone", - "description": "MV-Adapter nodes for ComfyUI - Multi-view image generation\nNOTE: The files in the repo are not organized." - }, - { - "author": "rafstahelin", - "title": "ComfyUI_KieNanoBananaPro", - "reference": "https://github.com/rafstahelin/ComfyUI_KieNanoBananaPro", - "files": [ - "https://github.com/rafstahelin/ComfyUI_KieNanoBananaPro" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for processing and filtering using KieNanoBananaPro algorithm. (Description by CC)" - }, - { - "author": "jinchanz", - "title": "ComfyUI-Midjourney", - "reference": "https://github.com/jinchanz/ComfyUI-Midjourney", - "files": [ - "https://github.com/jinchanz/ComfyUI-Midjourney" - ], - "install_type": "git-clone", - "description": "ComfyUI integration for Midjourney API with nodes for submitting requests, polling results, and extracting JSON data. (Description by CC)" - }, - { - "author": "saltchicken", - "title": "ComfyUI-Local-Loader", - "reference": "https://github.com/saltchicken/ComfyUI-Local-Loader", - "files": [ - "https://github.com/saltchicken/ComfyUI-Local-Loader" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for loading images from specified directories and file paths. (Description by CC)" - }, - { - "author": "satyasairazole", - "title": "ComfyUI-Turbandetection [WIP]", - "reference": "https://github.com/satyasairazole/ComfyUI-Turbandetection", - "files": [ - "https://github.com/satyasairazole/ComfyUI-Turbandetection" - ], - "install_type": "git-clone", - "description": "ComfyUI node for detecting turbans in images using computer vision. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "dougbtv", - "title": "comfyui-vllm-omni", - "reference": "https://github.com/dougbtv/comfyui-vllm-omni", - "files": [ - "https://github.com/dougbtv/comfyui-vllm-omni" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for vLLM-Omni text-to-image generation" - }, - { - "author": "u5dev", - "title": "ComfyUI_u5_EasyScripter [UNSAFE]", - "reference": "https://github.com/u5dev/ComfyUI_u5_EasyScripter", - "files": [ - "https://github.com/u5dev/ComfyUI_u5_EasyScripter" - ], - "install_type": "git-clone", - "description": "EASY VBA-style script for ComfyUI. Anything you want be in 1 node. Conditional branching, iteration, prompt generation, parameter adjustment, memory release, file input/output, Using HTTP RestAPI ... with 100+ built-in functions![w/This nodepack has RCE, SSRF, and arbitrary file read vulnerabilities.]" - }, - { - "author": "stalkervr", - "title": "ComfyUI-StalkerVr", - "reference": "https://github.com/stalkervr/ComfyUI-StalkerVr", - "files": [ - "https://github.com/stalkervr/ComfyUI-StalkerVr" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for image processing, aspect ratio fixing, batch cropping, grid manipulation, JSON handling, and value extraction. (Description by CC)" - }, - { - "author": "baoanhng", - "title": "ComfyUI-utils", - "reference": "https://github.com/baoanhng/ComfyUI-utils", - "files": [ - "https://github.com/baoanhng/ComfyUI-utils" - ], - "install_type": "git-clone", - "description": "Provides text utility nodes (TextJoiner, TextSplitter) for ComfyUI workflows. (Description by CC)" - }, - { - "author": "xuchenxu168", - "title": "[WIP] comfyui_meituan_image", - "reference": "https://github.com/xuchenxu168/comfyui_meituan_image", - "files": [ - "https://github.com/xuchenxu168/comfyui_meituan_image" - ], - "install_type": "git-clone", - "description": "Generate high-quality images from text prompts with excellent Chinese text rendering,Edit images using natural language instructions..\nNOTE: The files in the repo are not organized." - }, - { - "author": "saltchicken", - "title": "ComfyUI-Identity-Mixer", - "reference": "https://github.com/saltchicken/ComfyUI-Identity-Mixer", - "files": [ - "https://github.com/saltchicken/ComfyUI-Identity-Mixer" - ], - "install_type": "git-clone", - "description": "Mixes multiple identity LoRAs with normalized strength." - }, - { - "author": "saltchicken", - "title": "ComfyUI-Prompter", - "reference": "https://github.com/saltchicken/ComfyUI-Prompter", - "files": [ - "https://github.com/saltchicken/ComfyUI-Prompter" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node providing customizable prompt generation capabilities. (Description by CC)" - }, - { - "author": "JBKing514", - "title": "[WIP] map_comfyui", - "reference": "https://github.com/JBKing514/map_comfyui", - "files": [ - "https://github.com/JBKing514/map_comfyui" - ], - "install_type": "git-clone", - "description": "A custom node implementing the Manifold Alignment Protocol (MAP) within ComfyUI, transforming diffusion sampling into a measurable and visualizable geometric process. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "binarystatic", - "title": "ComfyUI-BinarystaticMasterSeed", - "reference": "https://github.com/binarystatic/ComfyUI-BinarystaticMasterSeed", - "files": [ - "https://github.com/binarystatic/ComfyUI-BinarystaticMasterSeed" - ], - "install_type": "git-clone", - "description": "BinarystaticMasterSeed node for ComfyUI. (Description by CC)" - }, - { - "author": "Aruntd008", - "title": "[WIP] ComfyUI_SeamlessPattern", - "reference": "https://github.com/Aruntd008/ComfyUI_SeamlessPattern", - "files": [ - "https://github.com/Aruntd008/ComfyUI_SeamlessPattern" - ], - "install_type": "git-clone", - "description": "SeamlessPatternNode for ComfyUI. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "SilentLuxRay", - "title": "[WIP] ComfyUI-Furrey-Super-Prompt", - "reference": "https://github.com/SilentLuxRay/ComfyUI-Furrey-Super-Prompt", - "files": [ - "https://github.com/SilentLuxRay/ComfyUI-Furrey-Super-Prompt" - ], - "install_type": "git-clone", - "description": "A personalized all-in-one node for ComfyUI that simplifies prompt management and LoRA handling with automatic translation to English. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "Rayen21", - "title": "[WIP] ComfyUI-PromptLinePlus", - "reference": "https://github.com/Rayen21/ComfyUI-PromptLinePlus", - "files": [ - "https://github.com/Rayen21/ComfyUI-PromptLinePlus" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node that splits multi-line prompts by line, enabling batch image generation with each line triggering one execution and supporting custom prompt boxes. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "rookiestar28", - "title": "ComfyUI_Security_Audit", - "reference": "https://github.com/rookiestar28/ComfyUI_Security_Audit", - "files": [ - "https://github.com/rookiestar28/ComfyUI_Security_Audit" - ], - "install_type": "git-clone", - "description": "A lightweight, dual-layer security extension for ComfyUI using AST-based static analysis and runtime monitoring to detect malicious code in custom nodes." - }, - { - "author": "c1660181647-hash", - "title": "ComfyUI-MM-Visual-Encryption", - "reference": "https://github.com/c1660181647-hash/ComfyUI-MM-Visual-Encryption", - "files": [ - "https://github.com/c1660181647-hash/ComfyUI-MM-Visual-Encryption" - ], - "install_type": "git-clone", - "description": "A visual noise encryption custom node for ComfyUI, supporting Image and Video privacy protection." - }, - { - "author": "charlierz", - "title": "comfyui-charlierz", - "reference": "https://github.com/charlierz/comfyui-charlierz", - "files": [ - "https://github.com/charlierz/comfyui-charlierz" - ], - "install_type": "git-clone", - "description": "NODES: BackgroundColor, ScaleDimensions" - }, - { - "author": "lrzjason", - "title": "Comfyui-DiffusersUtils [WIP]", - "reference": "https://github.com/lrzjason/Comfyui-DiffusersUtils", - "files": [ - "https://github.com/lrzjason/Comfyui-DiffusersUtils" - ], - "install_type": "git-clone", - "description": "A set of nodes which provide flexible inference using diffusers in comfyui env. (Description by CC)" - }, - { - "author": "Toxic1228", - "title": "Eleven-labs-comfyui-sts", - "reference": "https://github.com/Toxic1228/Eleven-labs-comfyui-sts", - "files": [ - "https://github.com/Toxic1228/Eleven-labs-comfyui-sts" - ], - "install_type": "git-clone", - "description": "ComfyUI integration node for Eleven Labs text-to-speech service (requires API key). (Description by CC)" - }, - { - "author": "NeoTech", - "title": "comfyui-laserprep", - "reference": "https://github.com/NeoTech/comfyui-laserprep", - "files": [ - "https://github.com/NeoTech/comfyui-laserprep" - ], - "install_type": "git-clone", - "description": "ComfyUI node implementing laser preparation functionality with LaserPrep node. (Description by CC)" - }, - { - "author": "Enferlain", - "title": "ComfyUI-extra-schedulers [WIP]", - "reference": "https://github.com/Enferlain/ComfyUI-extra-schedulers", - "files": [ - "https://github.com/Enferlain/ComfyUI-extra-schedulers" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes providing additional scheduler implementations for advanced sampling control. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "tiange-tree", - "title": "BLUEAI_ComfyUI_OpenAI", - "reference": "https://github.com/tiange-tree/BLUEAI_ComfyUI_OpenAI", - "files": [ - "https://github.com/tiange-tree/BLUEAI_ComfyUI_OpenAI" - ], - "install_type": "git-clone", - "description": "NODES: BLUEAI_OpenAI_Node" - }, - { - "author": "nestflow", - "title": "ComfyUI-WanPlus", - "reference": "https://github.com/nestflow/ComfyUI-WanPlus", - "files": [ - "https://github.com/nestflow/ComfyUI-WanPlus" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for video frame manipulation and image-to-video conversion. (Description by CC)" - }, - { - "author": "twdockery", - "title": "ComfyUI_Prompt_Batch_Generator", - "reference": "https://github.com/twdockery/ComfyUI_Prompt_Batch_Generator", - "files": [ - "https://github.com/twdockery/ComfyUI_Prompt_Batch_Generator" - ], - "install_type": "git-clone", - "description": "Custom nodes for batch image generation with Stable Diffusion 1.5, optimized for low VRAM systems. (Description by CC)" - }, - { - "author": "tuxiansheng-ld", - "title": "Comfyui-tuxiansheng-nodes", - "reference": "https://github.com/tuxiansheng-ld/Comfyui-tuxiansheng-nodes", - "files": [ - "https://github.com/tuxiansheng-ld/Comfyui-tuxiansheng-nodes" - ], - "install_type": "git-clone", - "description": "NODES: StringToListNode" - }, - { - "author": "krakenunbound", - "title": "Kraken Discord Bot", - "id": "kraken-discord-bot", - "reference": "https://github.com/krakenunbound/kraken-discord-bot", - "files": [ - "https://github.com/krakenunbound/kraken-discord-bot" - ], - "install_type": "git-clone", - "description": "All-in-one Discord bot node for AI image generation. Simple setup - just add token, select model, and queue. Includes style presets, rate limiting, and queue management." - }, - { - "author": "quinteroac", - "title": "comfyui_api_executor_nodes", - "reference": "https://github.com/quinteroac/comfyui_api_executor_nodes", - "files": [ - "https://github.com/quinteroac/comfyui_api_executor_nodes" - ], - "install_type": "git-clone", - "description": "Custom nodes for ComfyUI that enable workflow execution via API (internal or external), as well as input/output handling and workflow selection." - }, - { - "author": "Chang-Jin-Lee", - "title": "ComfyUI-PromptMixer-AI [WIP]", - "reference": "https://github.com/Chang-Jin-Lee/ComfyUI-PromptMixer-AI", - "files": [ - "https://github.com/Chang-Jin-Lee/ComfyUI-PromptMixer-AI" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node collection for unified control of checkpoints, steps, CFG, samplers, LoRA and prompt parameters with local LLM integration. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "leacvikas0", - "title": "ComfyUI-Presence [WIP]", - "reference": "https://github.com/leacvikas0/ComfyUI-Presence", - "files": [ - "https://github.com/leacvikas0/ComfyUI-Presence" - ], - "install_type": "git-clone", - "description": "NODES: BeautifulTextNode, FluxAdaptiveInjector, InspectNode, PresenceDirector, PresenceDirectorFireworks, PresenceDirectorVertex, PresenceSaver, UnaliverBundlePreview, UnaliverNode, UnaliverPlanner, UnaliverStepIterato, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "Kraven1109", - "title": "ComfyUI-Llama [NAME CONFLICT]", - "reference": "https://github.com/Kraven1109/ComfyUI-Llama", - "files": [ - "https://github.com/Kraven1109/ComfyUI-Llama" - ], - "install_type": "git-clone", - "description": "Lightweight ComfyUI plugin exposing llama.cpp-based one-shot Qwen VQA nodes." - }, - { - "author": "xiaoxidashen", - "title": "comfyui_my_utils", - "reference": "https://github.com/xiaoxidashen/comfyui_my_utils", - "files": [ - "https://github.com/xiaoxidashen/comfyui_my_utils" - ], - "install_type": "git-clone", - "description": "Guide and utilities for creating ComfyUI custom nodes with image/video preview functionality. (Description by CC)" - }, - { - "author": "agavesunset", - "title": "ComfyUI_LoRA_Tracker", - "reference": "https://github.com/agavesunset/ComfyUI_LoRA_Tracker", - "files": [ - "https://github.com/agavesunset/ComfyUI_LoRA_Tracker" - ], - "install_type": "git-clone", - "description": "ComfyUI node for tracking and displaying LoRA parameters. (Description by CC)" - }, - { - "author": "SleazySleaze", - "title": "aesthetic-persona-comfyui-node", - "reference": "https://github.com/SleazySleaze/aesthetic-persona-comfyui-node", - "files": [ - "https://github.com/SleazySleaze/aesthetic-persona-comfyui-node" - ], - "install_type": "git-clone", - "description": "Node providing aesthetic persona parsing capabilities for ComfyUI. (Description by CC)" - }, - { - "author": "xtanqn", - "title": "comfyui-xishen [WIP]", - "reference": "https://github.com/xtanqn/comfyui-xishen", - "files": [ - "https://github.com/xtanqn/comfyui-xishen" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that generates random numbers as text output.\nNOTE: The files in the repo are not organized." - }, - { - "author": "heyburns", - "title": "ComfyUI-Logic-Redux [WIP]", - "reference": "https://github.com/heyburns/ComfyUI-Logic-Redux", - "files": [ - "https://github.com/heyburns/ComfyUI-Logic-Redux" - ], - "install_type": "git-clone", - "description": "Validation-friendly rewrite of ComfyUI Logic nodes with drop-in compatibility, featuring compare, int/float/bool/string pass-through, ternary logic, and debug nodes. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "Mohamed-Sakr", - "title": "ComfyUI-SimpleMarkdown [UNSAFE]", - "reference": "https://github.com/Mohamed-Sakr/ComfyUI-SimpleMarkdown", - "files": [ - "https://github.com/Mohamed-Sakr/ComfyUI-SimpleMarkdown" - ], - "install_type": "git-clone", - "description": "A simple markdown node for ComfyUI[w/This nodepack has a frontend vulnerability.]" - }, - { - "author": "starsFriday", - "title": "ComfyUI-Tracker-Person [WIP]", - "reference": "https://github.com/starsFriday/ComfyUI-Tracker-Person", - "files": [ - "https://github.com/starsFriday/ComfyUI-Tracker-Person" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node using YOLO instance segmentation to track specific people in video frames with color histogram re-identification for robust tracking across occlusions. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "judian17", - "title": "ComfyUI-MIDI-3D [WIP]", - "reference": "https://github.com/judian17/ComfyUI-MIDI-3D", - "files": [ - "https://github.com/judian17/ComfyUI-MIDI-3D" - ], - "install_type": "git-clone", - "description": "Partial implementation of the MIDI-3D project for ComfyUI with multi-view normal/depth map generation and texture baking. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "IAFFeng", - "title": "Comfyui_XF_Custom_Actual-Node", - "reference": "https://github.com/IAFFeng/Comfyui_XF_Custom_Actual-Node", - "files": [ - "https://github.com/IAFFeng/Comfyui_XF_Custom_Actual-Node" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node providing GeminiMatting functionality for image matting operations. (Description by CC)" - }, - { - "author": "LvyuanW", - "title": "ComfyUI-6yuan [WIP]", - "reference": "https://github.com/LvyuanW/ComfyUI-6yuan", - "files": [ - "https://github.com/LvyuanW/ComfyUI-6yuan" - ], - "install_type": "git-clone", - "description": "A curated set of ComfyUI nodes under 6yuan namespace." - }, - { - "author": "wciq1208", - "title": "even-comfyui-plugin", - "reference": "https://github.com/wciq1208/even-comfyui-plugin", - "files": [ - "https://github.com/wciq1208/even-comfyui-plugin" - ], - "install_type": "git-clone", - "description": "NODES: Gemini, NanoBanana" - }, - { - "author": "Fuhze73", - "title": "ComfyUI-PainterI2V-StartEndImage-Tiled [WIP]", - "reference": "https://github.com/Fuhze73/ComfyUI-PainterI2V-StartEndImage-Tiled", - "files": [ - "https://github.com/Fuhze73/ComfyUI-PainterI2V-StartEndImage-Tiled" - ], - "install_type": "git-clone", - "description": "Wan2.2 image-to-video enhancement node optimized for 4-step LoRAs, reducing slow-motion and enhancing camera movement. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "stevanisya", - "title": "comfyui_txt_selector [WIP]", - "reference": "https://github.com/stevanisya/comfyui_txt_selector", - "files": [ - "https://github.com/stevanisya/comfyui_txt_selector" - ], - "install_type": "git-clone", - "description": "A simple ComfyUI node that lets you choose one text from up to 10 inputs.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Isi-dev", - "title": "ComfyUI_DeleteModelPassthrough_ExecutionFlowControl [WIP]", - "reference": "https://github.com/Isi-dev/ComfyUI_DeleteModelPassthrough_ExecutionFlowControl", - "files": [ - "https://github.com/Isi-dev/ComfyUI_DeleteModelPassthrough_ExecutionFlowControl" - ], - "install_type": "git-clone", - "description": "Memory management custom node that deletes specific models from VRAM and RAM while passing through other input types unchanged, useful for low-memory environments. (Description by CC)" - }, - { - "author": "K1maran", - "title": "ComfyUI-Kimaran", - "reference": "https://github.com/K1maran/ComfyUI-Kimaran", - "files": [ - "https://github.com/K1maran/ComfyUI-Kimaran" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes for preset latent image generation including EmptyLatentImagePresetsAIO, FLUX, QWEN, and SD1.5 variants. (Description by CC)" - }, - { - "author": "silveroxides", - "title": "ComfyUI_LoHalo", - "reference": "https://github.com/silveroxides/ComfyUI_LoHalo", - "files": [ - "https://github.com/silveroxides/ComfyUI_LoHalo" - ], - "install_type": "git-clone", - "description": "ComfyUI upscaling node package with Lohalo high-fidelity scaling and kernel-based image enhancement capabilities. (Description by CC)" - }, - { - "author": "jorin91", - "title": "ComfyUI-JSG-Utils [UNSAFE]", - "reference": "https://github.com/jorin91/ComfyUI-JSG-Utils", - "files": [ - "https://github.com/jorin91/ComfyUI-JSG-Utils" - ], - "install_type": "git-clone", - "description": "Utility nodes for ComfyUI including file operations, recursive image finding, and path parsing. (Description by CC)[w/This nodepack contains a vulnerability that allows remote deletion of files at arbitrary paths.]" - }, - { - "author": "vadimcro", - "title": "External-Image-Revised-with-MASK", - "reference": "https://github.com/vadimcro/External-Image-Revised-with-MASK", - "files": [ - "https://github.com/vadimcro/External-Image-Revised-with-MASK" - ], - "install_type": "git-clone", - "description": "ComfyUI external image node with mask support for image composition and masking workflows. (Description by CC)" - }, - { - "author": "martinken", - "title": "ComfyUI-KMNodes", - "reference": "https://github.com/martinken/ComfyUI-KMNodes", - "files": [ - "https://github.com/martinken/ComfyUI-KMNodes" - ], - "install_type": "git-clone", - "description": "NODES: KM_Aspect_Ratio_Selector, KM_Color_Correct, KM_Downscale_Image, KM_Merge_Images, KM_Safe_Mask_Bounds, KM_Safe_SEGS_Bounds, KM_Video_Image_Color_Match, KM_WanVideoToVideo, WanImageToVide, ..." - }, - { - "author": "sahibalejandro", - "title": "ComfyUI Sahib Nodes", - "reference": "https://github.com/sahibalejandro/comfyui-sahib-nodes", - "files": [ - "https://github.com/sahibalejandro/comfyui-sahib-nodes" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI" - }, - { - "author": "pznodes", - "title": "MotionCapture [UNSAFE]", - "reference": "https://github.com/PozzettiAndrea/ComfyUI-MotionCapture", - "files": [ - "https://github.com/PozzettiAndrea/ComfyUI-MotionCapture" - ], - "install_type": "git-clone", - "description": "Motion Capture nodes for ComfyUI: SMPL data/retargeting using GVHMR. This node is a very first draft, putting it out there in the hope some blender whizzes can help me figure out retargeting *prayer emoji*[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "pznodes", - "title": "SAM3DObjects [UNSAFE]", - "reference": "https://github.com/PozzettiAndrea/ComfyUI-SAM3DObjects", - "files": [ - "https://github.com/PozzettiAndrea/ComfyUI-SAM3DObjects" - ], - "install_type": "git-clone", - "description": "ComfyUI wrapper for SAM3DObjects: extract 3D meshes/gaussians from pictures. This node is a very first draft, putting it out there in the hope people help me with the development. I hate shipping broken code but I have some hope at least some linux people will manage to use it.[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "nachoman", - "title": "nachoman Custom Nodes [WIP]", - "reference": "https://github.com/eddiehavila/comfyui-nachoman", - "files": [ - "https://github.com/eddiehavila/comfyui-nachoman" - ], - "install_type": "git-clone", - "description": "NODES: NACHOMAN_AudioCropTime, NACHOMAN_AutoQueueController, NACHOMAN_ChunkIndexController, NACHOMAN_CombineAllChunks, NACHOMAN_Extract_Frame_Number, NACHOMAN_FullSongAnalyzerV4, NACHOMAN_GetPromptByIndex, NACHOMAN_GetRunIndexFromJson, NACHOMAN_IndexedPromptChunker, NACHOMAN_IndexedPromptChunkerV2, NACHOMAN_LoadAudioSplitDynamic, NACHOMAN_LoadAudioSplitUpload, NACHOMAN_LoadAudioSplit_HUMO, NACHOMAN_LoadAudioSplit_HUMO_Transcribe, NACHOMAN_LoadSingleAudioChunk, NACHOMAN_LoadVideos, NACHOMAN_PostRunIndexStepper, NACHOMAN_SaveVideoChunkWithIndex, NACHOMAN_TranscribeText, NACHOMAN_VideoSplitte, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "xuchenxu168", - "title": "ComfyUI-Gemini-3 [UNSAFE/WIP]", - "reference": "https://github.com/xuchenxu168/ComfyUI-Gemini-3", - "files": [ - "https://github.com/xuchenxu168/ComfyUI-Gemini-3" - ], - "install_type": "git-clone", - "description": "Full-featured, high-performance Google Gemini 3 integration for ComfyUI. This plugin implements **all** Gemini 3 capabilities with optimized performance.\nNOTE: The files in the repo are not organized.[w/This nodepack has a vulnerability that allows arbitrary code execution remotely.]" - }, - { - "author": "starsFriday", - "title": "ComfyUI-Audio-Subtitle [WIP]", - "reference": "https://github.com/starsFriday/ComfyUI-Audio-Subtitle", - "files": [ - "https://github.com/starsFriday/ComfyUI-Audio-Subtitle" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI node that automatically transcribes audio using OpenAI Whisper and hardcodes subtitles onto video frames using FFmpeg with customizable fonts, colors, styles, and positioning. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "TheLocalLab", - "title": "ComfyUI-Kandinsky-GGUF [WIP]", - "reference": "https://github.com/TheLocalLab/ComfyUI-Kandinsky-GGUF", - "files": [ - "https://github.com/TheLocalLab/ComfyUI-Kandinsky-GGUF" - ], - "install_type": "git-clone", - "description": "A fork of Ada123-a’s ComfyUI-Kandinsky nodes for Kandinsky 5 DiT. Enables high-quality video generation from text (T2V) and images (I2V) with advanced text-guided controls. \nNOTE: The files in the repo are not organized." - }, - { - "author": "JosDigby", - "title": "ComfyUI-DigbyWan", - "reference": "https://github.com/JosDigby/ComfyUI-DigbyWan", - "files": [ - "https://github.com/JosDigby/ComfyUI-DigbyWan" - ], - "install_type": "git-clone", - "description": "NODES: ImageBatchLoopExtract, WanMiddleFrameToVideo, WanVACEVideoSmoother" - }, - { - "author": "UltraNoob-NazoGiken", - "title": "ComfyUI-TOML-Tools [WIP]", - "reference": "https://github.com/UltraNoob-NazoGiken/ComfyUI-TOML-Tools", - "files": [ - "https://github.com/UltraNoob-NazoGiken/ComfyUI-TOML-Tools" - ], - "install_type": "git-clone", - "description": "NODES: CreateTomlData, GetTomlValue, LoadToml, MergeTomlData, SaveTom, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "frost-byte", - "title": "fb-tools", - "reference": "https://github.com/frost-byte/fbTools", - "files": [ - "https://github.com/frost-byte/fbTools" - ], - "install_type": "git-clone", - "description": "A collect of custom nodes for ComfyUI" - }, - { - "author": "Aruntd008", - "title": "ComfyUI_blender_render", - "reference": "https://github.com/Aruntd008/ComfyUI_blender_render", - "files": [ - "https://github.com/Aruntd008/ComfyUI_blender_render" - ], - "install_type": "git-clone", - "description": "NODES: Blender Render (Auto-Setup)" - }, - { - "author": "mertgoksel", - "title": "comfyui-noot-nodes [WIP]", - "reference": "https://github.com/mertgoksel/ComfyUI-Noot-Nodes", - "files": [ - "https://github.com/mertgoksel/ComfyUI-Noot-Nodes" - ], - "install_type": "git-clone", - "description": "Nodes i made to suit my needs, currently includes a tag randomizer and a stagedksampler which iterates sampler steps and upscaling." - }, - { - "author": "egormly", - "title": "ComfyUI-EG_Tools [WIP]", - "reference": "https://github.com/egormly/ComfyUI-EG_Tools", - "files": [ - "https://github.com/egormly/ComfyUI-EG_Tools" - ], - "install_type": "git-clone", - "description": "Quality-of-life utility nodes for ComfyUI providing smart switches for dynamic data routing, path helpers for consistent file naming, and image utilities including Qwen vision model preparation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "huhu-tiger", - "title": "ComfyUI-RemoteDownload", - "reference": "https://github.com/huhu-tiger/ComfyUI-RemoteDownload", - "files": [ - "https://github.com/huhu-tiger/ComfyUI-RemoteDownload" - ], - "install_type": "git-clone", - "description": "NODES: LoadImageFromRemote" - }, - { - "author": "zhupeter010903", - "title": "ComfyUI-prompt-library", - "reference": "https://github.com/zhupeter010903/ComfyUI-prompt-library", - "files": [ - "https://github.com/zhupeter010903/ComfyUI-prompt-library" - ], - "install_type": "git-clone", - "description": "NODES: Grouped Prompt Node, ..." - }, - { - "author": "basenc", - "title": "Comfyui-Nodes-basenc [UNSAFE]", - "reference": "https://github.com/basenc/Comfyui-Nodes-basenc", - "files": [ - "https://github.com/basenc/Comfyui-Nodes-basenc" - ], - "install_type": "git-clone", - "description": "NODES: Custom OpenAI Chat Completion, Environment Variable, Eval, JSON Path Select, JSON Path Select, Wan Video Size, ... [w/This nodepack has a vulnerability that allows arbitrary file contents to be read remotely.]" - }, - { - "author": "jkaarlehto", - "title": "ComfyUI-GetWorkflowName [WIP]", - "reference": "https://github.com/jKaarlehto/ComfyUI-GetWorkflowName", - "files": [ - "https://github.com/jKaarlehto/ComfyUI-GetWorkflowName" - ], - "install_type": "git-clone", - "description": "Adds a node that outputs the workflow's name as a string\nNOTE: The files in the repo are not organized." - }, - { - "author": "newraina", - "title": "comfyui-photoshop-v2 [WIP]", - "reference": "https://github.com/newraina/comfyui-photoshop-v2", - "files": [ - "https://github.com/newraina/comfyui-photoshop-v2" - ], - "install_type": "git-clone", - "description": "NODES: Photoshop ComfyUI Plugin, Send To Photoshop, ClipPass, modelPass, Photoshop RemoteConnection, ..." - }, - { - "author": "eddyhhlure1Eddy", - "title": "ComfyUI-QwenImageWrapper [WIP]", - "reference": "https://github.com/eddyhhlure1Eddy/ComfyUI-QwenImageWrapper", - "files": [ - "https://github.com/eddyhhlure1Eddy/ComfyUI-QwenImageWrapper" - ], - "install_type": "git-clone", - "description": "Integrated Qwen-Image node for ComfyUI with all-in-one model loading, 4 LoRA slots, memory optimization via BlockSwap reducing VRAM usage by 30-60%, and multiple quantization options.\nNOTE: The files in the repo are not organized." - }, - { - "author": "tori29umai0123", - "title": "ComfyUI-SDXLGenerateFromTextFile [UNSAFE]", - "reference": "https://github.com/tori29umai0123/ComfyUI-SDXLGenerateFromTextFile", - "files": [ - "https://github.com/tori29umai0123/ComfyUI-SDXLGenerateFromTextFile" - ], - "install_type": "git-clone", - "description": "ComfyUI node for batch image generation from text file prompts with configurable resolution, quantity, and model settings for Stable Diffusion workflows. (Description by CC)[w/This nodepack has a vulnerability that allows arbitrary file contents to be read remotely.]" - }, - { - "author": "unphased", - "title": "code-nodes [UNSAFE]", - "reference": "https://github.com/unphased/code-nodes", - "files": [ - "https://github.com/unphased/code-nodes" - ], - "install_type": "git-clone", - "description": "This repository provides two lightweight execution helpers for ComfyUI graphs: Shell Code, Python Code[w/This nodepack has a vulnerability that allows arbitrary code execution remotely.]" - }, - { - "author": "marduk191", - "title": "comfyui_cmdtls [UNSAFE]", - "reference": "https://github.com/marduk191/comfyui_cmdtls", - "files": [ - "https://github.com/marduk191/comfyui_cmdtls" - ], - "install_type": "git-clone", - "description": "A comprehensive set of ComfyUI custom nodes for file system operations, file browsing, upload/download, and shell command execution.[w/This nodepack has a vulnerability that allows arbitrary code execution remotely.]" - }, - { - "author": "AIExplorer25", - "title": "ComfyUI_HuggingfaceHelper", - "reference": "https://github.com/AIExplorer25/ComfyUI_HuggingfaceHelper", - "files": [ - "https://github.com/AIExplorer25/ComfyUI_HuggingfaceHelper" - ], - "install_type": "git-clone", - "description": "This repo has different functions to help dealing with huggingface data" - }, - { - "author": "ilslli", - "title": "Comfyui-IV2Z", - "reference": "https://github.com/ilslli/Comfyui-IV2Z", - "files": [ - "https://github.com/ilslli/Comfyui-IV2Z" - ], - "install_type": "git-clone", - "description": "Modify the VideoCombine node of [a/ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite) so that, after encoding the video, it outputs the result as a compressed archive." - }, - { - "author": "PladsElsker", - "title": "comfyui-krita [WIP]", - "reference": "https://github.com/PladsElsker/comfyui-krita", - "files": [ - "https://github.com/PladsElsker/comfyui-krita" - ], - "install_type": "git-clone", - "description": "Minimal Krita extension and ComfyUI custom nodes for integrating both UIs together with workflow exposure and compositing capabilities. NOTE: The project is in active development (WIP). (Description by CC)" - }, - { - "author": "max-dingsda", - "title": "ComfyUI-PromptLoop [WIP]", - "reference": "https://github.com/max-dingsda/ComfyUI-PromptLoop", - "files": [ - "https://github.com/max-dingsda/ComfyUI-PromptLoop" - ], - "install_type": "git-clone", - "description": "Batch process multiple prompts from text files in a single ComfyUI queue job - supports prompt loops from files or multi-line text with optional start index and limit controls.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Gergzilla", - "title": "Slim-Comfy-Pack [private]", - "reference": "https://github.com/Gergzilla/Slim-Comfy-Pack", - "files": [ - "https://github.com/Gergzilla/Slim-Comfy-Pack" - ], - "install_type": "git-clone", - "description": "A slimmed down collection of ComfyUI nodes either from elsewhere, custom creation or updated independently. (Description by CC)" - }, - { - "author": "Sida Liu", - "title": "ComfyUI-Notebook [UNSAFE]", - "id": "notebook", - "reference": "https://github.com/liusida/ComfyUI-Notebook", - "files": [ - "https://github.com/liusida/ComfyUI-Notebook" - ], - "install_type": "git-clone", - "description": "A Jupyter-style custom node for executing Python code and plotting within ComfyUI workflows.[w/This extension allows remote command execution.]" - }, - { - "author": "pznodes", - "title": "ComfyUI-UniRig [WIP]", - "reference": "https://github.com/PozzettiAndrea/ComfyUI-UniRig", - "files": [ - "https://github.com/PozzettiAndrea/ComfyUI-UniRig" - ], - "install_type": "git-clone", - "description": "WORK IN PROGRESS. Automatic skeleton extraction for ComfyUI using UniRig (SIGGRAPH 2025). Self-contained with bundled Blender." - }, - { - "author": "pznodes", - "title": "CADabra [WIP]", - "reference": "https://github.com/PozzettiAndrea/ComfyUI-CADabra", - "files": [ - "https://github.com/PozzettiAndrea/ComfyUI-CADabra" - ], - "install_type": "git-clone", - "description": "WORK IN PROGRESS. CAD file processing nodes for ComfyUI with interactive 3D OCC CAD viewer. Load STEP, IGES, BREP files and generate meshes using Gmsh." - }, - { - "author": "pznodes", - "title": "Hunyuan3D-Part [WIP]", - "reference": "https://github.com/PozzettiAndrea/ComfyUI-Hunyuan3D-Part", - "files": [ - "https://github.com/PozzettiAndrea/ComfyUI-Hunyuan3D-Part" - ], - "install_type": "git-clone", - "description": "WORK IN PROGRESS. Hunyuan3D-Part nodes for ComfyUI: 3D part segmentation with P3-SAM and high-fidelity part generation with X-Part diffusion." - }, - { - "author": "hhelloe", - "title": "comfyuiWeightOnlyQuant [WIP]", - "reference": "https://github.com/hhelloe/comfyuiWeightOnlyQuant", - "files": [ - "https://github.com/hhelloe/comfyuiWeightOnlyQuant" - ], - "install_type": "git-clone", - "description": "NODES: Weight-Only Quantize (Linear)\nNOTE: The files in the repo are not organized." - }, - { - "author": "KXSR", - "title": "ComfyUI-Easy-s3", - "reference": "https://github.com/KXSR/ComfyUI-Easy-s3", - "files": [ - "https://github.com/KXSR/ComfyUI-Easy-s3" - ], - "install_type": "git-clone", - "description": "NODES: 'URL: Load Media', 'Image: Save To Disk (No UI)', 'S3: Upload Presigned', ..." - }, - { - "author": "akashch1512", - "title": "ComfyUI_UguuUploader", - "reference": "https://github.com/akashch1512/ComfyUI_UguuUploader", - "files": [ - "https://github.com/akashch1512/ComfyUI_UguuUploader" - ], - "install_type": "git-clone", - "description": "NODES: Uguu.se Video Uploader\nNOTE: The files in the repo are not organized." - }, - { - "author": "vadimcro", - "title": "string_by_index", - "reference": "https://github.com/vadimcro/string_by_index", - "files": [ - "https://github.com/vadimcro/string_by_index" - ], - "install_type": "git-clone", - "description": "ComfyUI node that lets you output a string, based on int index number on input" - }, - { - "author": "sickbraintwo", - "title": "WextraX [WIP]", - "reference": "https://github.com/sickbraintwo/WextraX", - "files": [ - "https://github.com/sickbraintwo/WextraX" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for LoRA management, including LoRA name extraction and tag filtering utilities. (Description by CC)\nNOTE: The files in the repo are not organized." - }, - { - "author": "hhelloe", - "title": "ComfyuiQuantizerToFp8 [WIP]", - "reference": "https://github.com/hhelloe/ComfyuiQuantizerToFp8", - "files": [ - "https://github.com/hhelloe/ComfyuiQuantizerToFp8" - ], - "install_type": "git-clone", - "description": "Linear quantizer node for ComfyUI that converts models to FP8 precision format for reduced memory usage and faster inference.\nNOTE: The files in the repo are not organized." - }, - { - "author": "nobinBB", - "title": "comfyui-samenodes", - "reference": "https://github.com/nobinBB/comfyui-samenodes", - "files": [ - "https://github.com/nobinBB/comfyui-samenodes" - ], - "install_type": "git-clone", - "description": "NODES: Batch Image Processor, Float to String, Float to String with Prefix, LoRA Wildcard Generator" - }, - { - "author": "MushroomFleet", - "title": "ComfyUI-SCP-nodes [UNSAFE]", - "reference": "https://github.com/MushroomFleet/ComfyUI-SCP-nodes", - "files": [ - "https://github.com/MushroomFleet/ComfyUI-SCP-nodes" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node that automatically saves generated images locally and uploads them to a remote server via SCP (Secure Copy Protocol).[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "jonstreeter", - "title": "Archive Output [UNSAFE]", - "id": "archive-output", - "reference": "https://github.com/jonstreeter/comfyui-archive-output", - "files": [ - "https://github.com/jonstreeter/comfyui-archive-output" - ], - "install_type": "git-clone", - "description": "Archive and organize your generated images into date-structured folders, and compress PNG images to JPEG/WebP with full workflow metadata preservation, saving 85-95% disk space.[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "xuchenxu168", - "title": "ComfyUI-Qwen3-VL [WIP]", - "id": "qwen3-vl", - "reference": "https://github.com/xuchenxu168/ComfyUI_Qwen3-VL", - "files": [ - "https://github.com/xuchenxu168/ComfyUI_Qwen3-VL" - ], - "install_type": "git-clone", - "description": "Enhanced Qwen3-VL integration for ComfyUI with direct image and video support.\nNOTE: The files in the repo are not organized." - }, - { - "author": "cedarconnor", - "title": "HunyuanWorld-Mirror ComfyUI Node Pack [WIP]", - "reference": "https://github.com/cedarconnor/ComfyUI-HunyuanWorld-Mirror", - "files": [ - "https://github.com/cedarconnor/ComfyUI-HunyuanWorld-Mirror" - ], - "install_type": "git-clone", - "description": "Transform 2D images into 3D worlds using Tencent's HunyuanWorld-Mirror model directly in ComfyUI." - }, - { - "author": "zRicmuriellee1xh", - "title": "ComfyUI-Logic-Switch", - "reference": "https://github.com/muriellee1x/ComfyUI-Logic-Switch", - "files": [ - "https://github.com/muriellee1x/ComfyUI-Logic-Switch" - ], - "install_type": "git-clone", - "description": "NODES: random string selector" - }, - { - "author": "zRich", - "title": "ComfyUI-zRich-ObjectCutter", - "reference": "https://github.com/zRich/ComfyUI-zRich-ObjectCutter", - "files": [ - "https://github.com/zRich/ComfyUI-zRich-ObjectCutter" - ], - "install_type": "git-clone", - "description": "NODES: zRich Object Cutter (MASK to Transparent), SAM2 Auto Layer Extractor" - }, - { - "author": "ajcampbell1333", - "title": "BudgetGuard for Artists [WIP]", - "reference": "https://github.com/ajcampbell1333/BudgetGuard_Artists", - "files": [ - "https://github.com/ajcampbell1333/BudgetGuard_Artists" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node that provides cost estimation and budget management for NVIDIA NIM nodes across multiple cloud providers." - }, - { - "author": "muriellee1x", - "title": "ComfyUI-switch-angles", - "reference": "https://github.com/muriellee1x/ComfyUI-switch-angles", - "files": [ - "https://github.com/muriellee1x/ComfyUI-switch-angles" - ], - "install_type": "git-clone", - "description": "NODES: Camera Prompt Generator, ..." - }, - { - "author": "APZmedia", - "title": "ComfyUI Folder Parser [UNSAFE]", - "reference": "https://github.com/APZmedia/ComfyUI-folder-parser", - "files": [ - "https://github.com/APZmedia/ComfyUI-folder-parser" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that parses files from a folder with filtering and sorting capabilities. Outputs file paths for use in other ComfyUI nodes.[w/This nodepack contains a path traversal vulnerability.]" - }, - { - "author": "Seb-Lis", - "title": "comfyui-seed-selector", - "reference": "https://github.com/Seb-Lis/comfyui-seed-selector", - "files": [ - "https://github.com/Seb-Lis/comfyui-seed-selector" - ], - "install_type": "git-clone", - "description": "NODES: Seed Selector (INT), My Seed Selector (INT), Seed Selector with Display, ..." - }, - { - "author": "Amor718", - "title": "AmorForge", - "reference": "https://github.com/Amor718/AmorForge", - "files": [ - "https://github.com/Amor718/AmorForge" - ], - "install_type": "git-clone", - "description": "NODES: Amor's Image WebpSaver, Amor's Image Resizer, Amor's Text Translator, Amor's CogView Image Generator, ..." - }, - { - "author": "Solarish", - "title": "fyUI-MidnightLook [WIP]", - "reference": "https://github.com/Solarish/ComfyUI-MidnightLook", - "files": [ - "https://github.com/Solarish/ComfyUI-MidnightLook" - ], - "install_type": "git-clone", - "description": "Custom node for MidnightLook.com\nNOTE: The files in the repo are not organized." - }, - { - "author": "hujuying", - "title": "ComfyUI-QwenVL3-image-Plus [WIP]", - "reference": "https://github.com/hujuying/ComfyUI-QwenVL3-image-Plus", - "files": [ - "https://github.com/hujuying/ComfyUI-QwenVL3-image-Plus" - ], - "install_type": "git-clone", - "description": "A custom node designed for ComfyUI that integrates the **Qwen3-VL-4B-Instruct-FP8** vision-language model for efficient image understanding and description. It supports flexible prompt-combination features to enhance the accuracy and diversity of image descriptions.\nNOTE: The files in the repo are not organized." - }, - { - "author": "r-vage", - "title": "ComfyUI_Eclipse [UNSAFE]", - "reference": "https://github.com/r-vage/ComfyUI_Eclipse", - "files": [ - "https://github.com/r-vage/ComfyUI_Eclipse" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes and utilities for workflow building, type conversions, checkpoint/pipe loaders and file utilities.[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "silveroxides", - "title": "ComfyUI_LogicMath", - "reference": "https://github.com/silveroxides/ComfyUI_LogicMath", - "files": [ - "https://github.com/silveroxides/ComfyUI_LogicMath" - ], - "install_type": "git-clone", - "description": "Custom node for with some basic logic and math nodes based on pull request [a/comfyanonymous/ComfyUI#8024](https://github.com/comfyanonymous/ComfyUI/pull/8024)" - }, - { - "author": "lyra-ai", - "title": "lyra-nodes", - "reference": "https://github.com/lyra-ai/lyra-nodes", - "files": [ - "https://github.com/lyra-ai/lyra-nodes" - ], - "install_type": "git-clone", - "description": "NODES: Lyra – 🎞️ Image Audio Fusion, Lyra – 🌐 Load Audio from URL, Lyra – 🖼️ Load Image from URL" - }, - { - "author": "VectorASD", - "title": "ComfyUI-VectorASD", - "reference": "https://github.com/VectorASD/ComfyUI-VectorASD", - "files": [ - "https://github.com/VectorASD/ComfyUI-VectorASD" - ], - "install_type": "git-clone", - "description": "VectorASD's custom nodes for ComfyUI" - }, - { - "author": "laobaishu1", - "title": "ComfyUI-JSON-Upload-Iterator [UNSAFE]", - "reference": "https://github.com/laobaishu1/ComfyUI-JSON-Upload-Iterator", - "files": [ - "https://github.com/laobaishu1/ComfyUI-JSON-Upload-Iterator" - ], - "install_type": "git-clone", - "description": "A JSON upload and iterator node for ComfyUI that supports uploading JSON storyboard files and outputs each field sequentially.[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "dibowei1980", - "title": "Comfyui-QwenEditUtilsFromRapid", - "reference": "https://github.com/dibowei1980/Comfyui-QwenEditUtilsFromRapid", - "files": [ - "https://github.com/dibowei1980/Comfyui-QwenEditUtilsFromRapid" - ], - "install_type": "git-clone", - "description": "NODES: TextEncodeQwenImageEdit_FromRapid, TextEncodeQwenImageEditPlus_FromRapid" - }, - { - "author": "minujeong", - "title": "comfyui-oklch-color-picker", - "reference": "https://github.com/minujeong/comfyui-oklch-color-picker", - "files": [ - "https://github.com/minujeong/comfyui-oklch-color-picker" - ], - "install_type": "git-clone", - "description": "provide OKLCH space color picker as Comfy UI node" - }, - { - "author": "gaonprime-lab", - "title": "ComfyUI_Nordy", - "reference": "https://github.com/gaonprime-labs/ComfyUI_Nordy", - "files": [ - "https://github.com/gaonprime-labs/ComfyUI_Nordy" - ], - "install_type": "git-clone", - "description": "NODES: Save Image S3 PresignedUrl (Nordy), Memory Test" - }, - { - "author": "mittimi", - "title": "ComfyUI_mittimiLoadPresetLite [UNSAFE]", - "reference": "https://github.com/mittimi/ComfyUI_mittimiLoadPresetLite", - "files": [ - "https://github.com/mittimi/ComfyUI_mittimiLoadPresetLite" - ], - "install_type": "git-clone", - "description": "This node can easily switch between models and prompts by saving presets.[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "WenerSantos3", - "title": "ComfyUI-BlueeUtils [UNSAFE]", - "reference": "https://github.com/WenerSantos3/ComfyUI-BlueeUtils", - "files": [ - "https://github.com/WenerSantos3/ComfyUI-BlueeUtils" - ], - "install_type": "git-clone", - "description": "NODES: Bluee Utils - Delete File, blueeInput, Bluee Template String, Bluee Utils - Download Temp, Bluee Utils - Read Temp Image, Bluee Utils - Read Temp Audio, bluee - file exists, Bluee - Combine Images + Audio (H.264 MP4)[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "Rosegun007", - "title": "ComfyUI-Pandora", - "reference": "https://github.com/Rosegun007/ComfyUI-Pandora", - "files": [ - "https://github.com/Rosegun007/ComfyUI-Pandora" - ], - "install_type": "git-clone", - "description": "A simple and user-friendly ComfyUI plugin that offers advanced text rendering, streamlined bridge-style image preview, and various convenient mask tools — primarily designed for ComfyUI tutorial creators." - }, - { - "author": "fuselayer", - "title": "comfyui-libcom-image-composition", - "reference": "https://github.com/fuselayer/comfyui-libcom-image-composition", - "files": [ - "https://github.com/fuselayer/comfyui-libcom-image-composition" - ], - "install_type": "git-clone", - "description": "port of bcmi/libcom and other image composition (harmonization, shadow generation) utilities" - }, - { - "author": "Long-form-AI-video-generation", - "title": "ComfyUI_vaceFramepack", - "reference": "https://github.com/Long-form-AI-video-generation/ComfyUI_vaceFramepack", - "files": [ - "https://github.com/Long-form-AI-video-generation/ComfyUI_vaceFramepack" - ], - "install_type": "git-clone", - "description": "NODES: WanVACE FramePack Sampler 2" - }, - { - "author": "ifilipis", - "title": "ComfyUI-Auto-Prompt-Engineer", - "reference": "https://github.com/ifilipis/ComfyUI-Auto-Prompt-Engineer", - "files": [ - "https://github.com/ifilipis/ComfyUI-Auto-Prompt-Engineer" - ], - "install_type": "git-clone", - "description": "Automated prompt engineering using LLMs" - }, - { - "author": "JHBOY-ha", - "title": "ComfyUI-jh-essential", - "reference": "https://github.com/JHBOY-ha/ComfyUI-jh-essential", - "files": [ - "https://github.com/JHBOY-ha/ComfyUI-jh-essential" - ], - "install_type": "git-clone", - "description": "NODES: Start Timer, End Timer" - }, - { - "author": "APZmedia", - "title": "Joshua Brand Assets Loader [private]", - "reference": "https://github.com/APZmedia/Joshua-brand-assets-loader", - "files": [ - "https://github.com/APZmedia/Joshua-brand-assets-loader" - ], - "install_type": "git-clone", - "description": "A comprehensive brand asset loader for ComfyUI that handles logos, fonts, and color palettes with dual loading modes (API and manual)." - }, - { - "author": "GoddessLabs", - "title": "ComfyUI-GoddessLabs-NodePack", - "reference": "https://github.com/GoddessLabs/ComfyUI-GoddessLabs-NodePack", - "files": [ - "https://github.com/GoddessLabs/ComfyUI-GoddessLabs-NodePack" - ], - "install_type": "git-clone", - "description": "Custom node pack for ComfyUI This nodepack helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more." - }, - { - "author": "Beinsezii", - "title": "DEET: Diffusion Entropy Equalization Transform [WIP]", - "reference": "https://github.com/Beinsezii/DEET", - "files": [ - "https://github.com/Beinsezii/DEET" - ], - "install_type": "git-clone", - "description": "A simple transform to regulate the noise as it flows in and out of a diffusion model. Currently implemented as a nn.Module forward hook for Diffusers and as a node for ComfyUI" - }, - { - "author": "woitec", - "title": "ComfyUI_ImageDimensions [WIP]", - "reference": "https://github.com/woitec/ComfyUI_ImageDimensions", - "files": [ - "https://github.com/woitec/ComfyUI_ImageDimensions" - ], - "install_type": "git-clone", - "description": "Node for extraction of dimensions from an upstream-loaded tensor image.\nNOTE: The files in the repo are not organized." - }, - { - "author": "PopeyesBiscuit", - "title": "ComfyUI-WebSocketLoRAExporter", - "reference": "https://github.com/PopeyesBiscuit/ComfyUI-WebSocketLoRAExporter", - "files": [ - "https://github.com/PopeyesBiscuit/ComfyUI-WebSocketLoRAExporter" - ], - "install_type": "git-clone", - "description": "NODES: Simple LoRA Loader, ..." - }, - { - "author": "PopeyesBiscuit", - "title": "ComfyUI Delete Folder Nodes [UNSAFE]", - "reference": "https://github.com/PopeyesBiscuit/ComfyUI-DeleteFolder", - "files": [ - "https://github.com/PopeyesBiscuit/ComfyUI-DeleteFolder" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for clearing folders in the input or output folder." - }, - { - "author": "hndrr", - "title": "Comfyui-SceneDetect [WIP]", - "reference": "https://github.com/hndrr/Comfyui-SceneDetect", - "files": [ - "https://github.com/hndrr/Comfyui-SceneDetect" - ], - "install_type": "git-clone", - "description": "Comfyui-SceneDetect is a ComfyUI custom node that uses PySceneDetect to locate scene boundaries in a video and emit one representative frame per scene as an `IMAGE` batch. It also returns per-scene metadata as JSON (`STRING`) and the total number of detected scenes (`INT`)." - }, - { - "author": "zRich", - "title": "sam2_layer_node", - "reference": "https://github.com/zRich/sam2_layer_node", - "files": [ - "https://github.com/zRich/sam2_layer_node" - ], - "install_type": "git-clone", - "description": "ComfyUI Sam2 Layer Extract" - }, - { - "author": "promptingpixels", - "title": "ComfyUI-DreamOmni2-GGUF [WIP]", - "reference": "https://github.com/xuchenxu168/Comfyui_Prompt_Edit", - "files": [ - "https://github.com/xuchenxu168/Comfyui_Prompt_Edit" - ], - "install_type": "git-clone", - "description": "Prompt Edit is a custom node for ComfyUI that allows you to pause workflow execution, edit text prompts, and then continue. This is very useful for scenarios where you need to dynamically adjust prompts during the generation process.\nNOTE: The files in the repo are not organized." - }, - { - "author": "promptingpixels", - "title": "Simple CSV Parser [UNSAFE]", - "reference": "https://github.com/content-and-code/ComfyUI-SimpleCSVParse", - "files": [ - "https://github.com/content-and-code/ComfyUI-SimpleCSVParse" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node for parsing CSV files with positive and negative prompts[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "PauldeLavallaz", - "title": "comfyui_morpheus_NanoBanana_Mask [WIP]", - "reference": "https://github.com/PauldeLavallaz/comfyui_morpheus_NanoBanana_Mask", - "files": [ - "https://github.com/PauldeLavallaz/comfyui_morpheus_NanoBanana_Mask" - ], - "install_type": "git-clone", - "description": "NODES: Morpheus · Batch Images + crop image, Morpheus · NanoBanana Mask, Morpheus · Image Editing Prompt, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "silveroxides", - "title": "ComfyUI_PromptAttention [WIP]", - "reference": "https://github.com/silveroxides/ComfyUI_PromptAttention", - "files": [ - "https://github.com/silveroxides/ComfyUI_PromptAttention" - ], - "install_type": "git-clone", - "description": "NODES: Text Encode with Attention Bias" - }, - { - "author": "cedarconnor", - "title": "ComfyUI Motion Transfer Pack [WIP]", - "reference": "https://github.com/cedarconnor/ComfyUI_MotionTransfer", - "files": [ - "https://github.com/cedarconnor/ComfyUI_MotionTransfer" - ], - "install_type": "git-clone", - "description": "Transfer motion from low-resolution AI-generated videos to ultra-high-resolution still images (up to 16K+).\nNOTE: The files in the repo are not organized." - }, - { - "author": "theshubzworld", - "title": "ComfyUI-NvidiaVision [WIP]", - "reference": "https://github.com/theshubzworld/ComfyUI-NvidiaVision", - "files": [ - "https://github.com/theshubzworld/ComfyUI-NvidiaVision" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for NVIDIA AI Vision and Text models\nNOTE: The files in the repo are not organized." - }, - { - "author": "MadPonyInteractive", - "title": "ComfyUi-MpiNodes [WIP]", - "reference": "https://github.com/MadPonyInteractive/ComfyUi-MpiNodes", - "files": [ - "https://github.com/MadPonyInteractive/ComfyUi-MpiNodes" - ], - "install_type": "git-clone", - "description": "NODES: Mpi Batch Text Replace, Mpi Prompt Chain Selector, Mpi Preset Selector, Mpi Prompt Override Seed, Mpi If Else Probability Inverted, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "fantaskiss", - "title": "ComfyUI-Qwen3_VQA_enhanced [WIP]", - "reference": "https://github.com/fantaskiss/ComfyUI-Qwen3_VQA_enhanced", - "files": [ - "https://github.com/fantaskiss/ComfyUI-Qwen3_VQA_enhanced" - ], - "install_type": "git-clone", - "description": "Enhancement of the original qwen3_VQA node. Other functions remain unchanged, with only the automatic scanning of the prompt_generator folder added. No new node group is created; only additions and modifications are made to the original node group." - }, - { - "author": "pacchikAI", - "title": "comfyui_pacchik_window [UNSAFE]", - "reference": "https://github.com/pacchikAI/comfyui_pacchik_window", - "files": [ - "https://github.com/pacchikAI/comfyui_pacchik_window" - ], - "install_type": "git-clone", - "description": "A simple ComfyUI node that takes any window you have as an Input to ComfyUI node\nNOTE: WORKS IN WINDOWS ONLY[w/This nodepack has a feature that can retrieve your screen information remotely.]" - }, - { - "author": "yamanacn", - "title": "ComfyUI-QwenVL3-image [WIP]", - "reference": "https://github.com/yamanacn/ComfyUI-QwenVL3-image", - "files": [ - "https://github.com/yamanacn/ComfyUI-QwenVL3-image" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that integrates the Qwen3-VL-4B-Instruct-FP8 vision-language model for efficient image understanding and description.\nNOTE: The files in the repo are not organized." - }, - { - "author": "DeyanSrossturkhahov", - "title": "ComfyUI Primitive Mesh Generator [WIP]", - "reference": "https://github.com/rossturk/comfyui-primitive-mesh", - "files": [ - "https://github.com/rossturk/comfyui-primitive-mesh" - ], - "install_type": "git-clone", - "description": "Image-to-vector art generator for ComfyUI - Convert photographs into artistic collages composed of geometric shapes using a greedy optimization algorithm.\nNOTE: The files in the repo are not organized." - }, - { - "author": "DeyanShahov", - "title": "Dynamic Prompt Selector for ComfyUI [WIP]", - "reference": "https://github.com/DeyanShahov/ComfyUI_RedFox", - "files": [ - "https://github.com/DeyanShahov/ComfyUI_RedFox" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that allows you to dynamically select parts from large text prompts split by delimiters. The node supports 5 independent tabs (each like a mini-prompt selector), various selection behaviors, and persists state across ComfyUI restarts.\nNOTE: The files in the repo are not organized." - }, - { - "author": "addddd2", - "title": "AI_Generated_nodes", - "reference": "https://github.com/addddd2/AI_Generated_nodes", - "files": [ - "https://github.com/addddd2/AI_Generated_nodes" - ], - "install_type": "git-clone", - "description": "NODES: ImageScaleToTotalPixelsRound64, ImageBlendLighter, ImageOffset, RGBtoRYGCBM, RYGCBMtoRGB, ExtractImageChannel, MatchRYGCBMColors, TextCommaToWeighted, TextCommaToRandomWeighted, RGBtoLAB, LABtoRGB, ImageMirrorPad, ImageCropBorders, ImageStitcher, ImageScaleToQwen, ..." - }, - { - "author": "silveroxides", - "title": "Sampling Utility Toolkit", - "reference": "https://github.com/silveroxides/ComfyUI_SamplingUtils", - "files": [ - "https://github.com/silveroxides/ComfyUI_SamplingUtils" - ], - "install_type": "git-clone", - "description": "Utilities making it easy to set up inference parameters." - }, - { - "author": "henjicc", - "title": "ComfyUI-CC-ImageLoader [UNSAFE]", - "reference": "https://github.com/henjicc/ComfyUI-CC-ImageLoader", - "files": [ - "https://github.com/henjicc/ComfyUI-CC-ImageLoader" - ], - "install_type": "git-clone", - "description": "ComfyUI-CC-ImageLoader is an enhanced image loading node designed for ComfyUI. It is developed based on two excellent projects: ComfyUI-Thumbnails and ComfyUI_Local_Media_Manager.[w/This nodepack includes an endpoint that access files from arbitrary paths.]" - }, - { - "author": "A1rCHAN", - "title": "Eric's Prompt Enhancers for ComfyUI# Eric's Prompt Enhancers for ComfyUI", - "reference": "https://github.com/A1rCHAN/ComfyUI_A1rSpace", - "files": [ - "https://github.com/A1rCHAN/ComfyUI_A1rSpace" - ], - "install_type": "git-clone", - "description": "A comfyui custom node package used by myself. " - }, - { - "author": "franklydegenerate", - "title": "WAN Resolution Helper [WIP]", - "reference": "https://github.com/franklydegenerate/ComfyUI-WAN-Resolution-Helper", - "files": [ - "https://github.com/franklydegenerate/ComfyUI-WAN-Resolution-Helper" - ], - "install_type": "git-clone", - "description": "ComfyUI node that outputs WAN 2.2-ready sizes by preserving aspect ratio, capping the long side, and rounding both dimensions to multiples of 16 pixels to reduce artifacts.\nNOTE: The files in the repo are not organized." - }, - { - "author": "RM2-0", - "title": "ComfyUI-Upscale", - "reference": "https://github.com/RM2-0/ComfyUI-Upscale", - "files": [ - "https://github.com/RM2-0/ComfyUI-Upscale" - ], - "install_type": "git-clone", - "description": "Efficient upscaling node with FP16 and batch processing support for ComfyUI" - }, - { - "author": "light-and-ray", - "title": "Minimalistic-Comfy-Wrapper-WebUI", - "reference": "https://github.com/light-and-ray/Minimalistic-Comfy-Wrapper-WebUI", - "files": [ - "https://github.com/light-and-ray/Minimalistic-Comfy-Wrapper-WebUI" - ], - "install_type": "git-clone", - "description": "An alternative additional non-node based UI for ComfyUI, that dynamically adapts to your workflows - you only need to change the titles of nodes that you want to see in the Minimalistic webui, and click 'Refresh' button" - }, - { - "author": "bakker", - "title": "BK_Utils [WIP]", - "reference": "https://github.com/bakker/BK_Utils", - "files": [ - "https://github.com/bakker/BK_Utils" - ], - "install_type": "git-clone", - "description": "NODES: Same Pixel Resolution Calculator, .." - }, - { - "author": "garciaone", - "title": "g_one_toolkit", - "reference": "https://github.com/garciaone/g_one_toolkit", - "files": [ - "https://github.com/garciaone/g_one_toolkit" - ], - "install_type": "git-clone", - "description": "A collection of quality of life nodes" - }, - { - "author": "banini1841", - "title": "BigModelPipe [WIP]", - "reference": "https://github.com/banini1841/BigModelPipe", - "files": [ - "https://github.com/banini1841/BigModelPipe" - ], - "install_type": "git-clone", - "description": "NODES: BigModelPipeIn, BigModelPipeOut" - }, - { - "author": "comfyui-wiki", - "title": "ComfyUI-SelectionFocus [WIP]", - "reference": "https://github.com/comfyui-wiki/ComfyUI-SelectionFocus", - "files": [ - "https://github.com/comfyui-wiki/ComfyUI-SelectionFocus" - ], - "install_type": "git-clone", - "description": "A powerful ComfyUI extension that helps you focus on selected nodes by highlighting their connections while dimming or hiding unrelated links\nNOTE: The files in the repo are not organized." - }, - { - "author": "pizurny", - "title": "ComfyUI Feedback Sampler [WIP]", - "reference": "https://github.com/pizurny/Comfyui-FeedbackSampler", - "files": [ - "https://github.com/pizurny/Comfyui-FeedbackSampler" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI sampler for creating Deforum-style zoom animations through iterative feedback loop." - }, - { - "author": "FlowX Team", - "title": "FlowX Preprocess [UNSAFE]", - "reference": "https://github.com/cgf120/comfyui-flowx-preprocess", - "files": [ - "https://github.com/cgf120/comfyui-flowx-preprocess" - ], - "install_type": "git-clone", - "description": "FlowX Preprocess Node for ComfyUI - Export workflow configurations with input parameters[w/This nodepack includes an endpoint that deletes files from arbitrary paths.]" - }, - { - "author": "jpwgad", - "title": "jpwgad", - "reference": "https://github.com/jpwgad/jpwgad-comfy", - "files": [ - "https://github.com/jpwgad/jpwgad-comfy" - ], - "install_type": "git-clone", - "description": "NODES: Image Size Input, Date/Time based output path" - }, - { - "author": "octapus8085", - "title": "OpenAI-comfyui-O", - "reference": "https://github.com/octapus8085/OpenAI-comfyui-O", - "files": [ - "https://github.com/octapus8085/OpenAI-comfyui-O" - ], - "install_type": "git-clone", - "description": "NODES: AI API → Image" - }, - { - "author": "Immac", - "title": "Immac's Tools", - "reference": "https://github.com/Immac/ComfyUI-ImmacTools", - "files": [ - "https://github.com/Immac/ComfyUI-ImmacTools" - ], - "install_type": "git-clone", - "description": "NODES: Concatenate Sigmas Node, Splice Sigmas At %" - }, - { - "author": "silveroxides", - "title": "WIP Conditioning Toolkit [UNSAFE]", - "reference": "https://github.com/silveroxides/ComfyUI_CondsUtils", - "files": [ - "https://github.com/silveroxides/ComfyUI_CondsUtils" - ], - "install_type": "git-clone", - "description": "Custom nodes for modifying, saving and loading of various conditionings.[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "ThuckMaBaws", - "title": "TMB Camera Modifier for ComfyUI [WIP]", - "reference": "https://github.com/ThuckMaBaws/TMB-Camera-Modifier", - "files": [ - "https://github.com/ThuckMaBaws/TMB-Camera-Modifier" - ], - "install_type": "git-clone", - "description": "This custom node adds a powerful camera-based modifier system to ComfyUI, enabling rich, authentic prompt generation for image workflows. It’s designed for creators who want to simulate real-world camera setups, film types, and stylistic traits — all with flexible filtering and randomization.\nNOTE: The files in the repo are not organized." - }, - { - "author": "pfpb", - "title": "comfyui-badnodes", - "reference": "https://github.com/pfpb/comfyui-badnodes", - "files": [ - "https://github.com/pfpb/comfyui-badnodes" - ], - "install_type": "git-clone", - "description": "NODES: ReplaceEmptyMasksWithLastMasks, ..." - }, - { - "author": "isurulkh", - "title": "ComfyUI-Lovis-Node [WIP]", - "reference": "https://github.com/isurulkh/ComfyUI-Lovis-Node", - "files": [ - "https://github.com/isurulkh/ComfyUI-Lovis-Node" - ], - "install_type": "git-clone", - "description": "Custom nodes for text and audio processing in ComfyUI.\nNOTE: The files in the repo are not organized." - }, - { - "author": "doc", - "title": "doc-utils", - "reference": "https://github.com/doc-packages/comfyui-doc-utils", - "files": [ - "https://github.com/doc-packages/comfyui-doc-utils" - ], - "install_type": "git-clone", - "description": "NODES: DOC_SaveImageAndAddToHistory, DOC_RandomPromptChoice" - }, - { - "author": "obisin", - "title": "FSampler for ComfyUI — Fast Skips via Epsilon Extrapolation [WIP]", - "reference": "https://github.com/obisin/ComfyUI-FSampler", - "files": [ - "https://github.com/obisin/ComfyUI-FSampler" - ], - "install_type": "git-clone", - "description": "FSampler is a training‑free, sampler‑agnostic acceleration layer for diffusion sampling that reduces model calls by predicting each step’s epsilon (noise) from recent real calls and feeding it into the existing integrator. It provides fixed history modes (h2/h3/h4) and an aggressive adaptive mode that, per step, builds two predictions (e.g., h3 vs h2), compares their predicted next states to get a relative error, and skips the model call when that error is below a hardcoded tolerance. Predicted epsilons are validated and scaled by a universal learning stabilizer L, skips are bounded by guard rails, and the sampler math (Euler, RES 2M/2S, DDIM, DPM++ 2M/2S, LMS) is unchanged.\nNOTE: The files in the repo are not organized." - }, - { - "author": "remhesneb", - "title": "comfyui-remote-media", - "reference": "https://github.com/remhesneb/comfyui-remote-media", - "files": [ - "https://github.com/remhesneb/comfyui-remote-media" - ], - "install_type": "git-clone", - "description": "NODES: Load Image by URL, Load Video by URL" - }, - { - "author": "takoyaki1118", - "title": "ComfyUI-Kakimoji-Layer", - "reference": "https://github.com/takoyaki1118/ComfyUI-Kakimoji-Layer", - "files": [ - "https://github.com/takoyaki1118/ComfyUI-Kakimoji-Layer" - ], - "install_type": "git-clone", - "description": "NODES: Kakimoji Editor (Live Preview)" - }, - { - "author": "ethanfel", - "title": "ComfyUI-Mamad8 [WIP]", - "reference": "https://github.com/ethanfel/ComfyUI-Mamad8-QwenEdit-fix", - "files": [ - "https://github.com/ethanfel/ComfyUI-Mamad8-QwenEdit-fix" - ], - "install_type": "git-clone", - "description": "Router nodes for optimized batch processing\nNOTE: The files in the repo are not organized." - }, - { - "author": "wTechArtist", - "title": "ComfyUI-VVL-Tools", - "reference": "https://github.com/wTechArtist/ComfyUI-VVL-Tools", - "files": [ - "https://github.com/wTechArtist/ComfyUI-VVL-Tools" - ], - "install_type": "git-clone", - "description": "NODES: VVL For Loop Start (Async), VVL For Loop End (Async), VVL List Chunk (Async), VVL Batch Anything Dynamic, VVL Tensor List Preview, VVL JSON Object Deduplicator, ..." - }, - { - "author": "Goldlionren", - "title": "ComfyUI-CleanSlate [WIP]", - "reference": "https://github.com/Goldlionren/ComfyUI-CleanSlate", - "files": [ - "https://github.com/Goldlionren/ComfyUI-CleanSlate" - ], - "install_type": "git-clone", - "description": "CleanSlate: Post-Sample Cleanup — put it between Sampler and VAE Decode to free memory aggressively.\nNOTE: The files in the repo are not organized." - }, - { - "author": "systemaiofinterest-wq", - "title": "ComfyUI-GoogleFX", - "reference": "https://github.com/systemaiofinterest-wq/ComfyUI-GoogleFX", - "files": [ - "https://github.com/systemaiofinterest-wq/ComfyUI-GoogleFX" - ], - "install_type": "git-clone", - "description": "NODES: Google Imagen 3.5, Google Imagen R2I, Google Imagen Nano Banana x4" - }, - { - "author": "alisson-anjos", - "title": "ComfyUI-Workarounds [WIP]", - "reference": "https://github.com/alisson-anjos/ComfyUI-Workarounds", - "files": [ - "https://github.com/alisson-anjos/ComfyUI-Workarounds" - ], - "install_type": "git-clone", - "description": "This repository provides practical ComfyUI nodes for fast, planar face overlay without non-linear warping, plus flexible region masks to transfer the full head (face + hair + eyebrows + eyes + nose + mouth) or any combination of regions. It also includes optional color matching and JSON diagnostics to integrate with downstream logic.\nNOTE: The files in the repo are not organized." - }, - { - "author": "adamdavidcole", - "title": "ComfyUI XY Video Plot Suite [WIP]", - "reference": "https://github.com/adamdavidcole/comfyui-video-xy-plot", - "files": [ - "https://github.com/adamdavidcole/comfyui-video-xy-plot" - ], - "install_type": "git-clone", - "description": "A suite of ComfyUI nodes for generating parameter sweep video matrices with labeled grids. Create comprehensive visual comparisons of how different parameter combinations affect your video generation results.\nNOTE: The files in the repo are not organized." - }, - { - "author": "clubmate", - "title": "comfyui-boll-nodes", - "reference": "https://github.com/clubmate/comfyui-boll-nodes", - "files": [ - "https://github.com/clubmate/comfyui-boll-nodes" - ], - "install_type": "git-clone", - "description": "NODES: Get Last Image, Remove Last Image, WAN Config" - }, - { - "author": "vunguyen-oss", - "title": "ComfyUI-ccsrv2 [WIP]", - "reference": "https://github.com/vunguyen-oss/ComfyUI-ccsrv2", - "files": [ - "https://github.com/vunguyen-oss/ComfyUI-ccsrv2" - ], - "install_type": "git-clone", - "description": "NODES: CCSR Upscale" - }, - { - "author": "kesslerdev", - "title": "kesslerdev_comfyui_nodes [WIP/UNSAFE]", - "reference": "https://github.com/kesslerdev/kesslerdev_comfyui_nodes", - "files": [ - "https://github.com/kesslerdev/kesslerdev_comfyui_nodes" - ], - "install_type": "git-clone", - "description": "NODES: S3 Upload File, File Explorer, Save Image Export Path, Save Video Export Path, Save Audio Export Path, S3 Combine Images to Video, Multi-Line Text Input, ...\nNOTE: The files in the repo are not organized.[w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "kesslerdev", - "title": "Comfyui : kesslerdev_comfyui_nodes [UNSAFE]", - "reference": "https://github.com/kesslerdev/kesslerdev_comfyui_nodes", - "files": [ - "https://github.com/kesslerdev/kesslerdev_comfyui_nodes" - ], - "install_type": "git-clone", - "description": "NODES: Multiline Text Choice (Kesslerdev), Environment Variable Getter (Kesslerdev)[w/This nodepack includes a node that can remotely read arbitrary environment variables.]" - }, - { - "author": "panchovial-max", - "title": "ComfyUI-ListHelper [NAME CONFLICT]", - "reference": "https://github.com/panchovial-max/ComfyUI-ListHelper", - "files": [ - "https://github.com/panchovial-max/ComfyUI-ListHelper" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI that provides enhanced list manipulation and AI integration capabilities." - }, - { - "author": "Eric7758", - "title": "ComfyUI-IAT [WIP]", - "reference": "https://github.com/Eric7758/ComfyUI-IAT", - "files": [ - "https://github.com/Eric7758/ComfyUI-IAT" - ], - "install_type": "git-clone", - "description": "A ComfyUI plugin for image & text utilities with Qwen translator and prompt optimizer\nNOTE: The files in the repo are not organized." - }, - { - "author": "NeuroWaifu", - "title": "ComfyUI.Node.MediaSuite", - "reference": "https://github.com/NeuroWaifu/ComfyUI.Node.MediaSuite", - "files": [ - "https://github.com/NeuroWaifu/ComfyUI.Node.MediaSuite" - ], - "install_type": "git-clone", - "description": "NODES: Image Resize, Video Resize, Resize Calculator, Mask Operations, Mask Blur, Mask Threshold, Dominant Color, Color Palette, Shadows/Highlights/Midtones, Tone Curve, Color Grading, Upscale Video (using Model), Get Video Size, Get Video Info, Compare Video Size, Math Operation, Math Function, Boolean Logic, Compare Numbers, Convert Number, Random Number, Clamp, Remap, Smooth, Constants, ..." - }, - { - "author": "BizaNator", - "title": "BrainDead_QwenCharacterEditor [WIP]", - "reference": "https://github.com/BizaNator/BrainDead_QwenCharacterEditor", - "files": [ - "https://github.com/BizaNator/BrainDead_QwenCharacterEditor" - ], - "install_type": "git-clone", - "description": "Created by BizaNator for the BrainDeadGuild community. These professional-grade character editing tools for ComfyUI specialize in identity preservation and multi-reference image control.\nNOTE: The files in the repo are not organized." - }, - { - "author": "sk-palani", - "title": "ComfyUI_Simpler", - "reference": "https://github.com/sk-palani/ComfyUI_Simpler", - "files": [ - "https://github.com/sk-palani/ComfyUI_Simpler" - ], - "install_type": "git-clone", - "description": "This repository contains custom nodes designed for the ComfyUI framework, focusing on simplification improvements. These nodes aim to make tasks easier and simple" - }, - { - "author": "SushantSingh-23-01", - "title": "comfyui_flowrider_nodes [UNSAFE]", - "reference": "https://github.com/SushantSingh-23-01/comfyui_flowrider_nodes", - "files": [ - "https://github.com/SushantSingh-23-01/comfyui_flowrider_nodes" - ], - "install_type": "git-clone", - "description": "Collection of nodes performing random utility functions like: JPG Saver - Save images in jpg format. Resolution Selector - Selects from standard resolutions with aspect ratios.[w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "YokoYokoTEC", - "title": "comfyui-sequential-prompt-file [UNSAFE]", - "reference": "https://github.com/YokoYokoTEC/comfyui-sequential-prompt-file", - "files": [ - "https://github.com/YokoYokoTEC/comfyui-sequential-prompt-file" - ], - "install_type": "git-clone", - "description": "This is the file-based version of comfyui-sequential-prompt. For detailed usage instructions, please refer to the following link: [a/https://note.com/alive_gibbon2712/n/n0eb671383b30](https://note.com/alive_gibbon2712/n/n0eb671383b30)[w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "YokoYokoTEC", - "title": "ComfyUI Batch Sequence Node [UNSAFE]", - "reference": "https://github.com/YokoYokoTEC/comfyui-batch-sequence", - "files": [ - "https://github.com/YokoYokoTEC/comfyui-batch-sequence" - ], - "install_type": "git-clone", - "description": "This custom node is a toolkit designed to help you run batch processing (such as sequential image generation) more smoothly in ComfyUI.[w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "kaski23", - "title": "Kaskis_Comfy_Nodes", - "reference": "https://github.com/kaski23/Kaskis_Comfy_Nodes", - "files": [ - "https://github.com/kaski23/Kaskis_Comfy_Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Load Image (with Filename), Load Video (with Filename), String Split & Select, Int → String, Pair Switch, Unbroken-Video-Handler, Unbroken-Controlvideo-Handler, ..." - }, - { - "author": "asaddi", - "title": "Allan's ComfyUI nodes", - "reference": "https://github.com/asaddi/allans-comfy-nodes", - "files": [ - "https://github.com/asaddi/allans-comfy-nodes" - ], - "install_type": "git-clone", - "description": "Parameter Experimentation, Lists, Image Utilities, Utilities, Buses (Combiners/Splitters), Prompting, Switches, Tabular Data, Debugging, Third-Party Models" - }, - { - "author": "Suzu008", - "title": "ComfyUI-TTYd", - "reference": "https://github.com/Suzu008/ComfyUI-TTYd", - "files": [ - "https://github.com/Suzu008/ComfyUI-TTYd" - ], - "install_type": "git-clone", - "description": "A ComfyUI extension for web-based terminal access via side panel" - }, - { - "author": "Mets3D", - "title": "Mets-Nodes", - "reference": "https://github.com/Mets3D/mets-nodes", - "files": [ - "https://github.com/Mets3D/mets-nodes" - ], - "install_type": "git-clone", - "description": "Random nodes for now, may split them out later into properly maintained repos." - }, - { - "author": "downlifted", - "title": "ComfyUI_GroqPrompt [WIP]", - "reference": "https://github.com/downlifted/ComfyUI_GroqPrompt", - "files": [ - "https://github.com/downlifted/ComfyUI_GroqPrompt" - ], - "install_type": "git-clone", - "description": " Groq API Access in ComfyUI, Free Prompt Creation!\nNOTE: The files in the repo are not organized." - }, - { - "author": "kijai", - "title": "ComfyUI-WanAnimatePreprocess [WIP]", - "reference": "https://github.com/kijai/ComfyUI-WanAnimatePreprocess", - "files": [ - "https://github.com/kijai/ComfyUI-WanAnimatePreprocess" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for WanAnimate model input preprocessing" - }, - { - "author": "charlie-otoy", - "title": "ComfyUI-otoySKW-SystemMetrics", - "reference": "https://github.com/charlie-otoy/ComfyUI-otoySKW-SystemMetrics", - "files": [ - "https://github.com/charlie-otoy/ComfyUI-otoySKW-SystemMetrics" - ], - "install_type": "git-clone", - "description": "System monitoring API endpoints for ComfyUI by otoy SKW" - }, - { - "author": "lihaoyun6", - "title": "ComfyUI-lhyNodes", - "reference": "https://github.com/lihaoyun6/ComfyUI-lhyNodes", - "files": [ - "https://github.com/lihaoyun6/ComfyUI-lhyNodes" - ], - "install_type": "git-clone", - "description": "NODES: Mask to SAM2Coordinates, String Format, String Format (Advanced), CSV RandomPicker, CSV RandomPicker (Advanced)" - }, - { - "author": "orion4d", - "title": "Orion4D MasterPrompt Suite for ComfyUI [UNSAFE]", - "reference": "https://github.com/orion4d/Orion4D_MasterPrompt", - "files": [ - "https://github.com/orion4d/Orion4D_MasterPrompt" - ], - "install_type": "git-clone", - "description": "Welcome to the MasterPrompt suite, a collection of custom nodes for ComfyUI designed to supercharge your text, list, and JSON manipulation workflows. Whether you need to dynamically load styles from files, mix concepts with weighted probabilities, format complex prompts, or handle JSON data, this suite provides the necessary tools with intuitive and powerful user interfaces. As a bonus I have included a basic pack of thematic lists generated with gpt5, have fun![w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "adigayung", - "title": "comfyui_PlutoNode", - "reference": "https://github.com/adigayung/comfyui_PlutoNode", - "files": [ - "https://github.com/adigayung/comfyui_PlutoNode" - ], - "install_type": "git-clone", - "description": "NODES: 'Pluto : Auto Crop Faces', 'Pluto : Composite Image', 'Pluto : Float Math', 'Pluto : GetSizeFromImage', 'Pluto : Text Append', ..." - }, - { - "author": "lu64k", - "title": "ks_nodes", - "reference": "https://github.com/lu64k/ks_nodes", - "files": [ - "https://github.com/lu64k/ks_nodes" - ], - "install_type": "git-clone", - "description": "NODES: KS Text_String, KS Random File Name, KS Save Text, KS load text, KS get time int, KS json float range filter, KS json array constrains filter, KS json key replace, KS json value eliminator, KS json extract key and value, KS json key random, KS Json Count, KS Word Frequency Statistics, KS Load Images From Folder KS, KS Json To String, KS JsonlReader, KS JsonKeyReplacer, KS JsonKeyExtractor, KS_merge_json_node, KS_make_json_node, KS_qwen_image_api, KS_any_payload_image, KS JsonlFolderMatchReader, KS_image_metadata_node" - }, - { - "author": "DavidJBarnes", - "title": "comfyui-djb-utils", - "reference": "https://github.com/DavidJBarnes/comfyui-djb-utils", - "files": [ - "https://github.com/DavidJBarnes/comfyui-djb-utils" - ], - "install_type": "git-clone", - "description": "NODES: Working Filename Extractor, Direct Image Filename Input" - }, - { - "author": "husw725", - "title": "comfyui_alta_nodes", - "reference": "https://github.com/husw725/comfyui_alta_nodes", - "files": [ - "https://github.com/husw725/comfyui_alta_nodes" - ], - "install_type": "git-clone", - "description": "NODES: 'Alta:SaveImagePlus', 'Alta:LoadImagesPath', 'Alta:LoadVideoPath', 'Alta:LoadVideosFromFolder', 'Alta:VideoCombine', ..." - }, - { - "author": "LukeCoulson1", - "title": "comfyui-sundryutils", - "reference": "https://github.com/orpheus-gaze/comfyui-sundryutils", - "files": [ - "https://github.com/orpheus-gaze/comfyui-sundryutils" - ], - "install_type": "git-clone", - "description": "NODES: 'Return DateString', 'Sharpen, RealGrain & Autocontrast', 'Apply HaldClut'" - }, - { - "author": "dcduanchao", - "title": "comfyui_video_http", - "reference": "https://github.com/dcduanchao/comfyui_video_http", - "files": [ - "https://github.com/dcduanchao/comfyui_video_http" - ], - "install_type": "git-clone", - "description": "NODES: Video Upload Node" - }, - { - "author": "AIGCZero", - "title": "AIGCZero-comfyui-Qwen_edit-zero", - "reference": "https://github.com/AIGCZero/AIGCZero-comfyui-Qwen_edit-zero", - "files": [ - "https://github.com/AIGCZero/AIGCZero-comfyui-Qwen_edit-zero" - ], - "install_type": "git-clone", - "description": "A node that resolves the pixel offset issue in Qwen_edit." - }, - { - "author": "SlackinJack", - "title": "multigpu_diffusion_comfyui", - "reference": "https://github.com/SlackinJack/multigpu_diffusion_comfyui", - "files": [ - "https://github.com/SlackinJack/multigpu_diffusion_comfyui" - ], - "install_type": "git-clone", - "description": "NODES: GGUFSelector, CheckpointSelector, SchedulerSelector, LoraSelector, MultiLoraJoiner, VAESelector, MotionModuleSelector, MotionAdapterSelector, MotionAdapterLoraSelector, ControlNetSelector, IPAdapterSelector, EncodePromptWithCompel, HostConfig, AsyncDiffConfig, AsyncDiffADSampler, AsyncDiffSDSampler, AsyncDiffSDUpscaleSampler, AsyncDiffSVDSampler, DistrifuserConfig, DistrifuserSDSampler, xDiTConfig, xDiTSampler, xDiTUSPConfig, xDiTUSPImageSampler, xDiTUSPVideoSampler" - }, - { - "author": "dmitry-guskov", - "title": "ComfyUI_light_image_ops", - "reference": "https://github.com/dmitry-guskov/ComfyUI_light_image_ops", - "files": [ - "https://github.com/dmitry-guskov/ComfyUI_light_image_ops" - ], - "install_type": "git-clone", - "description": "NODES: Light Scale to Side, Light Batch to List, Light List to Batch, Wan Latents Resize To Size, Wan Embeds Resize Spatial (Packed), Wan Latents Resize + Normalize + Noise" - }, - { - "author": "78Ventures", - "title": "ComfyUI-Tortu [WIP/UNSAFE]", - "reference": "https://github.com/78Ventures/ComfyUI-Tortu", - "files": [ - "https://github.com/78Ventures/ComfyUI-Tortu" - ], - "install_type": "git-clone", - "description": "Opinionated custom nodes and ready-to-use workflows for dataset prep and portrait/headshot pipelines in ComfyUI.\nNOTE: The files in the repo are not organized.[w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "ches2010", - "title": "comfyui_aliyundrive_uploader", - "reference": "https://github.com/ches2010/comfyui_aliyundrive_uploader", - "files": [ - "https://github.com/ches2010/comfyui_aliyundrive_uploader" - ], - "install_type": "git-clone", - "description": "comfyui_aliyundrive_uploader" - }, - { - "author": "CooperCorona", - "title": "comfy-auto-unload", - "reference": "https://github.com/CooperCorona/comfy-auto-unload", - "files": [ - "https://github.com/CooperCorona/comfy-auto-unload" - ], - "install_type": "git-clone", - "description": "unloads models from VRAM in comfyui after a set period of time" - }, - { - "author": "polygoningenieur", - "title": "ComfyUI-Diffusion-SDXL-Video", - "reference": "https://github.com/Polygoningenieur/ComfyUI-Diffusion-SDXL-Video", - "files": [ - "https://github.com/Polygoningenieur/ComfyUI-Diffusion-SDXL-Video" - ], - "install_type": "git-clone", - "description": "ComfyUI node for a frame by frame Diffusion." - }, - { - "author": "ric-y", - "title": "ComfyUI Datadog Monitor [WIP]", - "reference": "https://github.com/ric-yu/comfyui-datadog-monitor", - "files": [ - "https://github.com/ric-yu/comfyui-datadog-monitor" - ], - "install_type": "git-clone", - "description": "Minimal custom node that enables comprehensive Datadog APM tracing and profiling for ComfyUI.\nNOTE: The files in the repo are not organized." - }, - { - "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": "NODES: WanVideo T5 Apply Soft Prefix" - }, - { - "author": "NSFW-API", - "title": "ComfyUI-WanSoftPrefix", - "reference": "https://github.com/NSFW-API/ComfyUI-WanSoftPrefix", - "files": [ - "https://github.com/NSFW-API/ComfyUI-WanSoftPrefix" - ], - "install_type": "git-clone", - "description": "NODES: Resize Frame, Pad Batch to 4n+1, Trim Padded Batch, Get Image Dimensions, Slot Frame, ..." - }, - { - "author": "rishipandey125", - "title": "ComfyUI-StyleFrame-Nodes", - "reference": "https://github.com/rishipandey125/ComfyUI-StyleFrame-Nodes", - "files": [ - "https://github.com/rishipandey125/ComfyUI-StyleFrame-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Resize Frame, Pad Batch to 4n+1, Trim Padded Batch, Get Image Dimensions, Slot Frame, ..." - }, - { - "author": "nicolabergamascahkimkoohi", - "title": "ComfyUI-OSS-Upload [UNSAFE]", - "reference": "https://github.com/ahkimkoo/ComfyUI-OSS-Upload", - "files": [ - "https://github.com/ahkimkoo/ComfyUI-OSS-Upload" - ], - "install_type": "git-clone", - "description": "A ComfyUI plugin for uploading generated images and videos to Alibaba Cloud OSS (Object Storage Service).[w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "threecrowco", - "title": "ComfyUI-FlowMatchScheduler [WIP]", - "reference": "https://github.com/threecrowco/ComfyUI-FlowMatchScheduler", - "files": [ - "https://github.com/threecrowco/ComfyUI-FlowMatchScheduler" - ], - "install_type": "git-clone", - "description": "NODES: FlowMatch → SIGMAS\nNOTE: The files in the repo are not organized." - }, - { - "author": "NimbleWing", - "title": "ComfyUI-NW", - "reference": "https://github.com/NimbleWing/ComfyUI-NW", - "files": [ - "https://github.com/NimbleWing/ComfyUI-NW" - ], - "install_type": "git-clone", - "description": "ComfyUI node for a frame by frame Diffusion." - }, - { - "author": "tfernd", - "title": "Auto CPU Offload for ComfyUI [WIP]", - "reference": "https://github.com/tfernd/ComfyUI-AutoCPUOffload", - "files": [ - "https://github.com/tfernd/ComfyUI-AutoCPUOffload" - ], - "install_type": "git-clone", - "description": "This extension introduces an 'Auto CPU Offload' node designed to reduce GPU VRAM usage by automatically offloading model components to the CPU. It intelligently manages the movement of model layers between the GPU and CPU, aiming to keep only the necessary parts in VRAM during inference." - }, - { - "author": "hujuying", - "title": "comfyui_gemini_banana_api [WIP]", - "reference": "https://github.com/hujuying/comfyui_gemini_banana_api", - "files": [ - "https://github.com/hujuying/comfyui_gemini_banana_api" - ], - "install_type": "git-clone", - "description": "A powerful ComfyUI plugin that supports image editing and generation using the Gemini API, featuring multiple API key rotation and secure storage.\nNOTE: The files in the repo are not organized." - }, - { - "author": "pft-ChenKu", - "title": "ComfyUI_system-dev [WIP]", - "reference": "https://github.com/pft-ChenKu/ComfyUI_system-dev", - "files": [ - "https://github.com/pft-ChenKu/ComfyUI_system-dev" - ], - "install_type": "git-clone", - "description": "Collect Vram ram and Time\nNOTE: The files in the repo are not organized." - }, - { - "author": "z604159435g", - "title": "comfyui_random_prompt_plugin [WIP]", - "reference": "https://github.com/z604159435g/comfyui_random_prompt_plugin", - "files": [ - "https://github.com/z604159435g/comfyui_random_prompt_plugin" - ], - "install_type": "git-clone", - "description": "A ComfyUI natural language prompt plugin specifically designed for generating realistic photos of Caucasian women. Completely restructured to produce smooth natural language paragraphs instead of CLIP-style formatting.\nNOTE: The files in the repo are not organized." - }, - { - "author": "edisonchan", - "title": "ComfyUI-Sysinfo", - "reference": "https://github.com/edisonchan/ComfyUI-Sysinfo", - "files": [ - "https://github.com/edisonchan/ComfyUI-Sysinfo" - ], - "install_type": "git-clone", - "description": "A simple ComfyUI custom node for checking system CUDA information and the PyTorch version." - }, - { - "author": "Goldlionren", - "title": "comfyui-spawner-schedulers", - "reference": "https://github.com/spawner1145/comfyui-spawner-schedulers", - "files": [ - "https://github.com/spawner1145/comfyui-spawner-schedulers" - ], - "install_type": "git-clone", - "description": "This plugin provides additional scheduler options for ComfyUI. Currently, it includes a smooth scheduler designed to optimize the quality and consistency of generated images." - }, - { - "author": "nicolabergamaschi", - "title": "ComfyUI_XLweb [UNSAFE]", - "reference": "https://github.com/853587221/ComfyUI_XLweb", - "files": [ - "https://github.com/853587221/ComfyUI_XLweb" - ], - "install_type": "git-clone", - "description": "An elegant web frontend for ComfyUI that simplifies complex node workflows.[w/This extension has a vulnerability that allows arbitrary access to local files from remote.]" - }, - { - "author": "nicolabergamaschi", - "title": "ComfyUI-Dynamic-Lora-Loader", - "reference": "https://github.com/BARKEM-JC/ComfyUI-Dynamic-Lora-Loader", - "files": [ - "https://github.com/BARKEM-JC/ComfyUI-Dynamic-Lora-Loader" - ], - "install_type": "git-clone", - "description": "A simple dynamic lora loader, supports hundreds of loras at once by loading on demand and automatically adjusting weights + prompt injection." - }, - { - "author": "kuailefengnan2024", - "title": "Comfyui_Layer", - "reference": "https://github.com/kuailefengnan2024/Comfyui_Layer", - "files": [ - "https://github.com/kuailefengnan2024/Comfyui_Layer" - ], - "install_type": "git-clone", - "description": "NODES: Vision API Node" - }, - { - "author": "tony-zn", - "title": "comfyui-zn-pycode [UNSAFE]", - "id": "comfyui-zn-pycode", - "reference": "https://github.com/tony-zn/comfyui-zn-pycode", - "files": [ - "https://github.com/tony-zn/comfyui-zn-pycode" - ], - "install_type": "git-clone", - "description": "This node allows you to run custom Python code for flexible data handling. It supports up to 20 input parameters and 20 output results, with unused slots automatically showing or hiding based on whether the parameter and result slots are connected." - }, - { - "author": "Solankimayursinh", - "title": "PMSnodes [WIP]", - "reference": "https://github.com/Solankimayursinh/PMSnodes", - "files": [ - "https://github.com/Solankimayursinh/PMSnodes" - ], - "install_type": "git-clone", - "description": "A custom nodes for ComfyUI to Load audio in Base64 format and Send Audio to Websocket in Base64 Format for creating API of Audio related AI\nNOTE: The files in the repo are not organized." - }, - { - "author": "vasilmitov", - "title": "ComfyUI-SeedSnapShotManager [WIP]", - "reference": "https://github.com/vasilmitov/ComfyUI-SeedSnapShotManager", - "files": [ - "https://github.com/vasilmitov/ComfyUI-SeedSnapShotManager" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node for saving and restoring random seeds. Useful for workflow reproducibility, experimentation, and quickly trying different variations.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Juste-Leo2", - "title": "ComfyUI-Arduino [WIP]", - "reference": "https://github.com/Juste-Leo2/ComfyUI-Arduino", - "files": [ - "https://github.com/Juste-Leo2/ComfyUI-Arduino" - ], - "install_type": "git-clone", - "description": "ComfyUI-Arduino aims to bridge the gap between ComfyUI's powerful generative AI workflows and the physical world through Arduino. This project allows you to design, code, and interact with Arduino boards directly from your ComfyUI nodes, opening up possibilities for AI-driven robotics, physical generative art, interactive installations, and more." - }, - { - "author": "Nienai666", - "title": "ComfyUI-NanoBanana-ImageGenerator", - "reference": "https://github.com/Nienai666/comfyui-encrypt-image-main", - "files": [ - "https://github.com/Nienai666/comfyui-encrypt-image-main" - ], - "install_type": "git-clone", - "description": "This is an image encryption extension for ComfyUI. If you load this extension, images will be stored in an encrypted form and cannot be previewed normally." - }, - { - "author": "Madygnomo", - "title": "ComfyUI-NanoBanana-ImageGenerator", - "reference": "https://github.com/Madygnomo/ComfyUI-NanoBanana-ImageGenerator", - "files": [ - "https://github.com/Madygnomo/ComfyUI-NanoBanana-ImageGenerator" - ], - "install_type": "git-clone", - "description": "NODES: NanoBanana Image Generator" - }, - { - "author": "jerryname2022", - "title": "MegaTTS 3 [WIP]", - "reference": "https://github.com/jerryname2022/ComfyUI-MegaTTS3", - "files": [ - "https://github.com/jerryname2022/ComfyUI-MegaTTS3" - ], - "install_type": "git-clone", - "description": "NODES: MegaTTS3 Model Loader, MegaTTS3 Zero Shot, Text Editor\nNOTE: The files in the repo are not organized." - }, - { - "author": "punicfaith", - "title": "ComfyUI-GoogleAIStudio", - "reference": "https://github.com/punicfaith/ComfyUI-GoogleAIStudio", - "files": [ - "https://github.com/punicfaith/ComfyUI-GoogleAIStudio" - ], - "install_type": "git-clone", - "description": "NODES: Google Gemini Prompt" - }, - { - "author": "numq", - "title": "comfyui-camera-capture-node", - "reference": "https://github.com/numq/comfyui-camera-capture-node", - "files": [ - "https://github.com/numq/comfyui-camera-capture-node" - ], - "install_type": "git-clone", - "description": "NODES: Camera Capture" - }, - { - "author": "maoper11", - "title": "ComfyUI_Inteliweb_nodes", - "reference": "https://github.com/maoper11/comfyui_inteliweb_nodes", - "files": [ - "https://github.com/maoper11/comfyui_inteliweb_nodes" - ], - "install_type": "git-clone", - "description": "System Check (Inteliweb): RAM/VRAM live, Free RAM/VRAM buttons, Py Libs. + Photopea Editor integration (menu contextual)." - }, - { - "author": "casterpollux", - "title": "ComfyUI Affine Transform Mapper [WIP]", - "reference": "https://github.com/GuardSkill/ComfyUI-AffineImage", - "files": [ - "https://github.com/GuardSkill/ComfyUI-AffineImage" - ], - "install_type": "git-clone", - "description": "Insert one image into specific location on another image by affine transform.\nNOTE: The files in the repo are not organized." - }, - { - "author": "casterpollux", - "title": "ComfyUI USO Custom Node [WIP]", - "reference": "https://github.com/casterpollux/ComfyUI-USO", - "files": [ - "https://github.com/casterpollux/ComfyUI-USO" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node implementation of ByteDance's USO (Unified Style and Subject-Driven Generation) model, which enables advanced style transfer and subject preservation using FLUX.\nNOTE: The files in the repo are not organized." - }, - { - "author": "numq", - "title": "comfyui-camera-capture-node", - "reference": "https://github.com/numq/comfyui-camera-capture-node", - "files": [ - "https://github.com/numq/comfyui-camera-capture-node" - ], - "install_type": "git-clone", - "description": "NODES: Camera Capture" - }, - { - "author": "pickles", - "title": "PyPromptGenerator [UNSAFE]", - "reference": "https://github.com/pickles/ComfyUI-PyPromptGenerator", - "files": [ - "https://github.com/pickles/ComfyUI-PyPromptGenerator" - ], - "install_type": "git-clone", - "description": "Generate positve/negative prompt via python script. [w/This node allows you to execute arbitrary code via the workflow.]" - }, - { - "author": "bmgjet", - "title": "ComfyUI GPU Power Limit Node", - "reference": "https://github.com/bmgjet/comfyui-powerlimit", - "files": [ - "https://github.com/bmgjet/comfyui-powerlimit" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node to change NVIDIA GPU power limits dynamically while passing through any input. NOTE: Windows only.[w/This node warns if ComfyUI is not running as administrator/root — it does not attempt to elevate the process.]" - }, - { - "author": "Karlmeister", - "title": "comfyui-karlmeister-nodes-suit", - "reference": "https://github.com/Karlmeister/comfyui-karlmeister-nodes-suit", - "files": [ - "https://github.com/Karlmeister/comfyui-karlmeister-nodes-suit" - ], - "install_type": "git-clone", - "description": "NODES: Seed with Filename Generator, KSampler Config Selector, KSampler Config Selector With Tuple Output, KSampler Config Tuple, Text Concatenator, A If Not None., Split a string. " - }, - { - "author": "ServiceStack", - "title": "classifier-agent", - "reference": "https://github.com/ServiceStack/classifier-agent", - "files": [ - "https://github.com/ServiceStack/classifier-agent" - ], - "install_type": "git-clone", - "description": "NODES: Classifiy Image, Classifiy Audio" - }, - { - "author": "elfatherbrown", - "title": "Real-CUGAN ComfyUI Custom Node [WIP]", - "reference": "https://github.com/elfatherbrown/comfyui-realcugan-node", - "files": [ - "https://github.com/elfatherbrown/comfyui-realcugan-node" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node implementation for Real-CUGAN anime/illustration upscaling models from Bilibili's AI Lab.\nNOTE: The files in the repo are not organized." - }, - { - "author": "sthao42", - "title": "ComfyUI Melodkeet TTS", - "reference": "https://github.com/sthao42/comfyui-melodkeet-tts", - "files": [ - "https://github.com/sthao42/comfyui-melodkeet-tts" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that provides a simple and direct way to use OpenAI-compatible Text-to-Speech (TTS) services." - }, - { - "author": "Jairodaniel-17", - "title": "ComfyUI-traductor-offline", - "reference": "https://github.com/Jairodaniel-17/ComfyUI-traductor-offline", - "files": [ - "https://github.com/Jairodaniel-17/ComfyUI-traductor-offline" - ], - "install_type": "git-clone", - "description": "NODES: Traductor: CLIP Texto (EN↔ES), Traductor: Prompt Texto (EN↔ES)" - }, - { - "author": "aesethtics", - "title": "ComfyUI-OpenPoser [WIP]", - "reference": "https://github.com/aesethtics/ComfyUI-OpenPoser", - "files": [ - "https://github.com/aesethtics/ComfyUI-OpenPoser" - ], - "install_type": "git-clone", - "description": "ComfyUI-OpenPoser is a custom node extension for ComfyUI that provides an interactive pose editor.\nIt allows you to freely define 18 OpenPose-style keypoints (joints) directly in the ComfyUI front-end without bone-length constraints! The node generates an image tensor representing the skeleton that can be passed to models which support OpenPose/DWPose/etc input." - }, - { - "author": "dead-matrix", - "title": "ComfyUI-RMBG-Custom", - "reference": "https://github.com/dead-matrix/ComfyUI-RMBG-Custom", - "files": [ - "https://github.com/dead-matrix/ComfyUI-RMBG-Custom" - ], - "install_type": "git-clone", - "description": "NODES: RMBG Custom" - }, - { - "author": "gmammolo", - "title": "comfyui-gmammolo", - "reference": "https://github.com/gmammolo/comfyui-gmammolo", - "files": [ - "https://github.com/gmammolo/comfyui-gmammolo" - ], - "install_type": "git-clone", - "description": "NODES: Simple Textbox, Filter Text Prompt, Check Image Generation" - }, - { - "author": "543872524", - "title": "ComfyUI_crdong", - "reference": "https://github.com/543872524/ComfyUI_crdong", - "files": [ - "https://github.com/543872524/ComfyUI_crdong" - ], - "install_type": "git-clone", - "description": "NODES: INT Constant, Simple Int Math Handle, Simple Json Array Handle, Simple Json Object Handle, Select Image Size, Video Time & FPS, Video Frame Size, Wan22 Step Handle, Prompt Selector String, Prompt Example Node, Prompt Join or List, Prompt List, CRD Audio Length Node, ..." - }, - { - "author": "Saganaki22", - "title": "ComfyUI YTDL Nodes [WIP]", - "reference": "https://github.com/Saganaki22/ComfyUI-ytdl_nodes", - "files": [ - "https://github.com/Saganaki22/ComfyUI-ytdl_nodes" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes for downloading, converting, and previewing audio/video from YouTube and 1,000+ other platforms" - }, - { - "author": "comfyscript", - "title": "ComfyUI-CloudClient", - "reference": "https://github.com/comfyscript/ComfyUI-CloudClient", - "files": [ - "https://github.com/comfyscript/ComfyUI-CloudClient" - ], - "install_type": "git-clone", - "description": "Design to Easily Remote Operate ComfyUI in the Cloud" - }, - { - "author": "RobbertB80", - "title": "ComfyUI SharePoint/OneDrive Upload Node [UNSAFE]", - "reference": "https://github.com/RobbertB80/ComfyUI-SharePoint-Upload", - "files": [ - "https://github.com/RobbertB80/ComfyUI-SharePoint-Upload" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that automatically uploads generated images to SharePoint or OneDrive document libraries.[w/This nodepack contains a node that can write files to an arbitrary path.]" - }, - { - "author": "KoinnAI", - "title": "ComfyUI Dynamic Prompting Simplified [WIP]", - "reference": "https://github.com/KoinnAI/ComfyUI-DynPromptSimplified", - "files": [ - "https://github.com/KoinnAI/ComfyUI-DynPromptSimplified" - ], - "install_type": "git-clone", - "description": "A minimal dynamic prompting + mirrored wildcards node for ComfyUI.\nNOTE: The files in the repo are not organized." - }, - { - "author": "jtrue", - "title": "MaskTools", - "reference": "https://github.com/jtrue/ComfyUI-MaskTools", - "files": [ - "https://github.com/jtrue/ComfyUI-MaskTools" - ], - "install_type": "git-clone", - "description": "Pixel-selection tools (masks) for ComfyUI — modular." - }, - { - "author": "nadushu", - "title": "comfyui-handy-nodes [UNSAFE]", - "reference": "https://github.com/nadushu/comfyui-handy-nodes", - "files": [ - "https://github.com/nadushu/comfyui-handy-nodes" - ], - "install_type": "git-clone", - "description": "NODES: Empty Random Latent Image, Filename Prompt Extractor, My Image Save, Queue Batch Fixed Seed, Text Cleaner, Text Splitter[w/This nodepack contains a node that can write files to an arbitrary path.]" - }, - { - "author": "borisfaley", - "title": "ComfyUI-ACES-EXR-OCIOr [UNSAFE]", - "reference": "https://github.com/borisfaley/ComfyUI-ACES-EXR-OCIO", - "files": [ - "https://github.com/borisfaley/ComfyUI-ACES-EXR-OCIO" - ], - "install_type": "git-clone", - "description": "Save images and videos in ACESCg or ACES-2065-1[w/This nodepack contains a node that can write files to an arbitrary path.]" - }, - { - "author": "NSFW-API", - "title": "ComfyUI-Embedding-Delta-Adapter", - "reference": "https://github.com/NSFW-API/ComfyUI-Embedding-Delta-Adapter", - "files": [ - "https://github.com/NSFW-API/ComfyUI-Embedding-Delta-Adapter" - ], - "install_type": "git-clone", - "description": "NODES: Load EmbDelta Adapter, Apply EmbDelta (WAN TextEmbeds)" - }, - { - "author": "clcimir", - "title": "FileTo64", - "reference": "https://github.com/clcimir/FileTo64", - "files": [ - "https://github.com/clcimir/FileTo64" - ], - "install_type": "git-clone", - "description": "ComfyUI FileTo64" - }, - { - "author": "LittleTechPomp", - "title": "comfyui-pixxio", - "reference": "https://github.com/LittleTechPomp/comfyui-pixxio", - "files": [ - "https://github.com/LittleTechPomp/comfyui-pixxio" - ], - "install_type": "git-clone", - "description": "NODES: Load Image from Pixx.io, Auto-Upload Image to Pixxio Collection" - }, - { - "author": "RomanticQq", - "title": "ComfyUI-Groudingdino-Sam", - "reference": "https://github.com/RomanticQq/ComfyUI-Groudingdino-Sam", - "files": [ - "https://github.com/RomanticQq/ComfyUI-Groudingdino-Sam" - ], - "install_type": "git-clone", - "description": "NODES: GroundingDino, GroundedSam2CutGaussian" - }, - { - "author": "Firetheft", - "title": "ComfyUI Local Media Manager [UNSAFE]", - "reference": "https://github.com/Firetheft/ComfyUI_Local_Media_Manager", - "files": [ - "https://github.com/Firetheft/ComfyUI_Local_Media_Manager" - ], - "install_type": "git-clone", - "description": "The Ultimate Local File Manager for Images, Videos, and Audio in ComfyUI.[w/This nodepack provides functionality to access files through an endpoint.]" - }, - { - "author": "Omario92", - "title": "ComfyUI-OmarioNodes", - "reference": "https://github.com/Omario92/ComfyUI-OmarioNodes", - "files": [ - "https://github.com/Omario92/ComfyUI-OmarioNodes" - ], - "install_type": "git-clone", - "description": "NODES: Dual Endpoint Color Blend (by Frames)" - }, - { - "author": "locphan201", - "title": "ComfyUI-Alter-Nodes", - "reference": "https://github.com/locphan201/ComfyUI-Alter-Nodes", - "files": [ - "https://github.com/locphan201/ComfyUI-Alter-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Alter MMAudio Config, Alter MMAudio Model Loader, Alter MMAudio Feature Utils, Alter MMAudio Sampler" - }, - { - "author": "mrCodinghero", - "title": "ComfyUI File Transfer Plugin (comfyui-rsync-plugin) [UNSAFE]", - "reference": "https://github.com/tg-tjmitchell/comfyui-rsync-plugin", - "files": [ - "https://github.com/tg-tjmitchell/comfyui-rsync-plugin" - ], - "install_type": "git-clone", - "description": "Lightweight helper for using rsync and rclone from ComfyUI with a dedicated UI panel. This repository contains Python wrappers for file transfer CLI tools and a ComfyUI plugin that adds a user-friendly panel for file transfer operations." - }, - { - "author": "mrCodinghero", - "title": "ComfyUI-Codinghero", - "reference": "https://github.com/mrCodinghero/ComfyUI-Codinghero", - "files": [ - "https://github.com/mrCodinghero/ComfyUI-Codinghero" - ], - "install_type": "git-clone", - "description": "NODES: Image Size, Video Settings" - }, - { - "author": "Vsolon", - "title": "ComfyUI-CBZ-Pack [UNSAFE]", - "reference": "https://github.com/Vsolon/ComfyUI-CBZ-Pack", - "files": [ - "https://github.com/Vsolon/ComfyUI-CBZ-Pack" - ], - "install_type": "git-clone", - "description": "Nodes for Handling CBZ MetaData and Images as List or Bash.[w/This nodepack contains a node that has a vulnerability allowing access to arbitrary file paths.]" - }, - { - "author": "odedgranot", - "title": "ComfyUI Video Save Node [UNSAFE]", - "reference": "https://github.com/odedgranot/comfyui_video_save_node", - "files": [ - "https://github.com/odedgranot/comfyui_video_save_node" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node that saves video outputs as H.264 .mp4 files with unique naming and returns the file path as a string.[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "odedgranot", - "title": "ComfyUI FFmpeg Node [UNSAFE]", - "reference": "https://github.com/odedgranot/comfyui-ffmpeg-node", - "files": [ - "https://github.com/odedgranot/comfyui-ffmpeg-node" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node that allows you to run FFmpeg commands directly within your ComfyUI workflows. [w/This nodepack contains a vulnerability that allows remote code execution.]" - }, - { - "author": "viik420", - "title": "Model Copy Node for ComfyUI [UNSAFE]", - "reference": "https://github.com/apeirography/ModelCopyNode", - "files": [ - "https://github.com/apeirography/ModelCopyNode" - ], - "install_type": "git-clone", - "description": "A simple ComfyUI custom node that copies model files from the models/ folder to the output/ folder.[w/This nodepack has a vulnerability that allows writing files to arbitrary paths.]" - }, - { - "author": "viik420", - "title": "AdvancedModelDownloader [UNSAFE]", - "reference": "https://github.com/viik420/AdvancedModelDownloader", - "files": [ - "https://github.com/viik420/AdvancedModelDownloader" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that adds a powerful, integrated downloader to the main menu, complete with an automatic update checker.[w/This nodepack provides functionality to access files through an endpoint.]" - }, - { - "author": "DenRakEiw", - "title": "Comfyui-Aspect-Ratio-Processor [WIP]", - "reference": "https://github.com/DenRakEiw/Comfyui-Aspect-Ratio-Processor", - "files": [ - "https://github.com/DenRakEiw/Comfyui-Aspect-Ratio-Processor" - ], - "install_type": "git-clone", - "description": "Comfyui Aspect Ratio Processor 2:3 / 3:2\nNOTE: The files in the repo are not organized." - }, - { - "author": "yuvraj108c", - "title": "ComfyUI HYPIR [NAME CONFLICT]", - "reference": "https://github.com/yuvraj108c/ComfyUI-HYPIR", - "files": [ - "https://github.com/yuvraj108c/ComfyUI-HYPIR" - ], - "install_type": "git-clone", - "description": "This project is a ComfyUI wrapper for [a/HYPIR](https://github.com/XPixelGroup/HYPIR) (Harnessing Diffusion-Yielded Score Priors for Image Restoration)" - }, - { - "author": "miabrahams", - "title": "ComfyUI-WebAutomation [UNSAFE]", - "reference": "https://github.com/miabrahams/ComfyUI-WebAutomation", - "files": [ - "https://github.com/miabrahams/ComfyUI-WebAutomation" - ], - "install_type": "git-clone", - "description": "Automation for ComfyUI Web UI [w/This nodepack provides functionality to access files through an endpoint.]" - }, - { - "author": "Rizzlord", - "title": "ComfyUI-SeqTex", - "reference": "https://github.com/Rizzlord/ComfyUI-SeqTex", - "files": [ - "https://github.com/Rizzlord/ComfyUI-SeqTex" - ], - "install_type": "git-clone", - "description": "NODES: SeqTex Load Mesh, SeqTex Loader, SeqTex Step 1: Process Mesh, SeqTex Step 2: Generate Condition, SeqTex Step 3: Generate Texture, SeqTex Step 4: Apply Texture to Trimesh" - }, - { - "author": "BiodigitalJaz", - "title": "ComfyUI-Dafaja-Nodes [WIP]", - "reference": "https://github.com/BiodigitalJaz/ComfyUI-Dafaja-Nodes", - "files": [ - "https://github.com/BiodigitalJaz/ComfyUI-Dafaja-Nodes" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes for 3D mesh processing and STL export\nNOTE: The files in the repo are not organized." - }, - { - "author": "ervinne13", - "title": "ComfyUI-Metadata-Hub", - "reference": "https://github.com/ervinne13/ComfyUI-Metadata-Hub", - "files": [ - "https://github.com/ervinne13/ComfyUI-Metadata-Hub" - ], - "install_type": "git-clone", - "description": "NODES: Metadata Hub, Save Image With Metadata" - }, - { - "author": "mico-world", - "title": "comfyui_mico_node", - "reference": "https://github.com/mico-world/comfyui_mico_node", - "files": [ - "https://github.com/mico-world/comfyui_mico_node" - ], - "install_type": "git-clone", - "description": "NODES: HF UNET Loader" - }, - { - "author": "GuusF", - "title": "Comfyui_CrazyMaths [WIP]", - "reference": "https://github.com/GuusF/Comfyui_CrazyMaths", - "files": [ - "https://github.com/GuusF/Comfyui_CrazyMaths" - ], - "install_type": "git-clone", - "description": "A custom nodepack with a bunch of nodes that helps you generate fun math paterns directly inside of comfyui for masking or other reasons.\nNOTE: The files in the repo are not organized." - }, - { - "author": "TimothyCMeehan", - "title": "ComfyUI CK3 Presets", - "reference": "https://github.com/TimothyCMeehan/comfyui-ck3-presets", - "files": [ - "https://github.com/TimothyCMeehan/comfyui-ck3-presets" - ], - "install_type": "git-clone", - "description": "ComfyUI custom nodes for Crusader Kings III modding - size presets, image resize, style helpers" - }, - { - "author": "driftjohnson", - "title": "DaimalyadNodes [WIP]", - "reference": "https://github.com/MushroomFleet/DJZ-Nodes", - "files": [ - "https://github.com/MushroomFleet/DJZ-Nodes" - ], - "install_type": "git-clone", - "description": "AspectSize and 100 more nodes\nNOTE: The files in the repo are not organized." - }, - { - "author": "tnil25", - "title": "ComfyUI-TJNodes [WIP]", - "reference": "https://github.com/tnil25/ComfyUI-TJNodes", - "files": [ - "https://github.com/tnil25/ComfyUI-TJNodes" - ], - "install_type": "git-clone", - "description": "NODES: Point Tracker\nNOTE: The files in the repo are not organized." - }, - { - "author": "zhu733756", - "title": "ivan_knows [UNSAFE]", - "reference": "https://github.com/Babiduba/ivan_knows", - "files": [ - "https://github.com/Babiduba/ivan_knows" - ], - "install_type": "git-clone", - "description": "NODES: Role Selector, Save Absolute. [w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "zhu733756", - "title": "Comfyui-Anything-Converter [UNSAFE]", - "reference": "https://github.com/zhu733756/Comfyui-Anything-Converter", - "files": [ - "https://github.com/zhu733756/Comfyui-Anything-Converter" - ], - "install_type": "git-clone", - "description": "This is a custom node extension designed for ComfyUI, providing JSON/TEXT/IMG handling functionality etc.[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "twj515895394", - "title": "ComfComfyUI-LowMemVideoSuite [UNSAFE]", - "reference": "https://github.com/twj515895394/ComfyUI-LowMemVideoSuite", - "files": [ - "https://github.com/twj515895394/ComfyUI-LowMemVideoSuite" - ], - "install_type": "git-clone", - "description": "This is a low-memory video composition plugin designed for ComfyUI, which uses FFmpeg to combine disk-stored frame images into a video, avoiding loading all frames into memory at once.[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "chenpipi0807", - "title": "ComfyUI-InstantCharacterFlux [WIP]", - "reference": "https://github.com/chenpipi0807/ComfyUI-InstantCharacterFlux", - "files": [ - "https://github.com/chenpipi0807/ComfyUI-InstantCharacterFlux" - ], - "install_type": "git-clone", - "description": "NODES: IC → FLUX One-Knob, IC Strength Controller (InstantCharacter → FLUX), Load IC Weights, Load SigLIP Vision, Load DINOv2 Vision, Encode Reference Image (InstantCharacter)\nNOTE: The files in the repo are not organized." - }, - { - "author": "Yuan-ManX", - "title": "ComfyUI-Step1X-Edit [NAME CONFLICT]", - "reference": "https://github.com/Yuan-ManX/ComfyUI-Step1X-Edit", - "files": [ - "https://github.com/Yuan-ManX/ComfyUI-Step1X-Edit" - ], - "install_type": "git-clone", - "description": "Make Step1X-Edit avialbe in ComfyUI." - }, - { - "author": "hben35096", - "title": "hben35096/ComfyUI-ToolBox [NAME CONFLICT]", - "id": "hben-toolbox", - "reference": "https://github.com/hben35096/ComfyUI-ToolBox", - "files": [ - "https://github.com/hben35096/ComfyUI-ToolBox" - ], - "install_type": "git-clone", - "description": "A collection of utility nodes for ComfyUI, including audio/video processing, file uploads, and AI image generation." - }, - { - "author": "locphan201", - "title": "ComfyUI-Alternatives", - "reference": "https://github.com/locphan201/ComfyUI-Alternatives", - "files": [ - "https://github.com/locphan201/ComfyUI-Alternatives" - ], - "install_type": "git-clone", - "description": "NODES: LoraPreLoader, LoraApplier" - }, - { - "author": "tg-tjmitchell", - "title": "ComfyUI Manager Package Lister", - "reference": "https://github.com/tg-tjmitchell/comfyui-custom-node-lister", - "files": [ - "https://github.com/tg-tjmitchell/comfyui-custom-node-lister" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that lists installed custom nodepackages in ComfyUI Manager compatible format, providing the exact package names and install commands for sharing or reinstalling." - }, - { - "author": "duckmartians", - "title": "Duck_Nodes [UNSAFE]", - "reference": "https://github.com/duckmartians/Duck_Nodes", - "files": [ - "https://github.com/duckmartians/Duck_Nodes" - ], - "install_type": "git-clone", - "description": "Load data from Google Sheets, Google Docs, Excel, Word, and TXT with built-in login system for ComfyUI.[w/This nodepack contains a node with a vulnerability that allows reading files from arbitrary paths.]" - }, - { - "author": "xsai-collab", - "title": "ComfyUI-CombineVideoAndSubtitle", - "reference": "https://github.com/xsai-collab/ComfyUI-CombineVideoAndSubtitle", - "files": [ - "https://github.com/xsai-collab/ComfyUI-CombineVideoAndSubtitle" - ], - "install_type": "git-clone", - "description": "NODES: Combine Video and Subtitle" - }, - { - "author": "Lovzu", - "title": "ComfyUI-Qwen [NAME CONFLICT]", - "reference": "https://github.com/Lovzu/ComfyUI-Qwen", - "files": [ - "https://github.com/Lovzu/ComfyUI-Qwen" - ], - "install_type": "git-clone", - "description": "This custom node Qwen3 designed to integrate with a GPT-based system under the category GPT_QWEN/Qwen. It serves as an interface to interact with the Qwen language model, specifically the 'Qwen/Qwen3-4B-Instruct-2507' variant." - }, - { - "author": "MatthewClayHarrison", - "title": "MetaMan - Universal AI Image Metadata Manager [UNSAFE]", - "reference": "https://github.com/MatthewClayHarrison/ComfyUI-MetaMan", - "files": [ - "https://github.com/MatthewClayHarrison/ComfyUI-MetaMan" - ], - "install_type": "git-clone", - "description": "First universal metadata system for AI image generation, with template-driven architecture allowing easy extension to new services; comprehensive dependency tracking with automatic download resolution; lossless conversion between platform formats where possible; future-proof design with extensible schema and validation system.[w/This nodepack has a vulnerability that allows remote access to arbitrary file paths.]" - }, - { - "author": "Charonartist", - "title": "ComfyUI LoRA Random Selector", - "reference": "https://github.com/Charonartist/comfyui-lora-random-selector", - "files": [ - "https://github.com/Charonartist/comfyui-lora-random-selector" - ], - "install_type": "git-clone", - "description": "NODES: WanMoeKSampler, WanMoeKSamplerAdvanced" - }, - { - "author": "Charonartist", - "title": "ComfyUI LoRA Random Selector [WIP]", - "reference": "https://github.com/Charonartist/comfyui-lora-random-selector", - "files": [ - "https://github.com/Charonartist/comfyui-lora-random-selector" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that randomly selects LoRA files by category and automatically applies corresponding trigger words.\nNOTE: The files in the repo are not organized." - }, - { - "author": "idoru", - "title": "Filestash Upload Node [UNSAFE]", - "reference": "https://github.com/idoru/ComfyUI-SKCFI-NetworkFileIO", - "files": [ - "https://github.com/idoru/ComfyUI-SKCFI-NetworkFileIO" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for uploading files to Filestash server.[w/This nodepack has a vulnerability that allows remote access to arbitrary file paths.]" - }, - { - "author": "HWDigi", - "title": "Camera Factory Station [WIP]", - "reference": "https://github.com/HWDigi/Camera_Factory_Station_comfyui", - "files": [ - "https://github.com/HWDigi/Camera_Factory_Station_comfyui" - ], - "install_type": "git-clone", - "description": "Universal Photography & Visual Enhancement Suite for ComfyUI\nThe most comprehensive collection of 5 specialized nodes providing 600+ professional options for complete photography coverage. Designed to handle everything anyone needs to create professional images across all formats, platforms, and industries - from basic snapshots to high-end commercial photography." - }, - { - "author": "sschleis", - "title": "sschl-comfyui-notes", - "reference": "https://github.com/sschleis/sschl-comfyui-notes", - "files": [ - "https://github.com/sschleis/sschl-comfyui-notes" - ], - "install_type": "git-clone", - "description": "NODES: Add Numbers, Float to String, Input Text, Show Text, Combine Strings, Text Appender, SSchl Text Encoder, Character, Connector" - }, - { - "author": "xgfone", - "title": "ComfyUI_RasterCardMaker", - "reference": "https://github.com/xgfone/ComfyUI_RasterCardMaker", - "files": [ - "https://github.com/xgfone/ComfyUI_RasterCardMaker" - ], - "install_type": "git-clone", - "description": "NODES: Raster Card Maker" - }, - { - "author": "majocola", - "title": "Standbybutton", - "reference": "https://github.com/majocola/comfyui-standbybutton", - "files": [ - "https://github.com/majocola/comfyui-standbybutton" - ], - "install_type": "git-clone", - "description": "A Simple NODE for a Standbybutton in ComyUi. It works also with the webinterface." - }, - { - "author": "Dream-Pixels-Forge", - "title": "ComfyUI-Mzikart-Vocal [WIP]", - "reference": "https://github.com/Dream-Pixels-Forge/ComfyUI-Mzikart-Vocal", - "files": [ - "https://github.com/Dream-Pixels-Forge/ComfyUI-Mzikart-Vocal" - ], - "install_type": "git-clone", - "description": "Vocals mastering nodes for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "Dream-Pixels-Forge", - "title": "ComfyUI-RendArt-Nodes", - "reference": "https://github.com/Dream-Pixels-Forge/ComfyUI-RendArt-Nodes", - "files": [ - "https://github.com/Dream-Pixels-Forge/ComfyUI-RendArt-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: RendArt Ultimate, RendArt Pro (Legacy)" - }, - { - "author": "Karniverse", - "title": "ComfyUI-Randomselector", - "reference": "https://github.com/Karniverse/ComfyUI-Randomselector", - "files": [ - "https://github.com/Karniverse/ComfyUI-Randomselector" - ], - "install_type": "git-clone", - "description": "A node that dynamically accepts multiple inputs of the same type and selects one based on choice or randomly." - }, - { - "author": "thaakeno", - "title": "comfyui-universal-asset-downloader [UNSAFE/WIP]", - "reference": "https://github.com/thaakeno/comfyui-universal-asset-downloader", - "files": [ - "https://github.com/thaakeno/comfyui-universal-asset-downloader" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that intelligently downloads assets from Civitai, Hugging Face, and MEGA.\nNOTE: The files in the repo are not organized.[w/This nodepack has a vulnerability that allows remote access to arbitrary file paths.]" - }, - { - "author": "77oussam", - "title": "Alo77 - ComfyUI Custom Nodes Collection [WIP]", - "reference": "https://github.com/77oussam/Aio77-Comfyui", - "files": [ - "https://github.com/77oussam/Aio77-Comfyui" - ], - "install_type": "git-clone", - "description": "A comprehensive collection of three powerful ComfyUI custom nodes for advanced image processing workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "xgfone", - "title": "ComfyUI_FaceToMask", - "reference": "https://github.com/xgfone/ComfyUI_FaceToMask", - "files": [ - "https://github.com/xgfone/ComfyUI_FaceToMask" - ], - "install_type": "git-clone", - "description": "NODES: Face To Mask(Copy)" - }, - { - "author": "JasonW146", - "title": "JasonW146", - "reference": "https://github.com/pururin777/ComfyUI-Manual-Openpose", - "files": [ - "https://github.com/pururin777/ComfyUI-Manual-Openpose" - ], - "install_type": "git-clone", - "description": "ComfyUI node that provides the ability to manually map out Controlnet Openpose landmarks for a batch of images." - }, - { - "author": "slezica", - "title": "slezica/ComfyUI Personal Nodes", - "reference": "https://github.com/slezica/comfyui-personal", - "files": [ - "https://github.com/slezica/comfyui-personal" - ], - "install_type": "git-clone", - "description": "A custom node collection for ComfyUI containing simplified workflow nodes and enhanced UI features for my personal use." - }, - { - "author": "boggerrr1110", - "title": "Boggerrr Nodes [WIP]", - "reference": "https://github.com/mamamia1110/comfyui-boggerrr-nodes", - "files": [ - "https://github.com/mamamia1110/comfyui-boggerrr-nodes" - ], - "install_type": "git-clone", - "description": "A node for comfyui to use seedream3.0 and seededit3.0\nNOTE: The files in the repo are not organized." - }, - { - "author": "jonathan-bryant", - "title": "ComfyUI-ImageStraightener [WIP]", - "reference": "https://github.com/jonathan-bryant/ComfyUI-ImageStraightener", - "files": [ - "https://github.com/jonathan-bryant/ComfyUI-ImageStraightener" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that automatically detects and corrects image tilt/rotation to straighten images. This node uses computer vision techniques to detect lines in the image and calculate the optimal rotation angle to straighten the image.\nNOTE: The files in the repo are not organized." - }, - { - "author": "adithis197", - "title": "ComfyUI-multimodal-CaptionToVideoGen [WIP]", - "reference": "https://github.com/adithis197/ComfyUI-multimodal-CaptionToVideoGen", - "files": [ - "https://github.com/adithis197/ComfyUI-multimodal-CaptionToVideoGen" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for video generation using a music prompt to generate audio.\nNOTE: The files in the repo are not organized." - }, - { - "author": "adithis197", - "title": "ComfyUI-Caption_to_audio [WIP]", - "reference": "https://github.com/adithis197/ComfyUI-Caption_to_audio", - "files": [ - "https://github.com/adithis197/ComfyUI-Caption_to_audio" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node that converts image description to an appropriate prompt for music generation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "alistairallan", - "title": "ComfyUI-skin-retouch", - "reference": "https://github.com/alistairallan/ComfyUI-skin-retouch", - "files": [ - "https://github.com/alistairallan/ComfyUI-skin-retouch" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI" - }, - { - "author": "sprited-ai", - "title": "Sprited ComfyUI Nodes [WIP]", - "reference": "https://github.com/sprited-ai/sprited-comfyui-nodes", - "files": [ - "https://github.com/sprited-ai/sprited-comfyui-nodes" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI" - }, - { - "author": "trashkollector", - "title": "ComfyUI-TKVideoZoom [WIP]", - "reference": "https://github.com/trashkollector/TKVideoZoom", - "files": [ - "https://github.com/trashkollector/TKVideoZoom" - ], - "install_type": "git-clone", - "description": "Various Zoom/Slide effects for Video\nNOTE: The files in the repo are not organized." - }, - { - "author": "tankenyuen-ola", - "title": "comfyui-wanvideo-scheduler-loop", - "reference": "https://github.com/tankenyuen-ola/comfyui-wanvideo-scheduler-loop", - "files": [ - "https://github.com/tankenyuen-ola/comfyui-wanvideo-scheduler-loop" - ], - "install_type": "git-clone", - "description": "NODES: WanVideo Scheduler Selector, WanVideo Scheduler Loop, WanVideo Scheduler Info" - }, - { - "author": "ElyZeng", - "title": "ComfyUI-Translator [NAME CONFLICT]", - "reference": "https://github.com/ElyZeng/ComfyUI-Translator", - "files": [ - "https://github.com/ElyZeng/ComfyUI-Translator" - ], - "install_type": "git-clone", - "description": "A ComfyUI node to translate text between multiple languages using Argos Translate." - }, - { - "author": "lggcfx2020", - "title": "ComfyUI-LGGCFX-Tools", - "reference": "https://github.com/lggcfx2020/ComfyUI-LGGCFX-Tools", - "files": [ - "https://github.com/lggcfx2020/ComfyUI-LGGCFX-Tools" - ], - "install_type": "git-clone", - "description": "A small tool for calculating the frame number and total frames of a video. Originally required four nodes, but has been merged into a single node for convenience. Includes quick resolution selection and landscape mode switching." - }, - { - "author": "206811", - "title": "ComfyUI_ZhipuAIO", - "reference": "https://github.com/206811/ComfyUI_ZhipuAIO", - "files": [ - "https://github.com/206811/ComfyUI_ZhipuAIO" - ], - "install_type": "git-clone", - "description": "NODES: ZhipuAI AIO Config, ZhipuAI Translator, ZhipuAI GLM-4V Vision\nNOTE: The files in the repo are not organized." - }, - { - "author": "ahmedbana", - "title": "upload-to-azure", - "reference": "https://github.com/ahmedbana/upload-to-azure", - "files": [ - "https://github.com/ahmedbana/upload-to-azure" - ], - "install_type": "git-clone", - "description": "ComfyUI Upload to Azure Node" - }, - { - "author": "Inoriac", - "title": "comfyui-HandDetect", - "reference": "https://github.com/Inoriac/comfyui-HandDetect", - "files": [ - "https://github.com/Inoriac/comfyui-HandDetect" - ], - "install_type": "git-clone", - "description": "NODES: A custom node for ComfyUI that performs hand detection, implemented with the YOLOv8 model, supporting both hand detection and mask generation." - }, - { - "author": "Aero-Ex", - "title": "comfyui_diffswap", - "reference": "https://github.com/Aero-Ex/comfyui_diffswap", - "files": [ - "https://github.com/Aero-Ex/comfyui_diffswap" - ], - "install_type": "git-clone", - "description": "NODES: DiffSwap" - }, - { - "author": "eggsbenedicto", - "title": "DiffusionRenderer-ComfyUI [WIP]", - "reference": "https://github.com/eggsbenedicto/DiffusionRenderer-ComfyUI", - "files": [ - "https://github.com/eggsbenedicto/DiffusionRenderer-ComfyUI" - ], - "install_type": "git-clone", - "description": "Experimental wrapper for diffusion-renderer in ComfyUI.\nNOTE: Currently unfinished and non-functioning. Will update" - }, - { - "author": "system-out-cho", - "title": "displayHistory [WIP]", - "reference": "https://github.com/system-out-cho/displayHistory_ComfyUI", - "files": [ - "https://github.com/system-out-cho/displayHistory_ComfyUI" - ], - "install_type": "git-clone", - "description": "A node that displays any node's history\nNOTE: The files in the repo are not organized." - }, - { - "author": "lazybuttalented", - "title": "ComfyUI_LBT [WIP]", - "reference": "https://github.com/lazybuttalented/ComfyUI_LBT", - "files": [ - "https://github.com/lazybuttalented/ComfyUI_LBT" - ], - "install_type": "git-clone", - "description": "A custom node to program the image & text processing flows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "xgfone", - "title": "ComfyUI_PromptLogoCleaner", - "reference": "https://github.com/xgfone/ComfyUI_PromptLogoCleaner", - "files": [ - "https://github.com/xgfone/ComfyUI_PromptLogoCleaner" - ], - "install_type": "git-clone", - "description": "NODES: Prompt Cleaner (Remove Logo Words)" - }, - { - "author": "dexintenebri", - "title": "comfyui_voxel_nodes [WIP]", - "reference": "https://github.com/dexintenebri/comfyui_voxel_nodes", - "files": [ - "https://github.com/dexintenebri/comfyui_voxel_nodes" - ], - "install_type": "git-clone", - "description": "Extracts RGB and depthmap from image to create 3D Voxels, for integration into MagicaVoxel and Unity Engine\nNOTE: The files in the repo are not organized." - }, - { - "author": "1H-hobit", - "title": "ComfyComfyUI_InternVL3 [WIP]", - "reference": "https://github.com/1H-hobit/ComfyUI_InternVL3", - "files": [ - "https://github.com/1H-hobit/ComfyUI_InternVL3" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes to use InternVL3" - }, - { - "author": "LucianoCirino", - "title": "ComfyUI-invAIder-Nodes", - "reference": "https://github.com/LucianoCirino/ComfyUI-invAIder-Nodes", - "files": [ - "https://github.com/LucianoCirino/ComfyUI-invAIder-Nodes" - ], - "install_type": "git-clone", - "description": "Custom nodes I've created for my own personal workflows. Use at your own discretion.\nNOTE: The files in the repo are not organized." - }, - { - "author": "zhuanvi", - "title": "ComfyUI-ZVNodes [WIP]", - "reference": "https://github.com/zhuanvi/ComfyUI-ZVNodes", - "files": [ - "https://github.com/zhuanvi/ComfyUI-ZVNodes" - ], - "install_type": "git-clone", - "description": "NODES: Load One Image (Directory), Save Image (Directory), Count Image (Directory), Json Reader, Json List Node, Json List Length, Json List Indexer, Json List Slicer, Triangle Character Layout, Json List To Mask, Random Select From List, Join List, ..." - }, - { - "author": "Filexor", - "title": "File_x_dynamic_prompt2", - "reference": "https://github.com/Filexor/File_x_dynamic_prompt2", - "files": [ - "https://github.com/Filexor/File_x_dynamic_prompt2" - ], - "install_type": "git-clone", - "description": "ComfyUI node for random prompt generation" - }, - { - "author": "FileSystem Manager Team", - "title": "Comfyui-FileSytem-Manager", - "reference": "https://github.com/bleash-dev/Comfyui-FileSytem-Manager", - "files": [ - "https://github.com/bleash-dev/Comfyui-FileSytem-Manager" - ], - "install_type": "git-clone", - "description": "Comprehensive file system management for ComfyUI with multiple upload sources" - }, - { - "author": "thavocado", - "title": "comfyui-danbooru-lookup", - "reference": "https://github.com/thavocado/comfyui-danbooru-lookup", - "files": [ - "https://github.com/thavocado/comfyui-danbooru-lookup" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that performs FAISS cosine similarity lookup on Danbooru embeddings using multiple input modes: CLIP conditioning, images with WD14 tagging, or text tags.[w/This nodepack installs its dependencies automatically during execution.]" - }, - { - "author": "love2hina-net", - "title": "ComfyUI-Local-Translator", - "reference": "https://github.com/love2hina-net/ComfyUI-Local-Translator", - "files": [ - "https://github.com/love2hina-net/ComfyUI-Local-Translator" - ], - "install_type": "git-clone", - "description": "This is a text translation node using a local SLM (Microsoft Phi-4) for ComfyUI." - }, - { - "author": "DenRakEiw", - "title": "Denrakeiw Nodes [WIP]", - "reference": "https://github.com/DenRakEiw/DenRakEiw_Nodes", - "files": [ - "https://github.com/DenRakEiw/DenRakEiw_Nodes" - ], - "install_type": "git-clone", - "description": "A custom nodepack for ComfyUI that provides utility nodes for image generation and manipulation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "ahmedbana", - "title": "json-creator [WIP]", - "reference": "https://github.com/ahmedbana/json-creator", - "files": [ - "https://github.com/ahmedbana/json-creator" - ], - "install_type": "git-clone", - "description": "Create Json for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "ahmedbana", - "title": "File-Rename [UNSAFE]", - "reference": "https://github.com/ahmedbana/File-Rename", - "files": [ - "https://github.com/ahmedbana/File-Rename" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI nodepackage that allows you to rename files with incremented numbers based on various mathematical operations. Includes both basic and advanced functionality.[w/This nodepack includes a node that can rename files to arbitrary paths.]" - }, - { - "author": "ahmedbana", - "title": "ComfyUI_AC_FUNV7-FLUX- [WIP]", - "reference": "https://github.com/A719689614/ComfyUI_AC_FUNV7-FLUX-", - "files": [ - "https://github.com/A719689614/ComfyUI_AC_FUNV7-FLUX-" - ], - "install_type": "git-clone", - "description": "NODES: AC_Super_UNET(FLUX), AC_Super_CLIP(FLUX)\nNOTE: The files in the repo are not organized." - }, - { - "author": "broumbroum", - "title": "comfyui-time-system [WIP]", - "reference": "https://github.com/broumbroum/comfyui-time-system", - "files": [ - "https://github.com/broumbroum/comfyui-time-system" - ], - "install_type": "git-clone", - "description": "Package that adds nodes to retrieve the system date and time.\nNOTE: The files in the repo are not organized." - }, - { - "author": "rodpl", - "title": "comfyui-asset-manager", - "reference": "https://github.com/rodpl/comfyui-asset-manager", - "files": [ - "https://github.com/rodpl/comfyui-asset-manager" - ], - "install_type": "git-clone", - "description": "ComfyUI Asset Manager for managing assets in ComfyUI" - }, - { - "author": "blepping", - "title": "ComfyUI 'dum' samplers [WIP]", - "reference": "https://github.com/blepping/comfyui_dum_samplers", - "files": [ - "https://github.com/blepping/comfyui_dum_samplers" - ], - "install_type": "git-clone", - "description": "A collection of random, experimental (and most likely 'dum') samplers for ComfyUI." - }, - { - "author": "crimro-se", - "title": "ComfyUI-CascadedGaze", - "reference": "https://github.com/crimro-se/ComfyUI-CascadedGaze", - "files": [ - "https://github.com/crimro-se/ComfyUI-CascadedGaze" - ], - "install_type": "git-clone", - "description": "Two custom nodes that bring the CascadedGaze image denoising model architecture to ComfyUI." - }, - { - "author": "RamonGuthrie", - "title": "ComfyUI-RBG-LoRA-Converter [UNSAFE]", - "reference": "https://github.com/RamonGuthrie/ComfyUI-RBG-LoraConverter", - "files": [ - "https://github.com/RamonGuthrie/ComfyUI-RBG-LoraConverter" - ], - "install_type": "git-clone", - "description": "A node for converting LoRA (Low-Rank Adaptation) keys in ComfyUI.[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]" - }, - { - "author": "Estanislao-Oviedo", - "title": "ComfyUI-CustomNodes [NAME CONFLICT]", - "reference": "https://github.com/Estanislao-Oviedo/ComfyUI-CustomNodes", - "files": [ - "https://github.com/Estanislao-Oviedo/ComfyUI-CustomNodes" - ], - "install_type": "git-clone", - "description": "NODES: Load Image Folder (Custom), Make Batch from Single Image (Custom)" - }, - { - "author": "ctf05", - "title": "ComfyUI-AudioDuration", - "reference": "https://github.com/ctf05/ComfyUI-AudioDuration", - "files": [ - "https://github.com/ctf05/ComfyUI-AudioDuration" - ], - "install_type": "git-clone", - "description": "NODES: Audio Duration, Audio Overlay (Mix)" - }, - { - "author": "soliton", - "title": "Watermark Detection YOLO Custom Node [WIP]", - "reference": "https://github.com/Soliton80/ComfyUI-Watermark-Detection-YOLO", - "files": [ - "https://github.com/Soliton80/ComfyUI-Watermark-Detection-YOLO" - ], - "install_type": "git-clone", - "description": "Custom watermark detection using rained on 24,558 watermark images YOLO11 model for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "Jpzz", - "title": "IxiWorks StoryBoard Nodes [WIP]", - "reference": "https://github.com/IXIWORKS-KIMJUNGHO/comfyui-ixiworks", - "files": [ - "https://github.com/IXIWORKS-KIMJUNGHO/comfyui-ixiworks" - ], - "install_type": "git-clone", - "description": "StoryBoard nodes for ComfyUI - Parse JSON templates and build prompts for generative movie creation\nNOTE: The files in the repo are not organized." - }, - { - "author": "siyonomicon", - "title": "ComfyUI-Pin", - "reference": "https://github.com/siyonomicon/ComfyUI-Pin", - "files": [ - "https://github.com/siyonomicon/ComfyUI-Pin" - ], - "install_type": "git-clone", - "description": "NODES: Pin Grid Node" - }, - { - "author": "rakete", - "title": "comfyui-rakete", - "reference": "https://github.com/rakete/comfyui-rakete", - "files": [ - "https://github.com/rakete/comfyui-rakete" - ], - "install_type": "git-clone", - "description": "NODES: Get Widget or Default Value, GPU Garbage Collector, Build String from Widget Values" - }, - { - "author": "boricuapab", - "title": "ComfyUI-Bori-KontextPresets [WIP]", - "reference": "https://github.com/boricuapab/ComfyUI-Bori-KontextPresets", - "files": [ - "https://github.com/boricuapab/ComfyUI-Bori-KontextPresets" - ], - "install_type": "git-clone", - "description": "This is a custom node for ComfyUI that uses the Kontext Presets.\nNOTE: The files in the repo are not organized." - }, - { - "author": "sh570655308", - "title": "Comfyui-RayNodes [WIP]", - "reference": "https://github.com/sh570655308/Comfyui-RayNodes", - "files": [ - "https://github.com/sh570655308/Comfyui-RayNodes" - ], - "install_type": "git-clone", - "description": "NODES: Bracketed Tag-Index Merger, Florence2 Tag Processor, Image List Converter, Image Selector, Mask Blackener, Mask Applier and Combiner, Mask Processor, Tag Array to Lines, Tag-Index Merger, Grabber Tag Processor, Image Resizer, Save Image Websocket, Border Mask, SaturationAdjuster, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "Rocky-Lee-001", - "title": "ComfyUI_SZtools", - "reference": "https://github.com/Rocky-Lee-001/ComfyUI_SZtools", - "files": [ - "https://github.com/Rocky-Lee-001/ComfyUI_SZtools" - ], - "install_type": "git-clone", - "description": "This project is the comfyui implementation of ComfyUI_SZtools, a labeling and naming tool developed for Kontext's local training package T2ITrainer.\nNOTE: The files in the repo are not organized." - }, - { - "author": "stalkervr", - "title": "Custom Path Nodes for ComfyUI [UNSAFE]", - "reference": "https://github.com/stalkervr/comfyui-custom-path-nodes", - "files": [ - "https://github.com/stalkervr/comfyui-custom-path-nodes" - ], - "install_type": "git-clone", - "description": "Nodes for path handling and image cropping.[w/This nodepack contains a node that has a vulnerability allowing access to arbitrary file paths.]" - }, - { - "author": "gorillaframeai", - "title": "GF_pixtral_node [WIP]", - "reference": "https://github.com/gorillaframeai/GF_pixtral_node", - "files": [ - "https://github.com/gorillaframeai/GF_pixtral_node" - ], - "install_type": "git-clone", - "description": "NODES: GF Mistral & Pixtral" - }, - { - "author": "enlo", - "title": "ComfyUI-CheckpointSettings", - "reference": "https://github.com/enlo/ComfyUI-CheckpointSettings", - "files": [ - "https://github.com/enlo/ComfyUI-CheckpointSettings" - ], - "install_type": "git-clone", - "description": "A custom node created to fulfill a personal need I thought of while playing around with ComfyUI — 'I want to save checkpoint names and KSampler settings together and randomly switch between them for fun.'" - }, - { - "author": "Mzikart", - "title": "ComfyUI-Mzikart-Player [WIP]", - "reference": "https://github.com/Dream-Pixels-Forge/ComfyUI-Mzikart-Player", - "files": [ - "https://github.com/Dream-Pixels-Forge/ComfyUI-Mzikart-Player" - ], - "install_type": "git-clone", - "description": "Interactive audio player for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "babydjac", - "title": "comfyui-grok-ponyxl [WIP]", - "reference": "https://github.com/babydjac/comfyui-grok-ponyxl", - "files": [ - "https://github.com/babydjac/comfyui-grok-ponyxl" - ], - "install_type": "git-clone", - "description": "NODES: GrokPonyXLPrompter\nNOTE: The files in the repo are not organized." - }, - { - "author": "MarkFreeDom168", - "title": "ComfyUI-image-load-url [WIP]", - "reference": "https://github.com/MarkFreeDom168/ComfyUI-image-load-url", - "files": [ - "https://github.com/MarkFreeDom168/ComfyUI-image-load-url" - ], - "install_type": "git-clone", - "description": "NODES: Load Image From URL/Base64, Load Mask From URL/Base64, Load img and mask from url\nNOTE: The files in the repo are not organized." - }, - { - "author": "realm-weaver", - "title": "Tile Seamstress 360° [WIP]", - "reference": "https://github.com/realm-weaver/ComfyUI-tile-seamstress-360", - "files": [ - "https://github.com/realm-weaver/ComfyUI-tile-seamstress-360" - ], - "install_type": "git-clone", - "description": "Tile Seamstress 360 is a set of tools for fixing seams & poles in 360° panoramic equirectangular images inside ComfyUI." - }, - { - "author": "jisenhua", - "title": "ComfyUI-yolov5-face [WIP]", - "reference": "https://github.com/UmutGuzel/tryvariantai-comfyui", - "files": [ - "https://github.com/UmutGuzel/tryvariantai-comfyui" - ], - "install_type": "git-clone", - "description": "NODES: Fill Transparency, Mask Expand Border, Mask Expand Border (Advanced), Mask to Transparent, Debug Mask Visualizer, White to Transparent, White Detector\nNOTE: The files in the repo are not organized." - }, - { - "author": "visualbruno", - "title": "ComfyUI-QRemeshify", - "reference": "https://github.com/visualbruno/ComfyUI-QRemeshify", - "files": [ - "https://github.com/visualbruno/ComfyUI-QRemeshify" - ], - "install_type": "git-clone", - "description": "NODES: QRemeshify" - }, - { - "author": "jisenhua", - "title": "ComfyUI-yolov5-face [WIP]", - "reference": "https://github.com/JiSenHua/ComfyUI-yolov5-face", - "files": [ - "https://github.com/JiSenHua/ComfyUI-yolov5-face" - ], - "install_type": "git-clone", - "description": "A YOLOv5 face detection project for ComfyUI.\nNOTE: The files in the repo are not organized." - }, - { - "author": "zopieux", - "title": "ComfyUI-zopi [UNSAFE]", - "reference": "https://github.com/zopieux/ComfyUI-zopi", - "files": [ - "https://github.com/zopieux/ComfyUI-zopi" - ], - "install_type": "git-clone", - "description": "NODES: Eval Python, Load TensortRT + checkpoint + CLIP + VAE [w/This nodepack contains a vulnerability that allows remote code execution.]" - }, - { - "author": "przewodo", - "title": "ComfyUI-Przewodo-Utils [WIP]", - "reference": "https://github.com/przewodo/ComfyUI-Przewodo-Utils", - "files": [ - "https://github.com/przewodo/ComfyUI-Przewodo-Utils" - ], - "install_type": "git-clone", - "description": "Utilities to make it easy to develop advanced Workflows without having to use a lot of nodes for simple stuff.\nNOTE: The files in the repo are not organized." - }, - { - "author": "hulipanpan", - "title": "Comfyui_tuteng [WIP]", - "reference": "https://github.com/hulipanpan/Comfyui_tuteng", - "files": [ - "https://github.com/hulipanpan/Comfyui_tuteng" - ], - "install_type": "git-clone", - "description": "NODES: Tuteng Mj, Tuteng Mj Style, Tuteng Upload, Tuteng Mj Upscale, Tuteng Mj Vary/Zoom, Tuteng Kling Text2Video, Tuteng Kling Image2Video, Tuteng Kling Video Extend, Tuteng Gemini API, Tuteng Doubao SeedEdit, Tuteng ChatGPT API, Tuteng Jimeng API, Tuteng GPT-Image-1 Edit, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "PaleBloodq", - "title": "ComfyUI-HFTransformers", - "reference": "https://github.com/PaleBloodq/ComfyUI-HFTransformers", - "files": [ - "https://github.com/PaleBloodq/ComfyUI-HFTransformers" - ], - "install_type": "git-clone", - "description": "NODES: HFT Pipeline Loader, HFT Classifier, HFT Classification Selector, HFT Object Detector, HFT Image to Text, HFT Depth Estimator" - }, - { - "author": "whmc76", - "title": "ComfyUI-AudioSuiteAdvanced [WIP]", - "reference": "https://github.com/whmc76/ComfyUI-AudioSuiteAdvanced", - "files": [ - "https://github.com/whmc76/ComfyUI-AudioSuiteAdvanced" - ], - "install_type": "git-clone", - "description": "A ComfyUI plugin for processing long text files and generating speech, supporting features such as audio separation, text segmentation, and audio merging.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Letz-AI", - "title": "ComfyUI-LetzAI [UNSAFE]", - "reference": "https://github.com/Letz-AI/ComfyUI-LetzAI", - "files": [ - "https://github.com/Letz-AI/ComfyUI-LetzAI" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI Node for LetzAI Image Generation[w/The API key is embedded in the workflow.]" - }, - { - "author": "ZhouNLP", - "title": "comfyui_LK_selfuse", - "reference": "https://github.com/LK-168/comfyui_LK_selfuse", - "files": [ - "https://github.com/LK-168/comfyui_LK_selfuse" - ], - "install_type": "git-clone", - "description": "NODES: Mask Diff, Mask Connected Remove, Mask Get Max, Mask Filter with Rate, InspectModelArchitecture, Print Sigma, Adv Scheduler, LK_MaskToSEGS, LK_SegsAdjust, String Filter, String Remove Duplicate, String Modify, ... \nNOTE: The files in the repo are not organized." - }, - { - "author": "junhe421", - "title": "comfyui_batch_image_processor [WIP]", - "reference": "https://github.com/junhe421/comfyui_batch_image_processor", - "files": [ - "https://github.com/junhe421/comfyui_batch_image_processor" - ], - "install_type": "git-clone", - "description": "A Kontext Bench-style ComfyUI image difference analysis node that supports instruction-based prompt generation and batch TXT editing.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Tr1dae", - "title": "ComfyUI-CustomNodes-MVM", - "reference": "https://github.com/Tr1dae/ComfyUI-CustomNodes-MVM", - "files": [ - "https://github.com/Tr1dae/ComfyUI-CustomNodes-MVM" - ], - "install_type": "git-clone", - "description": "NODES: Load Image From Folder MVM, Load Guidance Images From Folder MVM, Load Text From Folder MVM" - }, - { - "author": "Vkabuto23", - "title": "ComfyUI Custom Nodes: OpenRouter & Ollama [UNSAFE]", - "reference": "https://github.com/Vkabuto23/comfyui_openrouter_ollama", - "files": [ - "https://github.com/Vkabuto23/comfyui_openrouter_ollama" - ], - "install_type": "git-clone", - "description": "ComfyUI Custom Nodes: OpenRouter & Ollama[w/The API key is embedded in the workflow.]" - }, - { - "author": "subnet99", - "title": "ComfyUI-URLLoader", - "reference": "https://github.com/subnet99/ComfyUI-URLLoader", - "files": [ - "https://github.com/subnet99/ComfyUI-URLLoader" - ], - "install_type": "git-clone", - "description": "ComfyUI plugin for downloading and loading media files from URLs." - }, - { - "author": "bikiam", - "title": "Comfyui_AudioRecoder", - "reference": "https://github.com/bikiam/Comfyui_AudioRecoder", - "files": [ - "https://github.com/bikiam/Comfyui_AudioRecoder" - ], - "install_type": "git-clone", - "description": "NODES: AUDIO Recorder" - }, - { - "author": "saulchiu", - "title": "comfyui-saul-plugin [WIP]", - "reference": "https://github.com/saulchiu/comfy_saul_plugin", - "files": [ - "https://github.com/saulchiu/comfy_saul_plugin" - ], - "install_type": "git-clone", - "description": "NODES: Cutting Video\nNOTE: The files in the repo are not organized." - }, - { - "author": "wasilone11", - "title": "comfyui-sync-translate-node", - "reference": "https://github.com/wasilone11/comfyui-sync-translate-node", - "files": [ - "https://github.com/wasilone11/comfyui-sync-translate-node" - ], - "install_type": "git-clone", - "description": "NODES: Sync.so Translator" - }, - { - "author": "ashllay", - "title": "ComfyUI_MoreComfy", - "reference": "https://github.com/ashllay/ComfyUI_MoreComfy", - "files": [ - "https://github.com/ashllay/ComfyUI_MoreComfy" - ], - "install_type": "git-clone", - "description": "NODES: MC Switch Seed, MC Switch Image, MC Switch String, MC Alter Seed, MC Set Tile Size, MC Get Image Size, MC Get Image Min Max, MC Multi Concat, MC Multi Concat(Advanced), MC Noise" - }, - { - "author": "gaowei-space", - "title": "ComfyUI Doubao LLM [WIP]", - "reference": "https://github.com/gaowei-space/ComfyUI-Doubao-LLM", - "files": [ - "https://github.com/gaowei-space/ComfyUI-Doubao-LLM" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for Doubao (ByteDance) LLM and Vision Language Model integration\nNOTE: The files in the repo are not organized." - }, - { - "author": "BrettMedia", - "title": "comfyui-bhtools [WIP]", - "reference": "https://github.com/BrettMedia/comfyui-bhtools", - "files": [ - "https://github.com/BrettMedia/comfyui-bhtools" - ], - "install_type": "git-clone", - "description": "A suite of creative tools designed to help AI artists with continuity, brainstorming, and workflow optimization. Born from real-world needs during my AI journey, these nodes solve common pain points in creative workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "XiaoHeiziGGG", - "title": "ComfyUI-Gemini-Kontext [WIP]", - "reference": "https://github.com/XiaoHeiziGGG/ComfyUI-Gemini-Kontext", - "files": [ - "https://github.com/XiaoHeiziGGG/ComfyUI-Gemini-Kontext" - ], - "install_type": "git-clone", - "description": "Google Gemini API powered translation nodes for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "Bwebbfx", - "title": "ComfyUI Face Parsing Nodes [WIP]", - "reference": "https://github.com/Bwebbfx/ComfyUI_FaceParsing", - "files": [ - "https://github.com/Bwebbfx/ComfyUI_FaceParsing" - ], - "install_type": "git-clone", - "description": "This package provides ComfyUI nodes for face parsing using BiSeNet (from yakhyo/face-parsing), supporting batch and video workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "orion4d", - "title": "Unified List Selector for ComfyUI [UNSAFE]", - "reference": "https://github.com/orion4d/ComfyUI_unified_list_selector", - "files": [ - "https://github.com/orion4d/ComfyUI_unified_list_selector" - ], - "install_type": "git-clone", - "description": "This project is a custom node for ComfyUI that allows you to dynamically load lists from text (.txt) or CSV (.csv) files and select an item to use in your workflow. It features a manual selection mode (via a dropdown list) and a random selection mode, as well as the ability to add prefixes and suffixes to the selected text.[w/This nodepack contains a node with a vulnerability that allows reading files from arbitrary paths.]" - }, - { - "author": "kongds1999", - "title": "ComfyUI_was_image", - "reference": "https://github.com/kongds1999/ComfyUI_was_image", - "files": [ - "https://github.com/kongds1999/ComfyUI_was_image" - ], - "install_type": "git-clone", - "description": "NODES: Replace Color By Palette, ConvertGrayToImage" - }, - { - "author": "zl9739379", - "title": "ComfyUI Qwen Vision Language API Node [NAME CONFLICT]", - "reference": "https://github.com/zl9739379/comfyui-qwen-vl-api", - "files": [ - "https://github.com/zl9739379/comfyui-qwen-vl-api" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node for describing images using Qwen Vision Language models through OpenAI-compatible APIs." - }, - { - "author": "bikiam", - "title": "ComfyUi_WhisperGTranslate", - "reference": "https://github.com/bikiam/ComfyUi_WhisperGTranslate", - "files": [ - "https://github.com/bikiam/ComfyUi_WhisperGTranslate" - ], - "install_type": "git-clone", - "description": "NODES: Whisper + AudioTranslate, Google Translate Node" - }, - { - "author": "edgerunner", - "title": "ComfyUI Queue Manager [WIP]", - "reference": "https://github.com/QuietNoise/ComfyUI-Queue-Manager", - "files": [ - "https://github.com/QuietNoise/ComfyUI-Queue-Manager" - ], - "install_type": "git-clone", - "description": "An extension supporting more streamlined prompt queue management." - }, - { - "author": "fylrid2", - "title": "lockValue", - "reference": "https://github.com/fylrid2/comfyui_lock_previous_value", - "files": [ - "https://github.com/fylrid2/comfyui_lock_previous_value" - ], - "install_type": "git-clone", - "description": "Allows the locking of a nodes value\nNOTE: The files in the repo are not organized." - }, - { - "author": "XiaoHeiziGGG", - "title": "ComfyUI Gemini Translator [WIP]", - "reference": "https://github.com/XiaoHeiziGGG/ComfyUI-GeminiTranslator", - "files": [ - "https://github.com/XiaoHeiziGGG/ComfyUI-GeminiTranslator" - ], - "install_type": "git-clone", - "description": "The API node library of gemini can be translated and recognized.The API node library of gemini can be translated and recognized.\nNOTE: The files in the repo are not organized." - }, - { - "author": "DiffusionWave-YT", - "title": "DiffusionWave_PickResolution [WIP]", - "reference": "https://github.com/DiffusionWave-YT/DiffusionWave_PickResolution", - "files": [ - "https://github.com/DiffusionWave-YT/DiffusionWave_PickResolution" - ], - "install_type": "git-clone", - "description": "Change of resolutions for ComfyUI and Upscalers\nNOTE: The files in the repo are not organized." - }, - { - "author": "PeterMikhai", - "title": "DoomFLUX Nodes [WIP]", - "reference": "https://github.com/PeterMikhai/Doom_Flux_NodePack", - "files": [ - "https://github.com/PeterMikhai/Doom_Flux_NodePack" - ], - "install_type": "git-clone", - "description": "Custom nodes for FLUX models, including a loader and specialized samplers for standard and inpaint generation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "maque", - "title": "comfyui_video_BC [WIP]", - "reference": "https://github.com/JioJe/comfyui_video_BC", - "files": [ - "https://github.com/JioJe/comfyui_video_BC" - ], - "install_type": "git-clone", - "description": "Batch load video nodes and save videos in custom paths\nNOTE: The files in the repo are not organized." - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "ComfyUI-Gemini [NAME CONFLICT]", - "id": "gemini", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Gemini", - "files": [ - "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Gemini" - ], - "install_type": "git-clone", - "description": "Using Gemini-pro & Gemini-pro-vision in ComfyUI." - }, - { - "author": "No-22-Github", - "title": "ComfyUI_SaveImageCustom", - "reference": "https://github.com/No-22-Github/ComfyUI_SaveImageCustom", - "files": [ - "https://github.com/No-22-Github/ComfyUI_SaveImageCustom" - ], - "install_type": "git-clone", - "description": "Easy save image with dir+name" - }, - { - "author": "jiafuzeng", - "title": "comfyui-fishSpeech", - "reference": "https://github.com/jiafuzeng/comfyui-fishSpeech", - "files": [ - "https://github.com/jiafuzeng/comfyui-fishSpeech" - ], - "install_type": "git-clone", - "description": "NODES: Fish-Speech Loader, Fish-Speech TTS, Fish-Speech Audio Preview" - }, - { - "author": "bleash-dev", - "title": "ComfyUI-Auth-Manager", - "reference": "https://github.com/bleash-dev/ComfyUI-Auth-Manager", - "files": [ - "https://github.com/bleash-dev/ComfyUI-Auth-Manager" - ], - "install_type": "git-clone", - "description": "A custom node that provides email/password authentication for ComfyUI pods with a beautiful modal interface." - }, - { - "author": "filliptm", - "title": "ComfyUI_Fill-Node-Loader [WIP]", - "reference": "https://github.com/filliptm/ComfyUI_Fill-Node-Loader", - "files": [ - "https://github.com/filliptm/ComfyUI_Fill-Node-Loader" - ], - "install_type": "git-clone", - "description": "A ComfyUI plugin to simplify loading and managing custom nodes with a sidebar interface." - }, - { - "author": "diogod", - "title": "Comfy Inpainting Works [WIP]", - "reference": "https://github.com/diodiogod/Comfy-Inpainting-Works", - "files": [ - "https://github.com/diodiogod/Comfy-Inpainting-Works" - ], - "install_type": "git-clone", - "description": "Go to the top menu>Workflow>Browse Templates. This is a collection of my Inpainting workflows for Flux (expanded and COMPACT) + others. Previously called: 'Proper Flux Control-Net inpainting and/or outpainting with batch size - Alimama or Flux Fill'. By installing this 'node' you can always keep them up to date by updating on the manager. This is not a new custom node. You will still need to install all other custom nodes used on the workflows. You will also find my 'Flux LoRA Block Weights Preset Tester' here as well.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Malloc-pix", - "title": "comfyui-QwenVL", - "reference": "https://github.com/Malloc-pix/comfyui-QwenVL", - "files": [ - "https://github.com/Malloc-pix/comfyui-QwenVL" - ], - "install_type": "git-clone", - "description": "NODES: Qwen2.5VL, Qwen2.5" - }, - { - "author": "artifyfun", - "title": "ComfyUI-JS [UNSAFE]", - "reference": "https://github.com/artifyfun/ComfyUI-JS", - "files": [ - "https://github.com/artifyfun/ComfyUI-JS" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node capable of executing JavaScript code: it takes JavaScript code as input and outputs the execution result.[w/This extension has an XSS vulnerability that can be triggered through workflow execution.]" - }, - { - "author": "OgreLemonSoup", - "title": "ComfyUI-Notes-manager", - "reference": "https://github.com/OgreLemonSoup/ComfyUI-Notes-manager", - "files": [ - "https://github.com/OgreLemonSoup/ComfyUI-Notes-manager" - ], - "install_type": "git-clone", - "description": "This extension provides the note feature." - }, - { - "author": "WozStudios", - "title": "ComfyUI-WozNodes", - "reference": "https://github.com/WozStudios/ComfyUI-WozNodes", - "files": [ - "https://github.com/WozStudios/ComfyUI-WozNodes" - ], - "install_type": "git-clone", - "description": "NODES: Trim Image Batch, Create Image Batch, Select Image Batch by Mask, Advanced Batch Creator" - }, - { - "author": "stalkervr", - "title": "comfyui-custom-path-nodes [UNSAFE]", - "reference": "https://github.com/stalkervr/comfyui-custom-path-nodes", - "files": [ - "https://github.com/stalkervr/comfyui-custom-path-nodes" - ], - "install_type": "git-clone", - "description": "Nodes for path handling and image cropping.[w/This nodepack has a vulnerability that allows remote access to arbitrary file paths.]" - }, - { - "author": "vovler", - "title": "comfyui-vovlertools", - "reference": "https://github.com/vovler/ComfyUI-vovlerTools", - "files": [ - "https://github.com/vovler/ComfyUI-vovlerTools" - ], - "install_type": "git-clone", - "description": "Advanced ComfyUI nodes for WD14 tagging, image filtering, and CLIP to TensorRT conversion" - }, - { - "author": "ELiZswe", - "title": "ComfyUI-ELiZTools", - "reference": "https://github.com/ELiZswe/ComfyUI-ELiZTools", - "files": [ - "https://github.com/ELiZswe/ComfyUI-ELiZTools" - ], - "install_type": "git-clone", - "description": "ELIZ Tools" - }, - { - "author": "yamanacn", - "title": "comfyui_qwenbbox", - "reference": "https://github.com/yamanacn/comfyui_qwenbbox", - "files": [ - "https://github.com/yamanacn/comfyui_qwenbbox" - ], - "install_type": "git-clone", - "description": "NODES: Load Qwen Model (v2), Qwen Bbox Detection, Prepare BBox for SAM (v2)" - }, - { - "author": "mikheys", - "title": "ComfyUI-mikheys", - "reference": "https://github.com/mikheys/ComfyUI-mikheys", - "files": [ - "https://github.com/mikheys/ComfyUI-mikheys" - ], - "install_type": "git-clone", - "description": "NODES: WAN Optimal Resolution Selector, WAN Show Image Dimensions" - }, - { - "author": "yamanacn", - "title": "comfyui_qwen_object [WIP]", - "reference": "https://github.com/yamanacn/comfyui_qwen_object", - "files": [ - "https://github.com/yamanacn/comfyui_qwen_object" - ], - "install_type": "git-clone", - "description": "This is a custom node for ComfyUI that integrates the Qwen vision model for tasks such as object detection.\nNOTE: The files in the repo are not organized." - }, - { - "author": "neverbiasu", - "title": "ComfyUI-Show-o [WIP]", - "reference": "https://github.com/neverbiasu/ComfyUI-Show-o", - "files": [ - "https://github.com/neverbiasu/ComfyUI-Show-o" - ], - "install_type": "git-clone", - "description": "NODES: Show-o Model Loader, Show-o Text to Image, Show-o Image Captioning, Show-o Image Inpainting" - }, - { - "author": "zyquon", - "title": "ComfyUI Stash", - "reference": "https://github.com/zyquon/ComfyUI-Stash", - "files": [ - "https://github.com/zyquon/ComfyUI-Stash" - ], - "install_type": "git-clone", - "description": "Nodes to use Stash within Comfy workflows" - }, - { - "author": "tankenyuen-ola", - "title": "comfyui-env-variable-reader [UNSAFE]", - "reference": "https://github.com/tankenyuen-ola/comfyui-env-variable-reader", - "files": [ - "https://github.com/tankenyuen-ola/comfyui-env-variable-reader" - ], - "install_type": "git-clone", - "description": "NODES: Environment Variable Reader [w/Installing this node may expose environment variables that contain sensitive information such as API keys.]" - }, - { - "author": "ftf001-tech", - "title": "ComfyUI-Lucian [WIP]", - "reference": "https://github.com/ftf001-tech/ComfyUI-ExternalLLMDetector", - "files": [ - "https://github.com/ftf001-tech/ComfyUI-ExternalLLMDetector" - ], - "install_type": "git-clone", - "description": "These nodes allow you to configure LLM API connections, send images with custom prompts, and convert the LLM's JSON bounding box responses into a format compatible with segmentation nodes like SAM2\nNOTE: The files in the repo are not organized." - }, - { - "author": "LucianGnn", - "title": "ComfyUI-Lucian [WIP]", - "reference": "https://github.com/LucianGnn/ComfyUI-Lucian", - "files": [ - "https://github.com/LucianGnn/ComfyUI-Lucian" - ], - "install_type": "git-clone", - "description": "NODES: Audio Duration Calculator\nNOTE: The files in the repo are not organized." - }, - { - "author": "akatz-ai", - "title": "ComfyUI-Execution-Inversion", - "reference": "https://github.com/akatz-ai/ComfyUI-Execution-Inversion", - "files": [ - "https://github.com/akatz-ai/ComfyUI-Execution-Inversion" - ], - "install_type": "git-clone", - "description": "Contains nodes related to the new execution inversion engine in ComfyUI. nodepack originally from [a/https://github.com/BadCafeCode/execution-inversion-demo-comfyui](https://github.com/BadCafeCode/execution-inversion-demo-comfyui)" - }, - { - "author": "mamorett", - "title": "comfyui_minicpm_vision", - "reference": "https://github.com/mamorett/comfyui_minicpm_vision", - "files": [ - "https://github.com/mamorett/comfyui_minicpm_vision" - ], - "install_type": "git-clone", - "description": "NODES: MiniCPM Vision GGUF" - }, - { - "author": "BigStationW", - "title": "flowmatch_scheduler-comfyui", - "reference": "https://github.com/BigStationW/flowmatch_scheduler-comfyui", - "files": [ - "https://github.com/BigStationW/flowmatch_scheduler-comfyui" - ], - "install_type": "git-clone", - "description": "NODES: FlowMatchSigmas" - }, - { - "author": "franky519", - "title": "ComfyUI Face Four Image Matcher [WIP]", - "reference": "https://github.com/franky519/comfyui_fnckc_Face_analysis", - "files": [ - "https://github.com/franky519/comfyui_fnckc_Face_analysis" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for four face image matching and face swap control\nNOTE: Invalid pyproject.toml" - }, - { - "author": "bleash-dev", - "title": "Comfyui-Iddle-Checker", - "reference": "https://github.com/bleash-dev/Comfyui-Idle-Checker", - "files": [ - "https://github.com/bleash-dev/Comfyui-Idle-Checker" - ], - "install_type": "git-clone", - "description": "front extension for idle checker" - }, - { - "author": "fangg2000", - "title": "ComfyUI-StableAudioFG [WIP]", - "reference": "https://github.com/fangg2000/ComfyUI-StableAudioFG", - "files": [ - "https://github.com/fangg2000/ComfyUI-StableAudioFG" - ], - "install_type": "git-clone", - "description": "The ComfyUI plugin for stable-audio (supports offline use)\nNOTE: The files in the repo are not organized." - }, - { - "author": "hdfhssg", - "title": "comfyui_EvoSearch [WIP]", - "reference": "https://github.com/hdfhssg/comfyui_EvoSearch", - "files": [ - "https://github.com/hdfhssg/comfyui_EvoSearch" - ], - "install_type": "git-clone", - "description": "NODES: EvoSearch_FLUX, EvoSearch_SD21, EvoSearch_WAN, EvolutionScheduleGenerator, GuidanceRewardsGenerator" - }, - { - "author": "simonjaq", - "title": "ComfyUI-sjnodes", - "reference": "https://github.com/simonjaq/ComfyUI-sjnodes", - "files": [ - "https://github.com/simonjaq/ComfyUI-sjnodes" - ], - "install_type": "git-clone", - "description": "Some modified ComfyUI custom nodes" - }, - { - "author": "xzuyn", - "title": "xzuynodes-ComfyUI", - "reference": "https://github.com/xzuyn/ComfyUI-xzuynodes", - "files": [ - "https://github.com/xzuyn/ComfyUI-xzuynodes" - ], - "install_type": "git-clone", - "description": "NODES: First/Last Frame (XZ), Resize Image (Original KJ), Resize Image (XZ), CLIP Text Encode (XZ), Load CLIP (XZ), TripleCLIPLoader (XZ), WanImageToVideo (XZ)" - }, - { - "author": "gilons", - "title": "ComfyUI-GoogleDrive-Downloader [UNSAFE]", - "reference": "https://github.com/gilons/ComfyUI-GoogleDrive-Downloader", - "files": [ - "https://github.com/gilons/ComfyUI-GoogleDrive-Downloader" - ], - "install_type": "git-clone", - "description": "ComfyUI custom node for downloading files from Google Drive.[w/There is a vulnerability that allows saving a remote file to an arbitrary local path.]" - }, - { - "author": "moonwhaler", - "title": "ComfyUI-FileBrowserAPI [UNSAFE]", - "reference": "https://github.com/GalactusX31/ComfyUI-FileBrowserAPI", - "files": [ - "https://github.com/GalactusX31/ComfyUI-FileBrowserAPI" - ], - "install_type": "git-clone", - "description": "A general-purpose, dependency-free File and Folder Browser API for ComfyUI custom nodes.[w/path traversal vulnerability]" - }, - { - "author": "DreamsInAutumn", - "title": "ComfyUI-Autumn-LLM-Nodes", - "reference": "https://github.com/DreamsInAutumn/ComfyUI-Autumn-LLM-Nodes", - "files": [ - "https://github.com/DreamsInAutumn/ComfyUI-Autumn-LLM-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Gemini-Image-To-Prompt, Gemini-Prompt-Builder, LLM-Prompt-Builder" - }, - { - "author": "alexgenovese", - "title": "ComfyUI-Reica", - "reference": "https://github.com/alexgenovese/ComfyUI-Reica", - "files": [ - "https://github.com/alexgenovese/ComfyUI-Reica" - ], - "install_type": "git-clone", - "description": "NODES: 'Reica GCP: Read Image', 'Reica GCP: Write Image & Get URL', 'Reica Text Image Display', 'Reica Read Image URL', 'Reica URL Image Loader Filename', 'Reica API: Send HTTP Notification', 'Insert Anything'" - }, - { - "author": "yichengup", - "title": "ComfyUI-Transition", - "reference": "https://github.com/yichengup/ComfyUI-Transition", - "files": [ - "https://github.com/yichengup/ComfyUI-Transition" - ], - "install_type": "git-clone", - "description": "NODES: Linear Transition, Gradient Transition, Dual Line Transition, Sequence Transition, Circular Transition, Circular Sequence Transition" - }, - { - "author": "wildminder", - "title": "ComfyUI-MagCache [NAME CONFLICT|WIP]", - "reference": "https://github.com/wildminder/ComfyUI-MagCache", - "files": [ - "https://github.com/wildminder/ComfyUI-MagCache" - ], - "install_type": "git-clone", - "description": "official implementation of [zehong-ma/MagCache](https://github.com/zehong-ma/MagCache) for ComfyUI" - }, - { - "author": "laubsauger", - "title": "ComfyUI Storyboard [WIP]", - "reference": "https://github.com/laubsauger/comfyui-storyboard", - "files": [ - "https://github.com/laubsauger/comfyui-storyboard" - ], - "install_type": "git-clone", - "description": "This custom node for ComfyUI provides a markdown renderer to display formatted text and notes within your workflow." - }, - { - "author": "grokuku", - "title": "ComfyUI-Holaf-Terminal [UNSAFE]", - "reference": "https://github.com/grokuku/ComfyUI-Holaf-Utilities", - "files": [ - "https://github.com/grokuku/ComfyUI-Holaf-Utilities" - ], - "install_type": "git-clone", - "description": "Interactive Terminal in a node for ComfyUI[w/This custom extension provides a remote web-based shell (terminal) interface to the machine running the ComfyUI server. By installing and using this extension, you are opening a direct, powerful, and potentially dangerous access point to your system.]" - }, - { - "author": "usrname0", - "title": "ComfyUI-AllergicPack [WIP]", - "reference": "https://github.com/usrname0/ComfyUI-AllergicPack", - "files": [ - "https://github.com/usrname0/ComfyUI-AllergicPack" - ], - "install_type": "git-clone", - "description": "This package is not ready for primetime but I'm making it public anyway. If I'm using the node then I'm putting it here. Might make it more official later. Use at your own risk." - }, - { - "author": "cesilk10", - "title": "cesilk-comfyui-nodes", - "reference": "https://github.com/cesilk10/cesilk-comfyui-nodes", - "files": [ - "https://github.com/cesilk10/cesilk-comfyui-nodes" - ], - "install_type": "git-clone", - "description": "NODES: Save and Upload to S3, SDXL Image Sizes" - }, - { - "author": "COcisuts", - "title": "CObot-ComfyUI-WhisperToTranscription [WIP]", - "reference": "https://github.com/COcisuts/CObot-ComfyUI-WhisperToTranscription", - "files": [ - "https://github.com/COcisuts/CObot-ComfyUI-WhisperToTranscription" - ], - "install_type": "git-clone", - "description": "CObot-ComfyUI-WhisperToTranscription\nNOTE: missing requirements.txt" - }, - { - "author": "jinchanz", - "title": "ComfyUI-AliCloud-Bailian [WIP]", - "reference": "https://github.com/jinchanz/ComfyUI-AliCloud-Bailian", - "files": [ - "https://github.com/jinchanz/ComfyUI-AliCloud-Bailian" - ], - "install_type": "git-clone", - "description": "This is a collection of custom nodes for invoking Alibaba Cloud's DashScope API within ComfyUI.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Yukinoshita-Yukinoe", - "title": "ComfyUI-KontextOfficialNode", - "reference": "https://github.com/Yukinoshita-Yukinoe/ComfyUI-KontextOfficialNode", - "files": [ - "https://github.com/Yukinoshita-Yukinoe/ComfyUI-KontextOfficialNode" - ], - "install_type": "git-clone", - "description": "NODES: Kontext Text-to-Image (Official Max), Kontext Image Editing (Official Max)" - }, - { - "author": "takoyaki1118", - "title": "ComfyUI_PromptExtractor", - "reference": "https://github.com/takoyaki1118/ComfyUI_PromptExtractor", - "files": [ - "https://github.com/takoyaki1118/ComfyUI_PromptExtractor" - ], - "install_type": "git-clone", - "description": "NODES: Custom Load Image With Path, Prompt Extractor Node" - }, - { - "author": "littleowl", - "title": "ComfyUI-MV-HECV", - "reference": "https://github.com/littleowl/ComfyUI-MV-HECV", - "files": [ - "https://github.com/littleowl/ComfyUI-MV-HECV" - ], - "install_type": "git-clone", - "description": "ComfyUI export of 3D Videos and Images Compatible with VR / XR, including the AVP." - }, - { - "author": "BinglongLi", - "title": "ComfyUI_ToolsForAutomask", - "reference": "https://github.com/BinglongLi/ComfyUI_ToolsForAutomask", - "files": [ - "https://github.com/BinglongLi/ComfyUI_ToolsForAutomask" - ], - "install_type": "git-clone", - "description": "NODES: Directional Mask Expansion, Remove Small Regions Mask, Precise Subtract Mask, Precise Add Mask, Closing Mask, Opening Mask, Conditional Mask Selector, Prune Thin Branches Mask, Mask Fill Gaps Convex Hull" - }, - { - "author": "strhwste", - "title": "CSV Utils [WIP]", - "reference": "https://github.com/strhwste/comfyui_csv_utils", - "files": [ - "https://github.com/strhwste/comfyui_csv_utils" - ], - "install_type": "git-clone", - "description": "Custom CSV handling nodes for ComfyUI\nNOTE: invalid pyproject.toml" - }, - { - "author": "retech995", - "title": "ComfyUI_SaveImageBulk [UNSAFE]", - "reference": "https://github.com/retech995/Save_Florence2_Bulk_Prompts", - "files": [ - "https://github.com/retech995/Save_Florence2_Bulk_Prompts" - ], - "install_type": "git-clone", - "description": "This comfyui node helps save image[w/This nodepack contains a node that can write files to an arbitrary path.]" - }, - { - "author": "etng", - "title": "ComfyUI-Heartbeat [UNSAFE]", - "reference": "https://github.com/etng/ComfyUI-Heartbeat", - "files": [ - "https://github.com/etng/ComfyUI-Heartbeat" - ], - "install_type": "git-clone", - "description": "A plugin for ComfyUI that sends periodic heartbeat requests to a configured gateway, including system information and node status." - }, - { - "author": "Novavision0313", - "title": "ComfyUI-NVVS [WIP]", - "reference": "https://github.com/Novavision0313/ComfyUI-NVVS", - "files": [ - "https://github.com/Novavision0313/ComfyUI-NVVS" - ], - "install_type": "git-clone", - "description": "A ComfyUI plugin customized by NOVEVISION\nNOTE: The files in the repo are not organized." - }, - { - "author": "zackabrams", - "title": "ComfyUI-KeySyncWrapper [WIP]", - "reference": "https://github.com/zackabrams/ComfyUI-KeySyncWrapper", - "files": [ - "https://github.com/zackabrams/ComfyUI-KeySyncWrapper" - ], - "install_type": "git-clone", - "description": "implementation of KeySync in ComfyUI" - }, - { - "author": "godric8", - "title": "ComfyUI_Step1X-Edit [NAME CONFLICT]", - "reference": "https://github.com/godric8/ComfyUI_Step1X-Edit", - "files": [ - "https://github.com/godric8/ComfyUI_Step1X-Edit" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes for Step1X-Edit" - }, - { - "author": "violet0927", - "title": "ComfyUI-Direct3DS2 [WIP]", - "reference": "https://github.com/y4my4my4m/ComfyUI_Direct3DS2", - "files": [ - "https://github.com/y4my4my4m/ComfyUI_Direct3DS2" - ], - "install_type": "git-clone", - "description": "Direct3D-S2 plugin for ComfyUI. [w/Doesn't work yet]" - }, - { - "author": "gamtruliar", - "title": "ComfyUI-N_SwapInput [UNSAFE]", - "reference": "https://github.com/gamtruliar/ComfyUI-N_SwapInput", - "files": [ - "https://github.com/gamtruliar/ComfyUI-N_SwapInput" - ], - "install_type": "git-clone", - "description": "This is a simple tool for swapping input folders with custom suffix in comfy-UI[w/]This nodepack performs deletion operations on local files and contains a vulnerability that allows arbitrary paths to be deleted." - }, - { - "author": "bulldog68", - "title": "ComfyUI_FMJ [WIP]", - "reference": "https://github.com/bulldog68/ComfyUI_FMJ", - "files": [ - "https://github.com/bulldog68/ComfyUI_FMJ" - ], - "install_type": "git-clone", - "description": "Generate random prompts easily for FMJ.\nNOTE: The files in the repo are not organized." - }, - { - "author": "pictorialink", - "title": "comfyui-static-resource[UNSAFE]", - "reference": "https://github.com/pictorialink/ComfyUI-static-resource", - "files": [ - "https://github.com/pictorialink/ComfyUI-static-resource" - ], - "install_type": "git-clone", - "description": "Use model bending to push your model beyond its visuals' limits. These nodes allow you to apply transformations to the intemediate densoising steps during sampling, e.g. add, multiplty, scale, rotate, dilate, erode ..etc.[w/This nodepack includes a feature that allows downloading remote files to arbitrary local paths. This is a vulnerability that can lead to Remote Code Execution.]" - }, - { - "author": "brace-great", - "title": "comfyui-mc [WIP]", - "reference": "https://github.com/brace-great/comfyui-mc", - "files": [ - "https://github.com/brace-great/comfyui-mc" - ], - "install_type": "git-clone", - "description": "NODES: IncrementCounterOnMatch\nNOTE: The files in the repo are not organized." - }, - { - "author": "blueraincoatli", - "title": "ComfyModelCleaner [WIP]", - "reference": "https://github.com/blueraincoatli/ComfyUI-Model-Cleaner", - "files": [ - "https://github.com/blueraincoatli/ComfyUI-Model-Cleaner" - ], - "install_type": "git-clone", - "description": "This plugin helps identify and clean up unused model files in ComfyUI installations. It analyzes workflows, custom nodes, and model usage to safely identify redundant files." - }, - { - "author": "avocadori", - "title": "ComfyUI Audio Amplitude Converter [WIP]", - "reference": "https://github.com/avocadori/ComfyUI-AudioAmplitudeConverter", - "files": [ - "https://github.com/avocadori/ComfyUI-AudioAmplitudeConverter" - ], - "install_type": "git-clone", - "description": "This is a high-performance custom node for ComfyUI that performs audio amplitude conversion.\nNOTE: The files in the repo are not organized." - }, - { - "author": "wTechArtist", - "title": "ComfyUI_VVL_VideoCamera", - "reference": "https://github.com/wTechArtist/ComfyUI_VVL_VideoCamera", - "files": [ - "https://github.com/wTechArtist/ComfyUI_VVL_VideoCamera" - ], - "install_type": "git-clone", - "description": "NODES: VVL Video Camera Estimator, VVL Video Frame Extractor" - }, - { - "author": "wTechArtist", - "title": "ComfyUI_VVL_Segmentation [WIP]", - "reference": "https://github.com/wTechArtist/ComfyUI_VVL_Segmentation", - "files": [ - "https://github.com/wTechArtist/ComfyUI_VVL_Segmentation" - ], - "install_type": "git-clone", - "description": "NODES: VVL Mask2Former Panoptic (Enhanced), VVL OneFormer Universal Segmentation\nNOTE: The files in the repo are not organized." - }, - { - "author": "lum3on", - "title": "comfyui_RollingDepth [WIP]", - "reference": "https://github.com/lum3on/comfyui_RollingDepth", - "files": [ - "https://github.com/lum3on/comfyui_RollingDepth" - ], - "install_type": "git-clone", - "description": "ComfyuI Needs longer to start the first time, because the mode gets downloaded.\nNOTE: The files in the repo are not organized." - }, - { - "author": "abuzreq", - "title": "ComfyUI Model Bending [UNSAFE]", - "reference": "https://github.com/abuzreq/ComfyUI-Model-Bending", - "files": [ - "https://github.com/abuzreq/ComfyUI-Model-Bending" - ], - "install_type": "git-clone", - "description": "Use model bending to push your model beyond its visuals' limits. These nodes allow you to apply transformations to the intemediate densoising steps during sampling, e.g. add, multiplty, scale, rotate, dilate, erode ..etc.[w/This nodepack contains a vulnerability that allows remote code execution.]" - }, - { - "author": "Stable Diffusion VN", - "title": "SDVN Comfy node [UNSAFE]", - "id": "SDVN", - "reference": "https://github.com/StableDiffusionVN/SDVN_Comfy_node", - "files": [ - "https://github.com/StableDiffusionVN/SDVN_Comfy_node" - ], - "install_type": "git-clone", - "description": "Update IC Lora Layout Support Node[w/This nodepack contains a vulnerability that allows remote code execution.]" - }, - { - "author": "Sephrael", - "title": "comfyui_caption-around-image", - "reference": "https://github.com/Sephrael/comfyui_caption-around-image", - "files": [ - "https://github.com/Sephrael/comfyui_caption-around-image" - ], - "install_type": "git-clone", - "description": "NODES: A comfyUI node to create captions around a generated image with the ability to dynamically include generation parameters" - }, - { - "author": "yincangshiwei", - "title": "ComfyUI-SEQLToolNode", - "reference": "https://github.com/yincangshiwei/ComfyUI-SEQLToolNode", - "files": [ - "https://github.com/yincangshiwei/ComfyUI-SEQLToolNode" - ], - "install_type": "git-clone", - "description": "NODES: ImageCropAlphaNode (Image), CanvasFusionNode (Image)" - }, - { - "author": "gabe-init", - "title": "comfyui_ui_render [UNSAFE]", - "reference": "https://github.com/gabe-init/comfyui_ui_render", - "files": [ - "https://github.com/gabe-init/comfyui_ui_render" - ], - "install_type": "git-clone", - "description": "ComfyUI HTML Renderer Node - Display rich HTML content within ComfyUI nodes[w/This nodepack contains nodes that potentially have XSS vulnerabilities.]" - }, - { - "author": "gabe-init", - "title": "ComfyUI LM Studio Node [WIP]", - "reference": "https://github.com/gabe-init/ComfyUI-LM-Studio", - "files": [ - "https://github.com/gabe-init/ComfyUI-LM-Studio" - ], - "install_type": "git-clone", - "description": "A powerful ComfyUI custom node that seamlessly integrates LM Studio's local language models into your ComfyUI workflows. This node supports both text-only and multimodal (text + image) inputs, making it perfect for complex AI-driven creative workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "LyazS", - "title": "ComfyUI-aznodes", - "reference": "https://github.com/LyazS/ComfyUI-aznodes", - "files": [ - "https://github.com/LyazS/ComfyUI-aznodes" - ], - "install_type": "git-clone", - "description": "NODES: CrossFadeImageSequence, SaveImageAZ" - }, - { - "author": "truebillyblue", - "title": "lC.ComfyUI_epistemic_nodes [WIP]", - "reference": "https://github.com/truebillyblue/lC.ComfyUI_epistemic_nodes", - "files": [ - "https://github.com/truebillyblue/lC.ComfyUI_epistemic_nodes" - ], - "install_type": "git-clone", - "description": "NODES: lC L1 Startle, lC L2 FrameClick, lC L3 KeymapClick, lC L4 AnchorClick, lC L5 FieldClick, lC L6 ReflectBoom, lC Epistemic Pipeline (L1-L7), Create PBI (lC), Query PBIs (lC), Update PBI (lC), lC API LLM Agent, lC Web LLM Agent, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "aklevecz", - "title": "ComfyUI-AutoPrompt [WIP]", - "reference": "https://github.com/aklevecz/ComfyUI-AutoPrompt", - "files": [ - "https://github.com/aklevecz/ComfyUI-AutoPrompt" - ], - "install_type": "git-clone", - "description": "NODES: Ollama Prompt Generator, Ollama Model Lister, Ollama Chat, Text Display" - }, - { - "author": "AlexYez", - "title": "ComfyUI Timesaver Nodes", - "reference": "https://github.com/AlexYez/comfyui-timesaver", - "files": [ - "https://github.com/AlexYez/comfyui-timesaver" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes from [Timesaver](https://github.com/AlexYez/comfyui-timesaver)." - }, - { - "author": "aa-parky", - "title": "pipemind-comfyui", - "reference": "https://github.com/aa-parky/pipemind-comfyui", - "files": [ - "https://github.com/aa-parky/pipemind-comfyui" - ], - "install_type": "git-clone", - "description": "NODES: Random Line from File (Seeded), Keyword Prompt Composer, Simple Prompt Combiner (5x), Boolean Switch (Any), Select Line from TxT (Any), Multiline Text Input, Flux 2M Aspect Ratios, SDXL Aspect Ratios, Room Mapper, ..." - }, - { - "author": "pacchikAI", - "title": "ImagePromptBatch [UNSAFE]", - "reference": "https://github.com/pacchikAI/ImagePromptBatch", - "files": [ - "https://github.com/pacchikAI/ImagePromptBatch" - ], - "install_type": "git-clone", - "description": "NODES: Load Image and Prompt[w/This includes a node that can read the contents of a `.csv` file from an arbitrary path.]" - }, - { - "author": "papcorns", - "title": "ComfyUI-Papcorns-Node-UploadToGCS", - "reference": "https://github.com/papcorns/ComfyUI-Papcorns-Node-UploadToGCS", - "files": [ - "https://github.com/papcorns/ComfyUI-Papcorns-Node-UploadToGCS" - ], - "install_type": "git-clone", - "description": "NODES: Upload Image To GCS" - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "Qwen-2.5 in ComfyUI [NAME CONFLICT]", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen", - "files": [ - "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen" - ], - "install_type": "git-clone", - "description": "Using Qwen-2.5 in ComfyUI" - }, - { - "author": "Charonartist", - "title": "gabe-init [WIP]", - "reference": "https://github.com/gabe-init/ComfyUI-Repo-Eater", - "files": [ - "https://github.com/gabe-init/ComfyUI-Repo-Eater" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that ingests GitHub repositories and outputs their content as text along with token count.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Charonartist", - "title": "ComfyUI-send-eagle-pro", - "reference": "https://github.com/Charonartist/ComfyUI-send-eagle-pro_2", - "files": [ - "https://github.com/Charonartist/ComfyUI-send-eagle-pro_2" - ], - "install_type": "git-clone", - "description": "Eagle integration extension node for ComfyUI (Pro version)" - }, - { - "author": "Mervent", - "title": "comfyui-yaml-prompt", - "reference": "https://github.com/Mervent/comfyui-yaml-prompt", - "files": [ - "https://github.com/Mervent/comfyui-yaml-prompt" - ], - "install_type": "git-clone", - "description": "NODES: YAMLPromptParser" - }, - { - "author": "dhpdong", - "title": "ComfyUI-IPAdapter-Flux-Repair", - "reference": "https://github.com/dhpdong/ComfyUI-IPAdapter-Flux-Repair", - "files": [ - "https://github.com/dhpdong/ComfyUI-IPAdapter-Flux-Repair" - ], - "install_type": "git-clone", - "description": "The IPAdapter-Flux node may cause some GPU memory to not be properly released during multiple inferences or when alternating between two nodes, eventually leading to a memory overflow. This project addresses and fixes that issue." - }, - { - "author": "Mervent", - "title": "comfyui-telegram-send", - "reference": "https://github.com/Mervent/comfyui-telegram-send", - "files": [ - "https://github.com/Mervent/comfyui-telegram-send" - ], - "install_type": "git-clone", - "description": "NODES: TelegramSend, TelegramReply" - }, - { - "author": "qlikpetersen", - "title": "ComfyUI-AI_Tools [UNSAFE]", - "reference": "https://github.com/qlikpetersen/ComfyUI-AI_Tools", - "files": [ - "https://github.com/qlikpetersen/ComfyUI-AI_Tools" - ], - "install_type": "git-clone", - "description": "NODES: DoLogin, HttpRequest, Json2String, String2Json, CreateListString, CreateListJSON, Query_OpenAI, Image_Attachment, JSON_Attachment, String_Attachment, RunPython\n[w/This nodepack contains a node with a vulnerability that allows arbitrary code execution.]" - }, - { - "author": "MuAIGC", - "title": "DMXAPI Nodes [WIP]", - "reference": "https://github.com/MuAIGC/ComfyUI-DMXAPI_mmx", - "files": [ - "https://github.com/MuAIGC/ComfyUI-DMXAPI_mmx" - ], - "install_type": "git-clone", - "description": "DMXAPI integration for ComfyUI with Seedream-3.0 text-to-image model\nNOTE: invalid pyproject.toml" - }, - { - "author": "Hapseleg", - "title": "This n that (Hapse)", - "reference": "https://github.com/Hapseleg/ComfyUI-This-n-That", - "files": [ - "https://github.com/Hapseleg/ComfyUI-This-n-That" - ], - "install_type": "git-clone", - "description": "Comfyui custom nodes I use for... This n That..." - }, - { - "author": "matDobek", - "title": "ComfyUI_duck", - "reference": "https://github.com/matDobek/ComfyUI_duck", - "files": [ - "https://github.com/matDobek/ComfyUI_duck" - ], - "install_type": "git-clone", - "description": "NODES: Combine Images (duck)" - }, - { - "author": "usman2003", - "title": "ComfyUI-Gender-Classifier", - "reference": "https://github.com/usman2003/ComfyUI-Gender-Classifier", - "files": [ - "https://github.com/usman2003/ComfyUI-Gender-Classifier" - ], - "install_type": "git-clone", - "description": "NODES: Gender Classification" - }, - { - "author": "wTechArtist", - "title": "ComfyUI_vvl_BBOX", - "reference": "https://github.com/wTechArtist/ComfyUI_vvl_BBOX", - "files": [ - "https://github.com/wTechArtist/ComfyUI_vvl_BBOX" - ], - "install_type": "git-clone", - "description": "NODES: vvl BBox Input" - }, - { - "author": "zhengxyz123", - "title": "zhengxyz123/ComfyUI-CLIPSeg [NAME CONFLICT]", - "reference": "https://github.com/zhengxyz123/ComfyUI-CLIPSeg", - "files": [ - "https://github.com/zhengxyz123/ComfyUI-CLIPSeg" - ], - "install_type": "git-clone", - "description": "Using CLIPSeg model to generate masks for image inpainting tasks based on text or image prompts." - }, - { - "author": "Alazuaka", - "title": "ComfyUI Image Analysis Toolkit [WIP]", - "reference": "https://github.com/ThatGlennD/ComfyUI-Image-Analysis-Tools", - "files": [ - "https://github.com/ThatGlennD/ComfyUI-Image-Analysis-Tools" - ], - "install_type": "git-clone", - "description": "A suite of custom ComfyUI nodes built to evaluate and diagnose the technical qualities of images—especially those generated by AI models. Rather than creating visuals, these tools measure them, offering precise insights into sharpness, noise, exposure, color balance, and more.\nNOTE: The files in the repo are not organized." - }, - { - "author": "trampolin", - "title": "comfy-ui-scryfall", - "reference": "https://github.com/trampolin/comfy-ui-scryfall", - "files": [ - "https://github.com/trampolin/comfy-ui-scryfall" - ], - "install_type": "git-clone", - "description": "Some ComfyUI nodes to fetch cards from scryfall" - }, - { - "author": "pomelyu", - "title": "cy-prompt-tools", - "reference": "https://github.com/pomelyu/cy-prompt-tools", - "files": [ - "https://github.com/pomelyu/cy-prompt-tools" - ], - "install_type": "git-clone", - "description": "prompt tools for comfyui" - }, - { - "author": "Alazuaka", - "title": "ES_nodes for ComfyUI by Alazuka [WIP]", - "reference": "https://github.com/Alazuaka/comfyui-lora-stack-node", - "files": [ - "https://github.com/Alazuaka/comfyui-lora-stack-node" - ], - "install_type": "git-clone", - "description": "Node for LoRA stack management in ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "PabloGrant", - "title": "comfyui-giraffe-test-panel", - "reference": "https://github.com/PabloGrant/comfyui-giraffe-test-panel", - "files": [ - "https://github.com/PabloGrant/comfyui-giraffe-test-panel" - ], - "install_type": "git-clone", - "description": "General-purpose test node. [w/Use at your own risk. No warranties. No guaranteed support or future updates. Feel free to fork, but remember to share in case anyone else can benefit.]" - }, - { - "author": "lrzjason", - "title": "Comfyui-Condition-Utils [WIP]", - "reference": "https://github.com/lrzjason/Comfyui-Condition-Utils", - "files": [ - "https://github.com/lrzjason/Comfyui-Condition-Utils" - ], - "install_type": "git-clone", - "description": "A collection of utility nodes for handling condition tensors in ComfyUI." - }, - { - "author": "gordon123", - "title": "ComfyUI_DreamBoard [WIP]", - "reference": "https://github.com/gordon123/ComfyUI_DreamBoard", - "files": [ - "https://github.com/gordon123/ComfyUI_DreamBoard" - ], - "install_type": "git-clone", - "description": "for making storyboard UNDERCONSTRUCTION!" - }, - { - "author": "erosDiffusion", - "title": "Select key from JSON (Alpha) [UNSAFE]", - "reference": "https://github.com/erosDiffusion/ComfyUI-enricos-json-file-load-and-value-selector", - "files": [ - "https://github.com/erosDiffusion/ComfyUI-enricos-json-file-load-and-value-selector" - ], - "install_type": "git-clone", - "description": "this node lists json files in the ComfyUI input folder[w/If this nodepack is installed and the server is running with remote access enabled, it can read the contents of JSON files located in arbitrary paths.]" - }, - { - "author": "yichengup", - "title": "ComfyUI-YCNodes_Advance", - "reference": "https://github.com/yichengup/ComfyUI-YCNodes_Advance", - "files": [ - "https://github.com/yichengup/ComfyUI-YCNodes_Advance" - ], - "install_type": "git-clone", - "description": "NODES: Face Detector Selector, YC Human Parts Ultra(Advance), Color Match (YC)" - }, - { - "author": "maizerrr", - "title": "comfyui-code-nodes", - "reference": "https://github.com/maizerrr/comfyui-code-nodes", - "files": [ - "https://github.com/maizerrr/comfyui-code-nodes" - ], - "install_type": "git-clone", - "description": "NODES: BBox Drawer, BBox Parser, Dummy Passthrough Node, Batch Images (up to 5), Mask Editor, OpenAI GPT-Image-1 Node, GhatGPT Node" - }, - { - "author": "virallover", - "title": "comfyui-virallover", - "reference": "https://github.com/virallover/comfyui-virallover", - "files": [ - "https://github.com/virallover/comfyui-virallover" - ], - "install_type": "git-clone", - "description": "NODES: Download and Load Lora Model Only, Depth Fitter, Brightness Correction, Edge Noise, Feathered Sharpen, Concat Horizontal With Mask" - }, - { - "author": "jax-explorer", - "title": "ComfyUI-DreamO", - "reference": "https://github.com/jax-explorer/ComfyUI-DreamO", - "files": [ - "https://github.com/jax-explorer/ComfyUI-DreamO" - ], - "install_type": "git-clone", - "description": "[a/https://github.com/bytedance/DreamO](https://github.com/bytedance/DreamO]) ComfyUI Warpper" - }, - { - "author": "SKBv0", - "title": "Retro Engine Node for ComfyUI", - "reference": "https://github.com/SKBv0/ComfyUI-RetroEngine", - "files": [ - "https://github.com/SKBv0/ComfyUI-RetroEngine" - ], - "install_type": "git-clone", - "description": "This custom node integrates [a/EmulatorJS](https://github.com/EmulatorJS/EmulatorJS) into ComfyUI, allowing you to run retro games and capture their screens for your image generation workflows." - }, - { - "author": "brace-great", - "title": "comfyui-eim", - "reference": "https://github.com/brace-great/comfyui-eim", - "files": [ - "https://github.com/brace-great/comfyui-eim" - ], - "install_type": "git-clone", - "description": "NODES: EncryptImage" - }, - { - "author": "p1atdev", - "title": "comfyui-aesthetic-predictor", - "reference": "https://github.com/p1atdev/comfyui-aesthetic-predictor", - "files": [ - "https://github.com/p1atdev/comfyui-aesthetic-predictor" - ], - "install_type": "git-clone", - "description": "NODES: Load Aesthetic Predictor, Predict Aesthetic Score" - }, - { - "author": "barakapa", - "title": "barakapa-nodes", - "reference": "https://github.com/barakapa/barakapa-nodes", - "files": [ - "https://github.com/barakapa/barakapa-nodes" - ], - "install_type": "git-clone", - "description": "Compare and save unique workflows, count tokens in prompt, and other utility." - }, - { - "author": "VictorLopes643", - "title": "ComfyUI-Video-Dataset-Tools [WIP]", - "reference": "https://github.com/VictorLopes643/ComfyUI-Video-Dataset-Tools", - "files": [ - "https://github.com/VictorLopes643/ComfyUI-Video-Dataset-Tools" - ], - "install_type": "git-clone", - "description": "NODES: Video Frame Extractor, Image Frame Saver\nNOTE: The files in the repo are not organized." - }, - { - "author": "George0726", - "title": "ComfyUI-video-accessory [WIP]", - "reference": "https://github.com/George0726/ComfyUI-video-accessory", - "files": [ - "https://github.com/George0726/ComfyUI-video-accessory" - ], - "install_type": "git-clone", - "description": "accessory nodes for video generation" - }, - { - "author": "bheins", - "title": "ComfyUI-glb-to-stl [WIP]", - "reference": "https://github.com/maurorilla/ComfyUI-MisterMR-Nodes", - "files": [ - "https://github.com/maurorilla/ComfyUI-MisterMR-Nodes" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI that add drawing capabilities to your workflow.\nNOTE: The files in the repo are not organized." - }, - { - "author": "TheJorseman", - "title": "IntrinsicCompositingClean-ComfyUI", - "reference": "https://github.com/TheJorseman/IntrinsicCompositingClean-ComfyUI", - "files": [ - "https://github.com/TheJorseman/IntrinsicCompositingClean-ComfyUI" - ], - "install_type": "git-clone", - "description": "NODES: DepthModelLoader, NormalsModelLoader, IntrinsicModelLoader, AlbedoModelLoader, ReshadingModelLoader, ReshadingProcessor, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "bheins", - "title": "ComfyUI-glb-to-stl [WIP]", - "reference": "https://github.com/bheins/ComfyUI-glb-to-stl", - "files": [ - "https://github.com/bheins/ComfyUI-glb-to-stl" - ], - "install_type": "git-clone", - "description": "GLB conversion to STL node for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "Anonymzx", - "title": "ComfyUI-Indonesia-TTS [WIP]", - "reference": "https://github.com/Anonymzx/ComfyUI-Indonesia-TTS", - "files": [ - "https://github.com/Anonymzx/ComfyUI-Indonesia-TTS" - ], - "description": "Repositori ini menyediakan integrasi model Text-to-Speech (TTS) Bahasa Indonesia dari Facebook (MMS-TTS-IND) ke dalam ComfyUI, sehingga Anda dapat langsung menyintesis suara berbahasa Indonesia dengan kontrol penuh via antarmuka node-based.\nNOTE: The files in the repo are not organized.", - "install_type": "git-clone" - }, - { - "author": "3dmindscapper", - "title": "ComfyUI-Sam-Mesh [WIP]", - "reference": "https://github.com/3dmindscapper/ComfyUI-Sam-Mesh", - "files": [ - "https://github.com/3dmindscapper/ComfyUI-Sam-Mesh" - ], - "install_type": "git-clone", - "description": "comfyui implementation of SaMesh segmentation of 3d meshes\nNOTE: The files in the repo are not organized." - }, - { - "author": "shinich39", - "title": "comfyui-run-js [UNSAFE]", - "reference": "https://github.com/shinich39/comfyui-run-js", - "files": [ - "https://github.com/shinich39/comfyui-run-js" - ], - "description": "Manipulate workflow via javascript on node.", - "install_type": "git-clone" - }, - { - "author": "fangg2000", - "title": "ComfyUI-SenseVoice [WIP]", - "reference": "https://github.com/fangg2000/ComfyUI-SenseVoice", - "files": [ - "https://github.com/fangg2000/ComfyUI-SenseVoice" - ], - "description": "A comfyui node plug-in developed based on the SenseVoise project, and a simple recording node.\nNOTE: The files in the repo are not organized.", - "install_type": "git-clone" - }, - { - "author": "risunobushi", - "title": "ComfyUI_FaceMesh_Eyewear_Mask", - "reference": "https://github.com/risunobushi/ComfyUI_FaceMesh_Eyewear_Mask", - "files": [ - "https://github.com/risunobushi/ComfyUI_FaceMesh_Eyewear_Mask" - ], - "description": "NODES: Face Mesh Eyewear Mask, MediaPipe Face to Mask, OpenPose Eyewear Mask (DWPose), Mask From Facial Keypoints", - "install_type": "git-clone" - }, - { - "author": "machinesarenotpeople", - "title": "comfyui-energycost", - "reference": "https://github.com/machinesarenotpeople/comfyui-energycost", - "files": [ - "https://github.com/machinesarenotpeople/comfyui-energycost" - ], - "description": "NODES: Energy Cost Timer, Energy Cost Calculator", - "install_type": "git-clone" - }, - { - "author": "xqqe", - "title": "honey_nodes [WIP]", - "reference": "https://github.com/xqqe/honey_nodes", - "files": [ - "https://github.com/xqqe/honey_nodes" - ], - "description": "honey nodes for comfyui\nNOTE: The files in the repo are not organized.", - "install_type": "git-clone" - }, - { - "author": "Raidez", - "title": "Kuniklo Collection", - "reference": "https://github.com/Raidez/comfyui-kuniklo-collection", - "files": [ - "https://github.com/Raidez/comfyui-kuniklo-collection" - ], - "description": "NODES: Properties, Apply SVG to Image", - "install_type": "git-clone" - }, - { - "author": "Kur0butiMegane", - "title": "Comfyui-StringUtils", - "reference": "https://github.com/Kur0butiMegane/Comfyui-StringUtils2", - "files": [ - "https://github.com/Kur0butiMegane/Comfyui-StringUtils2" - ], - "install_type": "git-clone", - "description": "NODES: Normalizer, Splitter, Selector, XML Parser, XML Parser, Make Property, Add XML Tag, Is String Empty, Cond Passthrough, CLIP Passthrough, ClipRegion Passthrough, Scheduler Selector (Impact), Scheduler Selector (Inspire), Save Text, XML to Cutoff" - }, - { - "author": "ronaldstg", - "title": "comfyui-plus-integrations [WIP]", - "reference": "https://github.com/ronalds-eu/comfyui-plus-integrations", - "files": [ - "https://github.com/ronalds-eu/comfyui-plus-integrations" - ], - "install_type": "git-clone", - "description": "NODES: Image Pass Through, Upload Image to S3\nNOTE: The files in the repo are not organized." - }, - { - "author": "kevin314", - "title": "ComfyUI-FastVideo", - "reference": "https://github.com/kevin314/ComfyUI-FastVideo", - "files": [ - "https://github.com/kevin314/ComfyUI-FastVideo" - ], - "description": "NODES: Video Generator, Inference Args, VAE Config, Text Encoder Config, DIT Config", - "install_type": "git-clone" - }, - { - "author": "benda1989", - "title": "Comfyui lama remover [WIP]", - "reference": "https://github.com/benda1989/WaterMarkRemover_ComfyUI", - "files": [ - "https://github.com/benda1989/WaterMarkRemover_ComfyUI" - ], - "install_type": "git-clone", - "description": "A very simple ComfyUI node to remove item like image/video with mask watermark\nNOTE: The files in the repo are not organized." - }, - { - "author": "3dmindscapper", - "title": "ComfyUI-PartField [WIP]", - "reference": "https://github.com/3dmindscapper/ComfyUI-PartField", - "files": [ - "https://github.com/3dmindscapper/ComfyUI-PartField" - ], - "install_type": "git-clone", - "description": "ComfyUI implementation of the partfield nvidea segmentation models\nNOTE: The files in the repo are not organized." - }, - { - "author": "silveroxides", - "title": "ComfyUI_ReduxEmbedToolkit", - "reference": "https://github.com/silveroxides/ComfyUI_ReduxEmbedToolkit", - "files": [ - "https://github.com/silveroxides/ComfyUI_ReduxEmbedToolkit" - ], - "install_type": "git-clone", - "description": "Custom nodes for managing, saving and loading of Redux/Style based embeddings." - }, - { - "author": "StaffsGull", - "title": "comfyui_scene_builder [WIP]", - "reference": "https://github.com/StaffsGull/comfyui_scene_builder", - "files": [ - "https://github.com/StaffsGull/comfyui_scene_builder" - ], - "install_type": "git-clone", - "description": "NODES: CharacterBuilderNode, ClothingItemNode, ClothingMergerNode, EnvironmentBuilderNode, MergeCharactersNode, PhotoStyleBuilderNode, SceneCombinerNode\nNOTE: The files in the repo are not organized." - }, - { - "author": "gagaprince", - "title": "ComfyUI_gaga_utils", - "reference": "https://github.com/gagaprince/ComfyUI_gaga_utils", - "files": [ - "https://github.com/gagaprince/ComfyUI_gaga_utils" - ], - "install_type": "git-clone", - "description": "NODES: GagaGetFileList, GagaGetStringListSize, GagaSplitStringToList, GagaTest, GagaBatchStringReplace" - }, - { - "author": "ftechmax", - "title": "ComfyUI-NovaKit-Pack", - "reference": "https://github.com/ftechmax/ComfyUI-NovaKit-Pack", - "files": [ - "https://github.com/ftechmax/ComfyUI-NovaKit-Pack" - ], - "install_type": "git-clone", - "description": "NODES: Count Tokens" - }, - { - "author": "jtydhr88", - "title": "ComfyUI Frontend Vue Basic [WIP]", - "reference": "https://github.com/jtydhr88/ComfyUI_frontend_vue_basic", - "files": [ - "https://github.com/jtydhr88/ComfyUI_frontend_vue_basic" - ], - "install_type": "git-clone", - "description": "A demonstration custom node that showcases how to integrate Vue as a frontend framework within ComfyUI, complete with PrimeVue components and vue-i18n support." - }, - { - "author": "silent-rain", - "title": "ComfyUI-SilentRain", - "reference": "https://github.com/silent-rain/ComfyUI-SilentRain", - "files": [ - "https://github.com/silent-rain/ComfyUI-SilentRain" - ], - "install_type": "git-clone", - "description": "Ecological extension of comfyui using Rust language." - }, - { - "author": "EricRollei", - "title": "Comfy-Metadata-System [WIP]", - "reference": "https://github.com/EricRollei/Comfy-Metadata-System", - "files": [ - "https://github.com/EricRollei/Comfy-Metadata-System" - ], - "install_type": "git-clone", - "description": "Series of custom Comfyui Nodes that collects and saves metadata to embedded (png, jpg) as well as optional xmp and txt sidecars and database" - }, - { - "author": "turskeli", - "title": "comfyui-SetWallpaper", - "reference": "https://github.com/turskeli/comfyui-SetWallpaper", - "files": [ - "https://github.com/turskeli/comfyui-SetWallpaper" - ], - "install_type": "git-clone", - "description": "Simple wallpaper node for ComfyUI. Curently only supports Windows OS" - }, - { - "author": "Sophylax", - "title": "ComfyUI-ReferenceMerge", - "reference": "https://github.com/Sophylax/ComfyUI-ReferenceMerge", - "files": [ - "https://github.com/Sophylax/ComfyUI-ReferenceMerge" - ], - "install_type": "git-clone", - "description": "NODES: Combine Images and Mask, Restitch Combined Crop" - }, - { - "author": "bandido37", - "title": "Kaggle ComfyUI Local Save Node [WIP]", - "reference": "https://github.com/bandido37/comfyui-kaggle-local-save", - "files": [ - "https://github.com/bandido37/comfyui-kaggle-local-save" - ], - "install_type": "git-clone", - "description": "This custom node for ComfyUI allows you to save generated images directly to your local PC instead of Kaggle's cloud output folder.\nNOTE: The files in the repo are not organized." - }, - { - "author": "springjk", - "title": "Psutil Container Memory Patch", - "reference": "https://github.com/springjk/ComfyUI-Psutil-Container-Memory-Patch", - "files": [ - "https://github.com/springjk/ComfyUI-Psutil-Container-Memory-Patch" - ], - "install_type": "git-clone", - "description": "Make ComfyUI get correct memory information in the container (psutil monkey path)" - }, - { - "author": "songtianhui", - "title": "ComfyUI-DMM [WIP]", - "reference": "https://github.com/songtianhui/ComfyUI-DMM", - "files": [ - "https://github.com/songtianhui/ComfyUI-DMM" - ], - "install_type": "git-clone", - "description": "NODES: DMMLoader, DMMApply" - }, - { - "author": "leon-etienne", - "title": "ComfyUI_Scoring-Nodes", - "reference": "https://github.com/leon-etienne/ComfyUI_Scoring-Nodes", - "files": [ - "https://github.com/leon-etienne/ComfyUI_Scoring-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Text Similarity (CLIP), Image Similarity (CLIP), Multi Text→Image Similarity, Multi Image→Text Similarity, Aesthetic Score, Multi Aesthetic Comparison" - }, - { - "author": "tanmoy-it", - "title": "comfyuiCustomNode", - "reference": "https://github.com/tanmoy-it/comfyuiCustomNode", - "files": [ - "https://github.com/tanmoy-it/comfyuiCustomNode" - ], - "install_type": "git-clone", - "description": "NODES: Download Image (Direct/No Save)" - }, - { - "author": "Jingwen-genies", - "title": "comfyui-genies-nodes", - "reference": "https://github.com/Jingwen-genies/comfyui-genies-nodes", - "files": [ - "https://github.com/Jingwen-genies/comfyui-genies-nodes" - ], - "install_type": "git-clone", - "description": "NODES: Genies Pose Estimation, Genies Scale Face by Keypoints, Get V Channel from HSV, Select RGB by Mask" - }, - { - "author": "Tawbaware", - "title": "ComfyUI-Tawbaware [WIP]", - "reference": "https://github.com/Tawbaware/ComfyUI-Tawbaware", - "files": [ - "https://github.com/Tawbaware/ComfyUI-Tawbaware" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "lucafoscili", - "title": "LF Nodes [UNSAFE]", - "reference": "https://github.com/lucafoscili/lf-nodes", - "files": [ - "https://github.com/lucafoscili/lf-nodes" - ], - "install_type": "git-clone", - "description": "Custom nodes with a touch of extra UX, including: history for primitives, JSON manipulation, logic switches with visual feedback, LLM chat... and more!\n[w/This nodepack contains a node with a vulnerability that allows arbitrary code execution.]" - }, - { - "author": "jerryname2022", - "title": "ComfyUI-Real-ESRGAN [WIP]", - "reference": "https://github.com/jerryname2022/ComfyUI-Real-ESRGAN", - "files": [ - "https://github.com/jerryname2022/ComfyUI-Real-ESRGAN" - ], - "install_type": "git-clone", - "description": "NODES: Real-ESRGAN Model Loader, GFPGAN Model Loader, Real-ESRGAN Image Generator, GFPGAN Image Generator" - }, - { - "author": "mm-akhtar", - "title": "comfyui-mask-selector-node", - "reference": "https://github.com/mm-akhtar/comfyui-mask-selector-node", - "files": [ - "https://github.com/mm-akhtar/comfyui-mask-selector-node" - ], - "install_type": "git-clone", - "description": "NODES: Mask Selector" - }, - { - "author": "ryanontheinside", - "title": "ComfyUI-Livepeer [WIP]", - "reference": "https://github.com/ryanontheinside/ComfyUI-Livepeer", - "files": [ - "https://github.com/ryanontheinside/ComfyUI-Livepeer" - ], - "install_type": "git-clone", - "description": "A ComfyUI extension that provides integration with [a/Livepeer](https://livepeer.org/)'s AI services allowing for both sync and async generation." - }, - { - "author": "newraina", - "title": "ComfyUI-Remote-Save-Image [UNSAFE]", - "reference": "https://github.com/newraina/ComfyUI-Remote-Save-Image", - "files": [ - "https://github.com/newraina/ComfyUI-Remote-Save-Image" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that allows uploading generated images to any HTTP endpoint.[w/This node allows any users to send any locally stored image to a specified URL.]" - }, - { - "author": "SXQBW", - "title": "ComfyUI-Qwen-VLM [WIP]", - "reference": "https://github.com/SXQBW/ComfyUI-Qwen3", - "files": [ - "https://github.com/SXQBW/ComfyUI-Qwen3" - ], - "install_type": "git-clone", - "description": "NODES: QwenVLM" - }, - { - "author": "kijai", - "title": "ComfyUI-FramePackWrapper [WIP]", - "reference": "https://github.com/kijai/ComfyUI-FramePackWrapper", - "files": [ - "https://github.com/kijai/ComfyUI-FramePackWrapper" - ], - "install_type": "git-clone", - "description": "ComfyUI Wrapper for FramePack by lllyasviel" - }, - { - "author": "WaiyanLing", - "title": "ComfyUI-Tracking [WIP]", - "reference": "https://github.com/WaiyanLing/ComfyUI-Tracking", - "files": [ - "https://github.com/WaiyanLing/ComfyUI-Tracking" - ], - "install_type": "git-clone", - "description": "ComfyUI-Tracking This nodepack helps to conveniently collect invocation data from workflows for further study.\nNOTE: The files in the repo are not organized." - }, - { - "author": "vladp0727", - "title": "ComfyUI Simple Image Tools [WIP]", - "reference": "https://github.com/vladp0727/Comfyui-with-Furniture", - "files": [ - "https://github.com/vladp0727/Comfyui-with-Furniture" - ], - "install_type": "git-clone", - "description": "NODES: Get Mask From Alpha, Get Quadrilateral Outfit\nNOTE: The files in the repo are not organized." - }, - { - "author": "ryanontheinside", - "title": "ComfyUI MineWorld Nodes [WIP]", - "reference": "https://github.com/ryanontheinside/ComfyUI-MineWorld", - "files": [ - "https://github.com/ryanontheinside/ComfyUI-MineWorld" - ], - "install_type": "git-clone", - "description": "This extension integrates Microsoft's MineWorld - an interactive world model for Minecraft - into ComfyUI.\nMineWorld allows you to generate interactive Minecraft gameplay based on actions you provide, creating realistic Minecraft gameplay videos." - }, - { - "author": "SanDiegoDude", - "title": "HiDreamSampler for ComfyUI [WIP]", - "reference": "https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler", - "files": [ - "https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node for generating images using the HiDream AI model.\nNOTE: The files in the repo are not organized." - }, - { - "author": "ZenAI-Vietnam", - "title": "ComfyUI_InfiniteYou [NAME CONFLICT]", - "reference": "https://github.com/ZenAI-Vietnam/ComfyUI_InfiniteYou", - "files": [ - "https://github.com/ZenAI-Vietnam/ComfyUI_InfiniteYou" - ], - "install_type": "git-clone", - "description": "An implementation of InfiniteYou for ComfyUI. Native support for [a/InfiniteYou](https://github.com/bytedance/InfiniteYou) in ComfyUI, designed by the ZenAI team." - }, - { - "author": "filipemeneses", - "title": "ComfyUI_html [UNSAFE]", - "reference": "https://github.com/filipemeneses/ComfyUI_html", - "files": [ - "https://github.com/filipemeneses/ComfyUI_html" - ], - "install_type": "git-clone", - "description": "Nodes to manipulate HTML.[w/This extension poses a risk of XSS vulnerability.]" - }, - { - "author": "m-ai-studio", - "title": "mai-prompt-progress", - "reference": "https://github.com/m-ai-studio/mai-prompt-progress", - "files": [ - "https://github.com/m-ai-studio/mai-prompt-progress" - ], - "install_type": "git-clone", - "description": "ComfyUI extensions for sending prompt progress to webhook" - }, - { - "author": "ashllay", - "title": "ComfyUI_MoreComfy", - "reference": "https://github.com/ashllay/ComfyUI_MoreComfy", - "files": [ - "https://github.com/ashllay/ComfyUI_MoreComfy" - ], - "install_type": "git-clone", - "description": "NODES: MC Switch Seed/Image/Latent/Model/String, MC Alter Seed, MC Set Tile Size, MC Get Image Size, MC Multi Concat" - }, - { - "author": "gordon1chuge2623", - "title": "ComfyUI_seal_migration [WIP]", - "reference": "https://github.com/chuge26/ComfyUI_seal_migration", - "files": [ - "https://github.com/chuge26/ComfyUI_seal_migration" - ], - "install_type": "git-clone", - "description": "This project implements stamp migration in PDF files based on ComfyUI, allowing stamps from specified pages of a source PDF to be transferred to specified pages of a target PDF.\nNOTE: The files in the repo are not organized." - }, - { - "author": "gordon123", - "title": "ComfyUI_srt2speech [WIP]", - "reference": "https://github.com/gordon123/ComfyUI_srt2speech", - "files": [ - "https://github.com/gordon123/ComfyUI_srt2speech" - ], - "install_type": "git-clone", - "description": "ComfyUI_srt2speech" - }, - { - "author": "hnmr293", - "title": "ComfyUI-SamOne - one-step sampling", - "reference": "https://github.com/hnmr293/ComfyUI-SamOne", - "files": [ - "https://github.com/hnmr293/ComfyUI-SamOne" - ], - "install_type": "git-clone", - "description": "This is a node that advances sampling by just one step in ComfyUI." - }, - { - "author": "rphmeier", - "title": "comfyui-videodepthanything", - "reference": "https://github.com/rphmeier/comfyui-videodepthanything", - "files": [ - "https://github.com/rphmeier/comfyui-videodepthanything" - ], - "install_type": "git-clone", - "description": "VideoDepthAnything nodes for ComfyUI" - }, - { - "author": "benmizrahi", - "title": "ComfyGCS [WIP]", - "reference": "https://github.com/benmizrahi/ComfyGCS", - "files": [ - "https://github.com/benmizrahi/ComfyGCS" - ], - "install_type": "git-clone", - "description": "ComfyGCS is a robust read/write plugin for Google Cloud Storage, designed to simplify interaction with GCS buckets in your projects.\nNOTE: The files in the repo are not organized." - }, - { - "author": "dogcomplex", - "title": "ComfyUI-LOKI [WIP]", - "reference": "https://github.com/dogcomplex/ComfyUI-LOKI", - "files": [ - "https://github.com/dogcomplex/ComfyUI-LOKI" - ], - "install_type": "git-clone", - "description": "NODES: Glamour\nNOTE: This nodepack installs pip dependencies outside the control of ComfyUI-Manager." - }, - { - "author": "hunzmusic", - "title": "Comfyui-CraftsMan3DWrapper [WIP]", - "reference": "https://github.com/hunzmusic/Comfyui-CraftsMan3DWrapper", - "files": [ - "https://github.com/hunzmusic/Comfyui-CraftsMan3DWrapper" - ], - "install_type": "git-clone", - "description": "A wrapper for CraftsMan\nNOTE: The files in the repo are not organized." - }, - { - "author": "tzsoulcap", - "title": "ComfyUI-SaveImg-W-MetaData", - "reference": "https://github.com/tzsoulcap/ComfyUI-SaveImg-W-MetaData", - "files": [ - "https://github.com/tzsoulcap/ComfyUI-SaveImg-W-MetaData" - ], - "install_type": "git-clone", - "description": "NODES: CAP Checkpoint Selector, CAP Save Image w/Metadata, CAP Load Image with Metadata, CAP Tag Image, CAP Sampler Selector, CAP Scheduler Selector, CAP Seed Generator, CAP String Literal, CAP Width/Height Literal, CAP Cfg Literal, CAP Int Literal" - }, - { - "author": "hylarucoder", - "title": "comfyui-copilot", - "reference": "https://github.com/hylarucoder/comfyui-copilot", - "files": [ - "https://github.com/hylarucoder/comfyui-copilot" - ], - "install_type": "git-clone", - "description": "NODES: Eagle Image Node for PNGInfo, SDXL Resolution Presets (ws), SDXL Prompt Styler, SDXL Prompt Styler Advanced" - }, - { - "author": "AlejandroTuzzi", - "title": "TUZZI-ByPass [WIP]", - "reference": "https://github.com/AlejandroTuzzi/TUZZI-ByPass", - "files": [ - "https://github.com/AlejandroTuzzi/TUZZI-ByPass" - ], - "install_type": "git-clone", - "description": "Custom nodes for automated AI pipelines\nNOTE: The files in the repo are not organized." - }, - { - "author": "oxysoft", - "title": "Comfy-Compel", - "reference": "https://github.com/oxysoft/Comfy-Compel", - "files": [ - "https://github.com/oxysoft/Comfy-Compel" - ], - "install_type": "git-clone", - "description": "NODES: CLIP Embed (Compel)" - }, - { - "author": "QingLuanWithoutHeart", - "title": "ComfyUI File/Image Utils Nodes [UNSAFE]", - "reference": "https://github.com/QingLuanWithoutHeart/comfyui-file-image-utils", - "files": [ - "https://github.com/QingLuanWithoutHeart/comfyui-file-image-utils" - ], - "install_type": "git-clone", - "description": "This custom node set provides useful utilities for file operations and image loading in ComfyUI." - }, - { - "author": "pmarmotte2", - "title": "VibeVoiceSelector [WIP]", - "reference": "https://github.com/pmarmotte2/Comfyui-VibeVoiceSelector", - "files": [ - "https://github.com/pmarmotte2/Comfyui-VibeVoiceSelector" - ], - "install_type": "git-clone", - "description": "NODES: Vibe Voice Selector" - }, - { - "author": "Temult", - "title": "TWanVideoSigmaSampler: EXPERIMENTAL [WIP]", - "reference": "https://github.com/Temult/TWanSigmaSampler", - "files": [ - "https://github.com/Temult/TWanSigmaSampler" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node that modifies the WanVideoSampler to accept an external sigma schedule. Allows for customized and non-standard noise schedules in Wan 2.1 video generation workflow.\nNOTE: The files in the repo are not organized." - }, - { - "author": "wordbrew", - "title": "WAN Control Nodes for ComfyUI [WIP]", - "reference": "https://github.com/wordbrew/comfyui-wan-control-nodes", - "files": [ - "https://github.com/wordbrew/comfyui-wan-control-nodes" - ], - "install_type": "git-clone", - "description": "This pack provides enhanced control nodes for working with Wan video models in ComfyUI. It is under active development and may change regularly, or may not. Depends entirely on my free time and waning interest. Please don't come to rely on it for anything, but you are welcome to improve on it.\nNOTE: The files in the repo are not organized." - }, - { - "author": "techtruth", - "title": "ComfyUI-Dreambooth", - "reference": "https://github.com/techtruth/ComfyUI-Dreambooth", - "files": [ - "https://github.com/techtruth/ComfyUI-Dreambooth" - ], - "install_type": "git-clone", - "description": "NODES: Dreambooth Trainer" - }, - { - "author": "438443467", - "title": "ComfyUI-SanMian-Nodes", - "reference": "https://github.com/438443467/ComfyUI-SanMian-Nodes", - "files": [ - "https://github.com/438443467/ComfyUI-SanMian-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Add Text To Image, Adjust Hex Brightness, Adjust Transparency By Mask, Align Images with Mask, Align Restore Json, Binarize Mask, Blend ICLight, ..." - }, - { - "author": "alexgenovese", - "title": "ComfyUI-Reica", - "reference": "https://github.com/alexgenovese/ComfyUI-Reica", - "files": [ - "https://github.com/alexgenovese/ComfyUI-Reica" - ], - "install_type": "git-clone", - "description": "NODES: Reica Text Image Display, Flux Image Generator, Reica GCP: Read Image, Reica GCP: Write Image & Get URL, Reica API: Send HTTP Notification" - }, - { - "author": "Stable-X", - "title": "ComfyUI-Hi3DGen", - "reference": "https://github.com/Stable-X/ComfyUI-Hi3DGen", - "files": [ - "https://github.com/Stable-X/ComfyUI-Hi3DGen" - ], - "install_type": "git-clone", - "description": "This extension integrates [a/Hi3DGen](https://github.com/Stable-X/Hi3DGen) into ComfyUI, allowing user to generate high-fidelity 3D geometry generation from Images.[w/If the *sageattention* package is installed, this nodepack causes problems.]" - }, - { - "author": "stiffy-committee", - "title": "comfyui-stiffy-nodes", - "reference": "https://github.com/V-woodpecker-V/comfyui-stiffy-nodes", - "files": [ - "https://github.com/V-woodpecker-V/comfyui-stiffy-nodes" - ], - "install_type": "git-clone", - "description": "NODES: StiffyPrompter, StiffyPersistentPrompter, StiffyDecoder, StiffyDebugger, ..." - }, - { - "author": "chetusangolgi", - "title": "Comfyui-supabase", - "reference": "https://github.com/chetusangolgi/Comfyui-supabase", - "files": [ - "https://github.com/chetusangolgi/Comfyui-supabase" - ], - "install_type": "git-clone", - "description": "NODES: Watch Supabase Bucket, Upload Image to Supabase" - }, - { - "author": "rickyars", - "title": "sd-cn-animation", - "reference": "https://github.com/rickyars/sd-cn-animation", - "files": [ - "https://github.com/rickyars/sd-cn-animation" - ], - "install_type": "git-clone", - "description": "SD-CN animation for Comfyui" - }, - { - "author": "daracazamea", - "title": "DCNodess [WIP]", - "reference": "https://github.com/daracazamea/comfyUI-DCNodes", - "files": [ - "https://github.com/daracazamea/comfyUI-DCNodes" - ], - "install_type": "git-clone", - "description": "NODES: Start Timer (Pass-Through), Get Generation Time, Manual Trigger, Flux: Resolution Picker, SDXL: Resolution Picker\nNOTE: The files in the repo are not organized." - }, - { - "author": "hunzmusic", - "title": "ComfyUI-Hunyuan3DTools [WIP]", - "reference": "https://github.com/hunzmusic/ComfyUI-Hunyuan3DTools", - "files": [ - "https://github.com/hunzmusic/ComfyUI-Hunyuan3DTools" - ], - "install_type": "git-clone", - "description": "NODES: Hy3DTools Render Specific View, Hy3DTools Back-Project Inpaint\nNOTE: The files in the repo are not organized." - }, - { - "author": "grokuku", - "title": "Holaf Custom Nodes for ComfyUI", - "reference": "https://github.com/grokuku/ComfyUI-Holaf", - "files": [ - "https://github.com/grokuku/ComfyUI-Holaf" - ], - "install_type": "git-clone", - "description": "NODES: Neurogrid Overload, Tile Calculator, Slice Calculator, Save Image, Tiled KSampler, KSampler, Image Comparer, Upscale, Overlay, Resolution Preset, Benchmark Runner, Benchmark Plotter, Benchmark Loader" - }, - { - "author": "Burgstall-labs", - "title": "ComfyUI-BS_FalAi-API-Video [WIP]", - "reference": "https://github.com/Burgstall-labs/ComfyUI-BS_FalAi-API-Video", - "files": [ - "https://github.com/Burgstall-labs/ComfyUI-BS_FalAi-API-Video" - ], - "install_type": "git-clone", - "description": "Experimental ComfyUI Custom Node for generating videos using various FAL AI API endpoints.\nNOTE: The files in the repo are not organized." - }, - { - "author": "uauaouau", - "title": "Mycraft [WIP]", - "reference": "https://github.com/sorption-dev/mycraft-comfyui", - "files": [ - "https://github.com/sorption-dev/mycraft-comfyui" - ], - "install_type": "git-clone", - "description": "Mycraft provides a limitless storyboard experience for image generation, powered by the ComfyUI API.\nEach container functions as an independent ComfyUI workflow, Supports workflows (text-to-text) and fine-tuning (image-to-image), Supports workflow customization." - }, - { - "author": "zhaorishuai", - "title": "ComfyUI-StoryboardDistributor", - "reference": "https://github.com/zhaorishuai/ComfyUI-StoryboardDistributor", - "files": [ - "https://github.com/zhaorishuai/ComfyUI-StoryboardDistributor" - ], - "install_type": "git-clone", - "description": "A ComfyUI plugin that automatically assigns storyboard content to 9 storyboard nodes." - }, - { - "author": "alexgenovese", - "title": "ComfyUI-Diffusion-4k [WIP]", - "reference": "https://github.com/alexgenovese/ComfyUI-Diffusion-4k", - "files": [ - "https://github.com/alexgenovese/ComfyUI-Diffusion-4k" - ], - "install_type": "git-clone", - "description": "A ComfyUI custom node implementation of the Diffusion 4K research paper.\nNOTE: The files in the repo are not organized." - }, - { - "author": "KERRY-YUAN", - "title": "Python_Executor [UNSAFE]", - "id": "PythonExecutor", - "reference": "https://github.com/KERRY-YUAN/ComfyUI_Python_Executor", - "files": [ - "https://github.com/KERRY-YUAN/ComfyUI_Python_Executor" - ], - "install_type": "git-clone", - "description": "Nodes: Provides nodes to execute arbitrary Python code snippets and Resize images directly within ComfyUI workflows. [w/This node allows you to execute arbitrary code via the workflow.]" - }, - { - "author": "ashllay", - "title": "ComfyUI_MoreComfy", - "reference": "https://github.com/ashllay/ComfyUI_MoreComfy", - "files": [ - "https://github.com/ashllay/ComfyUI_MoreComfy" - ], - "install_type": "git-clone", - "description": "NODES: MC Switch Seed, MC Alter Seed, MC Set Tile Size, MC Multi Concat" - }, - { - "author": "ayaoayaoayaoaya", - "title": "ComfyUI-KLUT-DeepSeek-API [WIP]", - "reference": "https://github.com/ayaoayaoayaoaya/ComfyUI-KLUT-DeepSeek-API", - "files": [ - "https://github.com/ayaoayaoayaoaya/ComfyUI-KLUT-DeepSeek-API" - ], - "install_type": "git-clone", - "description": "A collection of utility / quality-of-life nodes for ComfyUI. Probably only useful to me.\nNOTE: The files in the repo are not organized." - }, - { - "author": "olyyarm", - "title": "ComfyUI-VLMStudio", - "reference": "https://github.com/KurtHokke/ComfyUI_KurtHokke_Nodes", - "files": [ - "https://github.com/KurtHokke/ComfyUI_KurtHokke_Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Node_BOOL/INT/Float, BooleanToPipe, BooleanFromPipe, ExpMath, ExpMathDual/Quad, ...." - }, - { - "author": "olyyarm", - "title": "ComfyUI-VLMStudio", - "reference": "https://github.com/olyyarm/ComfyUI-VLMStudio", - "files": [ - "https://raw.githubusercontent.com/olyyarm/ComfyUI-VLMStudio/refs/heads/master/vlm_visionary_node_v3_.py" - ], - "install_type": "copy", - "description": "NODES: GemmaMultimodalAnalyzer" - }, - { - "author": "apetitbois", - "title": "nova_utils", - "reference": "https://github.com/apetitbois/nova_utils", - "files": [ - "https://github.com/apetitbois/nova_utils" - ], - "install_type": "git-clone", - "description": "Nova utils for ComfyUI" - }, - { - "author": "sugarkwork", - "title": "comfyui_my_img_util", - "reference": "https://github.com/sugarkwork/comfyui_my_img_util", - "files": [ - "https://github.com/sugarkwork/comfyui_my_img_util" - ], - "install_type": "git-clone", - "description": "NODES: Simple Image Rotate" - }, - { - "author": "sugarkwork", - "title": "comfyui-trtools", - "reference": "https://github.com/sugarkwork/comfyui-trtools", - "files": [ - "https://github.com/sugarkwork/comfyui-trtools" - ], - "install_type": "git-clone", - "description": "TensorRT Tools for ComfyUI" - }, - { - "author": "DonutsDelivery", - "title": "ComfyUI-DonutDetailer", - "reference": "https://github.com/DonutsDelivery/ComfyUI-DonutNodes", - "files": [ - "https://github.com/DonutsDelivery/ComfyUI-DonutNodes" - ], - "install_type": "git-clone", - "description": "This is an experimental node I made to mimick the 'adjust' in A1111 Supermerger [a/https://github.com/hako-mikan/sd-webui-supermerger?tab=readme-ov-file#adjust](https://github.com/hako-mikan/sd-webui-supermerger?tab=readme-ov-file#adjust)." - }, - { - "author": "ZenAI-Vietnam", - "title": "ComfyUI-gemini-IG", - "reference": "https://github.com/ZenAI-Vietnam/ComfyUI-gemini-IG", - "files": [ - "https://github.com/ZenAI-Vietnam/ComfyUI-gemini-IG" - ], - "install_type": "git-clone", - "description": "NODES: Gemini Image Generation, Gemini Text Generation" - }, - { - "author": "hunzmusic", - "title": "comfyui-hnznodes", - "reference": "https://github.com/hunzmusic/comfyui-hnznodes", - "files": [ - "https://github.com/hunzmusic/comfyui-hnznodes" - ], - "install_type": "git-clone", - "description": "NODES: Combine Channels Grayscale, Reorder Image Batch, Male Character Prompt" - }, - { - "author": "cidiro", - "title": "cid-node-pack", - "reference": "https://github.com/cidiro/cid-node-pack", - "files": [ - "https://github.com/cidiro/cid-node-pack" - ], - "install_type": "git-clone", - "description": "A lightweight nodepack for ComfyUI that adds a few handy nodes that I use in my workflows" - }, - { - "author": "CeeVeeR", - "title": "ComfyUi-Text-Tiler", - "reference": "https://github.com/CeeVeeR/ComfyUi-Text-Tiler", - "files": [ - "https://github.com/CeeVeeR/ComfyUi-Text-Tiler" - ], - "install_type": "git-clone", - "description": "NODES: Text Tiler" - }, - { - "author": "Dreamshot-io", - "title": "ComfyUI-Extend-Resolution", - "reference": "https://github.com/Dreamshot-io/ComfyUI-Extend-Resolution", - "files": [ - "https://github.com/Dreamshot-io/ComfyUI-Extend-Resolution" - ], - "install_type": "git-clone", - "description": "NODES: Resolution Padding" - }, - { - "author": "l1yongch1", - "title": "ComfyUI-YcNodes", - "reference": "https://github.com/l1yongch1/ComfyUI-YcNodes", - "files": [ - "https://github.com/l1yongch1/ComfyUI-YcNodes" - ], - "install_type": "git-clone", - "description": "NODES: RemoveHighlightAndBlur, RoundedCorners, PaddingAccordingToBackground\npersonal custom nodes for learning" - }, - { - "author": "GeekatplayStudio", - "title": "PMSnodes [WIP]", - "reference": "https://github.com/GeekatplayStudio/ComfyUI_Toolbox", - "files": [ - "https://github.com/GeekatplayStudio/ComfyUI_Toolbox" - ], - "install_type": "git-clone", - "description": "A custom nodes for ComfyUI to Load audio in Base64 format and Send Audio to Websocket in Base64 Format for creating API of Audio related AI\nNOTE: The files in the repo are not organized." - }, - { - "author": "rhinoflavored", - "title": "comfyui_QT", - "reference": "https://github.com/rhinoflavored/comfyui_QT", - "files": [ - "https://github.com/rhinoflavored/comfyui_QT" - ], - "install_type": "git-clone", - "description": "bunch of image manipulation nodes....\nNOTE: The files in the repo are not organized." - }, - { - "author": "ricklove", - "title": "ComfyUI-AutoSeg-SAM2", - "reference": "https://github.com/ricklove/ComfyUI-AutoSeg-SAM2", - "files": [ - "https://github.com/ricklove/ComfyUI-AutoSeg-SAM2" - ], - "install_type": "git-clone", - "description": "NODES: AutoSeg-SAM2 Batch Segmentation" - }, - { - "author": "JoeAu", - "title": "ComfyUI-PythonNode [UNSAFE]", - "reference": "https://github.com/JoeAu/ComfyUI-PythonNode", - "files": [ - "https://github.com/JoeAu/ComfyUI-PythonNode" - ], - "install_type": "git-clone", - "description": "A custom ComfyUI node that allows users to execute arbitrary Python code with a single input (value) and output (result), enabling flexible processing of the input value using any Python code before assigning the final result to result. It also captures print() output and exceptions for debugging.[w/This node is an unsafe node that includes the capability to execute arbitrary python script.]" - }, - { - "author": "smthemex", - "title": "ComfyUI_GPT_SoVITS_Lite", - "reference": "https://github.com/smthemex/ComfyUI_GPT_SoVITS_Lite", - "files": [ - "https://github.com/smthemex/ComfyUI_GPT_SoVITS_Lite" - ], - "install_type": "git-clone", - "description": "[a/GPT_SoVITS](https://github.com/RVC-Boss/GPT-SoVITS) infer only for ComfyUI users\nNOTE: The files in the repo are not organized." - }, - { - "author": "Nambi24", - "title": "ComfyUI-Save_Image", - "reference": "https://github.com/Nambi24/ComfyUI-Save_Image", - "files": [ - "https://github.com/Nambi24/ComfyUI-Save_Image" - ], - "description": "NODES: Save Image With Subfolder, Extract Last Path Component\nNOTE: The files in the repo are not organized.", - "install_type": "git-clone" - }, - { - "author": "sugarkwork", - "title": "comfyui_image_crop", - "reference": "https://github.com/sugarkwork/comfyui_image_crop", - "files": [ - "https://github.com/sugarkwork/comfyui_image_crop" - ], - "description": "NODES: CropTransparent, RestoreCrop, ExpandMultiple, CropReapply", - "install_type": "git-clone" - }, - { - "author": "AkiEvansDev", - "title": "ComfyUI-Tools", - "reference": "https://github.com/AkiEvansDev/ComfyUI-Tools", - "files": [ - "https://github.com/AkiEvansDev/ComfyUI-Tools" - ], - "install_type": "git-clone", - "description": "Custom nodes for basic actions." - }, - { - "author": "longzoho", - "title": "ComfyUI-Qdrant-Saver", - "reference": "https://github.com/longzoho/ComfyUI-Qdrant-Saver", - "files": [ - "https://github.com/longzoho/ComfyUI-Qdrant-Saver" - ], - "install_type": "git-clone", - "description": "NODES: QDrant Saver Node" - }, - { - "author": "RUFFY-369", - "title": "ComfyUI-FeatureBank", - "reference": "https://github.com/RUFFY-369/ComfyUI-FeatureBank", - "files": [ - "https://github.com/RUFFY-369/ComfyUI-FeatureBank" - ], - "install_type": "git-clone", - "description": "NODES: FeatureBankAttentionProcessor" - }, - { - "author": "Pablerdo", - "title": "ComfyUI-Sa2VAWrapper [WIP]", - "reference": "https://github.com/Pablerdo/ComfyUI-Sa2VAWrapper", - "files": [ - "https://github.com/Pablerdo/ComfyUI-Sa2VAWrapper" - ], - "install_type": "git-clone", - "description": "Wrapper for the Sa2VA model" - }, - { - "author": "aria1th", - "title": "ComfyUI-camietagger-onnx", - "reference": "https://github.com/aria1th/ComfyUI-camietagger-onnx", - "files": [ - "https://github.com/aria1th/ComfyUI-camietagger-onnx" - ], - "install_type": "git-clone", - "description": "NODES: Camie Tagger" - }, - { - "author": "zjkhurry", - "title": "comfyui_MetalFX [WIP]", - "reference": "https://github.com/zjkhurry/comfyui_MetalFX", - "files": [ - "https://github.com/zjkhurry/comfyui_MetalFX" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that enables high-quality image and video upscaling using Apple MetalFX technology.\nNOTE: The files in the repo are not organized." - }, - { - "author": "RoyKillington", - "title": "Miscomfy Nodes [WIP]", - "reference": "https://github.com/RoyKillington/miscomfy-nodes", - "files": [ - "https://github.com/RoyKillington/miscomfy-nodes" - ], - "install_type": "git-clone", - "description": "A repo of custom nodes for ComfyUI, from interacting with certain APIs to whatever other miscellanea I end up making" - }, - { - "author": "Elypha", - "title": "ComfyUI-Prompt-Helper [WIP]", - "reference": "https://github.com/Elypha/ComfyUI-Prompt-Helper", - "files": [ - "https://github.com/Elypha/ComfyUI-Prompt-Helper" - ], - "install_type": "git-clone", - "description": "Concat conditions and prompts for ComfyUI" - }, - { - "author": "StoryWalker", - "title": "comfyui_flux_collection_advanced [WIP]", - "reference": "https://github.com/StoryWalker/comfyui_flux_collection_advanced", - "files": [ - "https://github.com/StoryWalker/comfyui_flux_collection_advanced" - ], - "install_type": "git-clone", - "description": "This is a collection focused in give a little more flexibility in the use of Flux models." - }, - { - "author": "OSAnimate", - "title": "ComfyUI-SpriteSheetMaker [WIP]", - "reference": "https://github.com/OSAnimate/ComfyUI-SpriteSheetMaker", - "files": [ - "https://github.com/OSAnimate/ComfyUI-SpriteSheetMaker" - ], - "install_type": "git-clone", - "description": "The sprite sheet maker node is a simple way to create sprite sheets and image grids.\nNOTE: The files in the repo are not organized." - }, - { - "author": "BuffMcBigHuge", - "title": "ComfyUI-Buff-Nodes [WIP]", - "reference": "https://github.com/BuffMcBigHuge/ComfyUI-Buff-Nodes", - "files": [ - "https://github.com/BuffMcBigHuge/ComfyUI-Buff-Nodes" - ], - "install_type": "git-clone", - "description": "Several quality-of-life batch operation and string manipulation nodes." - }, - { - "author": "ritikvirus", - "title": "ComfyUI Terminal Command Node [UNSAFE]", - "reference": "https://github.com/ritikvirus/comfyui-terminal-modal-node", - "files": [ - "https://github.com/ritikvirus/comfyui-terminal-modal-node" - ], - "install_type": "git-clone", - "description": "This repository provides a custom ComfyUI node that lets you execute arbitrary terminal commands directly from the ComfyUI interface. [w/This extension allows remote command execution.]" - }, - { - "author": "pixuai", - "title": "ComfyUI-PixuAI", - "reference": "https://github.com/pixuai/ComfyUI-PixuAI", - "files": [ - "https://github.com/pixuai/ComfyUI-PixuAI" - ], - "install_type": "git-clone", - "description": "A collection of ComfyUI nodes designed to streamline prompt creation, organization, and discovery - making your workflows faster and more intuitive." - }, - { - "author": "techidsk", - "title": "comfyui_molook_nodes [WIP]", - "reference": "https://github.com/techidsk/comfyui_molook_nodes", - "files": [ - "https://github.com/techidsk/comfyui_molook_nodes" - ], - "install_type": "git-clone", - "description": "Some extra nodes" - }, - { - "author": "Northerner1", - "title": "ComfyUI_North_Noise [WIP]", - "reference": "https://github.com/Northerner1/ComfyUI_North_Noise", - "files": [ - "https://github.com/Northerner1/ComfyUI_North_Noise" - ], - "install_type": "git-clone", - "description": "NODES: North Noise" - }, - { - "author": "ManuShamil", - "title": "ComfyUI_BodyEstimation_Nodes", - "reference": "https://github.com/ManuShamil/ComfyUI_BodyEstimation_Nodes", - "files": [ - "https://github.com/ManuShamil/ComfyUI_BodyEstimation_Nodes" - ], - "install_type": "git-clone", - "description": "NODES: CogitareLabsPoseIDExtractor" - }, - { - "author": "MockbaTheBorg", - "title": "ComfyUI-Mockba", - "reference": "https://github.com/MockbaTheBorg/ComfyUI-Mockba", - "files": [ - "https://github.com/MockbaTheBorg/ComfyUI-Mockba" - ], - "install_type": "git-clone", - "description": "NODES: Image Batch/Flip/Rotate/Subtract/Dither, Barcode, Select, ..." - }, - { - "author": "jcomeme", - "title": "AsunaroTools", - "reference": "https://github.com/jcomeme/ComfyUI-AsunaroTools", - "files": [ - "https://github.com/jcomeme/ComfyUI-AsunaroTools" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI" - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "ComfyUI Wan2.1 [WIP]", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Wan-ZHO", - "files": [ - "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Wan-ZHO" - ], - "install_type": "git-clone", - "description": "It’s estimated that ComfyUI itself will support it soon, so go ahead and give it a try!" - }, - { - "author": "ltdrdata", - "title": "comfyui-unsafe-torch [UNSAFE]", - "reference": "https://github.com/ltdrdata/comfyui-unsafe-torch", - "files": [ - "https://github.com/ltdrdata/comfyui-unsafe-torch" - ], - "install_type": "git-clone", - "description": "disable torch.load's `weigths_only`" - }, - { - "author": "muvich3n", - "title": "ComfyUI-Crop-Border", - "reference": "https://github.com/muvich3n/ComfyUI-Crop-Border", - "files": [ - "https://github.com/muvich3n/ComfyUI-Crop-Border" - ], - "install_type": "git-clone", - "description": "NODES: Crop Image Borders" - }, - { - "author": "masmullin2000", - "title": "ComfyUI-MMYolo", - "reference": "https://github.com/masmullin2000/ComfyUI-MMYolo", - "files": [ - "https://github.com/masmullin2000/ComfyUI-MMYolo" - ], - "install_type": "git-clone", - "description": "A comfy node to find faces and output a mask" - }, - { - "author": "Yeonri", - "title": "ComfyUI_LLM_Are_You_Listening [WIP]", - "reference": "https://github.com/Yeonri/ComfyUI_LLM_Are_You_Listening", - "files": [ - "https://github.com/Yeonri/ComfyUI_LLM_Are_You_Listening" - ], - "install_type": "git-clone", - "description": "NODES: AYL_Node, AYL_GGUF_Node, AYL_API_Node\nNOTE: The files in the repo are not organized." - }, - { - "author": "altkeyproject", - "title": "Dream Painter [WIP]", - "reference": "https://github.com/alt-key-project/comfyui-dream-painter", - "files": [ - "https://github.com/alt-key-project/comfyui-dream-painter" - ], - "install_type": "git-clone", - "description": "Provide utilities for 2D image generation and processing." - }, - { - "author": "kimara-ai", - "title": "ComfyUI-Kimara-AI-Image-From-URL [WIP]", - "reference": "https://github.com/kimara-ai/ComfyUI-Kimara-AI-Image-From-URL", - "files": [ - "https://github.com/kimara-ai/ComfyUI-Kimara-AI-Image-From-URL" - ], - "install_type": "git-clone", - "description": "Load image from URL and downscale to desired megapixels. Set megapixels to 0 for no downscaling." - }, - { - "author": "krisshen2021", - "title": "comfyui_OpenRouterNodes [WIP]", - "reference": "https://github.com/krisshen2021/comfyui_OpenRouterNodes", - "files": [ - "https://github.com/krisshen2021/comfyui_OpenRouterNodes" - ], - "install_type": "git-clone", - "description": "LLM custom nodes for comfyui\nNOTE: The files in the repo are not organized." - }, - { - "author": "Velour-Fog", - "title": "comfy-latent-nodes [UNSAFE]", - "reference": "https://github.com/Velour-Fog/comfy-latent-nodes", - "files": [ - "https://github.com/Velour-Fog/comfy-latent-nodes" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes to save and load a latent to a specified directory. Saves time for doing operations on a latent such as upscaling without having to re-trigger the creation of the original latent.[w/This node can write files to an arbitrary path.]" - }, - { - "author": "jgbyte", - "title": "ComfyUI-RandomCube [WIP]", - "reference": "https://github.com/jgbyte/ComfyUI-RandomCube", - "files": [ - "https://github.com/jgbyte/ComfyUI-RandomCube" - ], - "install_type": "git-clone", - "description": "NODES: RandomCubeGrid" - }, - { - "author": "thot-experiment", - "title": "comfy-live-preview [WIP]", - "reference": "https://github.com/thot-experiment/comfy-live-preview", - "files": [ - "https://github.com/thot-experiment/comfy-live-preview" - ], - "install_type": "git-clone", - "description": "external live preview plugin for ComfyUI" - }, - { - "author": "AhBumm", - "title": "ComfyUI-Upscayl", - "reference": "https://github.com/AhBumm/ComfyUI-Upscayl", - "files": [ - "https://github.com/AhBumm/ComfyUI-Upscayl" - ], - "nodename_pattern": "\\(BillBum\\)$", - "install_type": "git-clone", - "description": "NODES: Upscayl Upscaler" - }, - { - "author": "NEZHA625", - "title": "ComfyUI-tools-by-dong [UNSAFE]", - "reference": "https://github.com/NEZHA625/ComfyUI-tools-by-dong", - "files": [ - "https://github.com/NEZHA625/ComfyUI-tools-by-dong" - ], - "install_type": "git-clone", - "description": "NODES: HuggingFaceUploadNode, ImageDownloader, LoraIterator, FileMoveNode, InputDetectionNode, ...\nNOTE: The files in the repo are not organized.[w/This nodepack includes nodes that can modify arbitrary files.]" - }, - { - "author": "if-ai", - "title": "ComfyUI-IF_Zonos [WIP]", - "reference": "https://github.com/if-ai/ComfyUI-IF_Zonos", - "files": [ - "https://github.com/if-ai/ComfyUI-IF_Zonos" - ], - "install_type": "git-clone", - "description": "Zonos for ComfyUI" - }, - { - "author": "grinlau18", - "title": "Xiser_Nodes [WIP]", - "reference": "https://github.com/grinlau18/ComfyUI_XISER_Nodes", - "files": [ - "https://github.com/grinlau18/ComfyUI_XISER_Nodes" - ], - "install_type": "git-clone", - "description": "Custom nodes for customizing workflows\nNOTE: The files in the repo are not organized." - }, - { - "author": "LAOGOU-666", - "title": "Comfyui_StartPatch [UNSAFE]", - "reference": "https://github.com/LAOGOU-666/Comfyui_StartPatch", - "files": [ - "https://github.com/LAOGOU-666/Comfyui_StartPatch" - ], - "install_type": "git-clone", - "description": "This patch plugin optimizes the node information processing mechanism of the ComfyUI server, significantly improving server performance and response speed. It greatly reduces the browser page initialization waiting time. [w/Since this patch modifies key functions of ComfyUI, it is highly likely to cause compatibility issues.]" - }, - { - "author": "badmike", - "title": "Prompt Factory [CONFLICT]", - "reference": "https://github.com/badmike/comfyui-prompt-factory", - "files": [ - "https://github.com/badmike/comfyui-prompt-factory" - ], - "install_type": "git-clone", - "description": "A modular system that adds randomness to prompt generation [w/This nodepack is causing a name conflict with https://github.com/satche/comfyui-prompt-factory]" - }, - { - "author": "owengillett", - "title": "ComfyUI-tilefusion", - "reference": "https://github.com/owengillett/ComfyUI-tilefusion", - "files": [ - "https://github.com/owengillett/ComfyUI-tilefusion" - ], - "install_type": "git-clone", - "description": "Helper nodes for generating seamless tiles." - }, - { - "author": "Scaryplasmon", - "title": "ComfTrellis [WIP]", - "reference": "https://github.com/Scaryplasmon/ComfTrellis", - "files": [ - "https://github.com/Scaryplasmon/ComfTrellis" - ], - "install_type": "git-clone", - "description": "1 click install to run Trellis in ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "fangziheng2321", - "title": "comfyuinode_chopmask [WIP]", - "reference": "https://github.com/fangziheng2321/comfyuinode_chopmask", - "files": [ - "https://github.com/fangziheng2321/comfyuinode_chopmask" - ], - "install_type": "git-clone", - "description": "a custom comfyui node for '/fooocusinpaint_upload'\nNOTE: The files in the repo are not organized." - }, - { - "author": "D1-3105", - "title": "ComfyUI-VideoStream", - "reference": "https://github.com/D1-3105/ComfyUI-VideoStream", - "files": [ - "https://github.com/D1-3105/ComfyUI-VideoStream" - ], - "install_type": "git-clone", - "description": "NODES: FloWWeaverExportSingleFrameGRPC" - }, - { - "author": "wirytiox", - "title": "ComfyUI-Qwen [CONFLICT]", - "reference": "https://github.com/mr-krak3n/ComfyUI-Qwen", - "files": [ - "https://github.com/mr-krak3n/ComfyUI-Qwen" - ], - "install_type": "git-clone", - "description": "This repository contains custom nodes for ComfyUI, designed to facilitate working with language models such as Qwen2.5 and DeepSeek. [w/This nodepack is causing a name conflict with https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen]" - }, - { - "author": "hiusdev", - "title": "ComfyUI_Lah_Toffee", - "reference": "https://github.com/hiusdev/ComfyUI_Lah_Toffee", - "files": [ - "https://github.com/hiusdev/ComfyUI_Lah_Toffee" - ], - "install_type": "git-clone", - "description": "NODES: Lah LoadVideoRandom" - }, - { - "author": "hdfhssg", - "title": "ComfyUI_pxtool [WIP]", - "reference": "https://github.com/hdfhssg/ComfyUI_pxtool", - "files": [ - "https://github.com/hdfhssg/ComfyUI_pxtool" - ], - "install_type": "git-clone", - "description": "This is a custom plugin node for ComfyUI that modifies and extends some features from existing projects. The main implementations include:\n* Reproducing some features of the [a/Stable-Diffusion-Webui-Civitai-Helper](https://github.com/zixaphir/Stable-Diffusion-Webui-Civitai-Helper) project within ComfyUI\n* Implementing a feature to randomly generate related prompt words by referencing the [a/noob-wiki dataset](https://huggingface.co/datasets/Laxhar/noob-wiki/tree/main)\nNOTE: The files in the repo are not organized." - }, - { - "author": "franky519", - "title": "comfyui-redux-style", - "reference": "https://github.com/franky519/comfyui-redux-style", - "files": [ - "https://github.com/franky519/comfyui-redux-style" - ], - "install_type": "git-clone", - "description": "NODES: Style Model Grid, Style Model Apply, Style Model Advanced" - }, - { - "author": "rishipandey125", - "title": "ComfyUI-FramePacking [WIP]", - "reference": "https://github.com/rishipandey125/ComfyUI-FramePacking", - "files": [ - "https://github.com/rishipandey125/ComfyUI-FramePacking" - ], - "install_type": "git-clone", - "description": "NODES: Add Grid Boundaries, Pack Frames, Unpack Frames, Resize Frame" - }, - { - "author": "Northerner1", - "title": "ComfyUI_North_Noise [WIP]", - "reference": "https://github.com/Northerner1/ComfyUI_North_Noise", - "files": [ - "https://github.com/Northerner1/ComfyUI_North_Noise" - ], - "install_type": "git-clone", - "description": "NODES: Unsampler" - }, - { - "author": "kimara-ai", - "title": "ComfyUI-Kimara-AI-Image-From-URL [WIP]", - "reference": "https://github.com/kimara-ai/ComfyUI-Kimara-AI-Image-From-URL", - "files": [ - "https://github.com/kimara-ai/ComfyUI-Kimara-AI-Image-From-URL" - ], - "install_type": "git-clone", - "description": "Load image from URL and downscale to desired megapixels. Set megapixels to 0 for no downscaling." - }, - { - "author": "tc8M4lF3s88", - "title": "comfy-tif-support", - "reference": "https://github.com/M4lF3s/comfy-tif-support", - "files": [ - "https://github.com/M4lF3s/comfy-tif-support" - ], - "install_type": "git-clone", - "description": "NODES: Load TIFF" - }, - { - "author": "tc888", - "title": "ComfyUI_Save_Flux_Image", - "reference": "https://github.com/tc888/ComfyUI_Save_Flux_Image", - "files": [ - "https://github.com/tc888/ComfyUI_Save_Flux_Image" - ], - "install_type": "git-clone", - "description": "Customized version of comfyui-image-save tailored for saving Flux images" - }, - { - "author": "xinyiSS", - "title": "CombineMasksNode", - "reference": "https://github.com/xinyiSS/CombineMasksNode", - "files": [ - "https://github.com/xinyiSS/CombineMasksNode" - ], - "install_type": "git-clone", - "description": "NODES: Combine Masks Node" - }, - { - "author": "osuiso-depot", - "title": "comfyui-keshigom_custom", - "reference": "https://github.com/osuiso-depot/comfyui-keshigom_custom", - "files": [ - "https://github.com/osuiso-depot/comfyui-keshigom_custom" - ], - "install_type": "git-clone", - "description": "NODES: RegexpChopper, FLIP-W/H Selector, FLIP-W/H SelectorConst, TextFind, ckpt_Loader_Simple, True-or-False, myStringNode" - }, - { - "author": "LucipherDev", - "title": "ComfyUI-Sentinel [WIP]", - "reference": "https://github.com/LucipherDev/ComfyUI-Sentinel", - "files": [ - "https://github.com/LucipherDev/ComfyUI-Sentinel" - ], - "install_type": "git-clone", - "description": "ComfyUI Extension for Advanced Security. Implements login, multi-user registration, IP filtering, and user-specific input/output directories.[w/WARN:While ComfyUI Sentinel enhances security for ComfyUI, it does not guarantee absolute protection. Security is about risk mitigation, not elimination. Users are responsible for implementing their own security measures.]" - }, - { - "author": "threadedblue", - "title": "MLXnodes [WIP]", - "reference": "https://github.com/threadedblue/MLXnodes", - "files": [ - "https://github.com/threadedblue/MLXnodes" - ], - "install_type": "git-clone", - "description": "A port of MLX Examples to ComfyUI custom_nodes. These are intended to run on a macOS M1.\nNOTE: The files in the repo are not organized." - }, - { - "author": "jschoormans", - "title": "Comfy-InterestingPixels [WIP]", - "reference": "https://github.com/jschoormans/Comfy-InterestingPixels", - "files": [ - "https://github.com/jschoormans/Comfy-InterestingPixels" - ], - "install_type": "git-clone", - "description": "NODES: Shareable Image Slider, Random Palette\nNOTE: The files in the repo are not organized." - }, - { - "author": "jschoormans", - "title": "ComfyUI-TexturePacker [WIP]", - "reference": "https://github.com/kijai/ComfyUI-TexturePacker", - "files": [ - "https://github.com/jschoormans/Comfy-InterestingPixels" - ], - "install_type": "git-clone", - "description": "ComfyUI node to use PyTexturePacker\nNOTE: The files in the repo are not organized." - }, - { - "author": "MickeyJ", - "title": "ComfyUI_mickster_nodes [WIP]", - "reference": "https://github.com/MickeyJ/ComfyUI_mickster_nodes", - "files": [ - "https://github.com/MickeyJ/ComfyUI_mickster_nodes" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI, focusing on image handling and LoRA training." - }, - { - "author": "gold24park", - "title": "loki-comfyui-node", - "reference": "https://github.com/gold24park/loki-comfyui-node", - "files": [ - "https://github.com/gold24park/loki-comfyui-node" - ], - "install_type": "git-clone", - "description": "NODES: Get Image Luminance, Get Dominant Color, Overlay Text" - }, - { - "author": "hayden-fr", - "title": "ComfyUI-Image-Browsing [USAFE]", - "id": "image-browsing", - "reference": "https://github.com/hayden-fr/ComfyUI-Image-Browsing", - "files": [ - "https://github.com/hayden-fr/ComfyUI-Image-Browsing" - ], - "install_type": "git-clone", - "description": "Image Browsing: browsing, download and delete." - }, - { - "author": "molbal", - "title": "comfy-url-fetcher [WIP]", - "reference": "https://github.com/molbal/comfy-url-fetcher", - "files": [ - "https://github.com/molbal/comfy-url-fetcher" - ], - "install_type": "git-clone", - "description": "Fetches URLs" - }, - { - "author": "neverbiasu", - "title": "ComfyUI_Output_as_Input", - "reference": "https://github.com/a-und-b/ComfyUI_Output_as_Input", - "files": [ - "https://github.com/a-und-b/ComfyUI_Output_as_Input" - ], - "install_type": "git-clone", - "description": "This is a simple custom ComfyUI node that allows you to easily use recent output images as input in your workflows. It does not allow image uploads on purpose and does not require any additional dependencies.\nNOTE: The files in the repo are not organized." - }, - { - "author": "neverbiasu", - "title": "ComfyUI-DeepSeek", - "reference": "https://github.com/neverbiasu/ComfyUI-DeepSeek", - "files": [ - "https://github.com/neverbiasu/ComfyUI-DeepSeek" - ], - "install_type": "git-clone", - "description": "NODES: DeepSeek Caller" - }, - { - "author": "Krish-701", - "title": "RK_Comfyui", - "reference": "https://github.com/Krish-701/RK_Comfyui", - "files": [ - "https://github.com/Krish-701/RK_Comfyui" - ], - "install_type": "git-clone", - "description": "NODES: RK Excel File State Looper, RK Accumulate Text, RK Advanced Script Finder, RK CSV File State Looper, RK Read Excel Row, RK Sequential Image Viewer, RK Concatenate Text, RK Write Text, RK Save Image, RK Seed Loop" - }, - { - "author": "Kayarte", - "title": "Time Series Nodes for ComfyUI [Experimental]", - "reference": "https://github.com/Kayarte/Time-Series-Nodes-for-ComfyUI", - "files": [ - "https://github.com/Kayarte/Time-Series-Nodes-for-ComfyUI" - ], - "install_type": "git-clone", - "description": "Basic nodes for time series analysis in ComfyUI. Currently in early development." - }, - { - "author": "comfyuiblog", - "title": "deepseek_prompt_generator_comfyui [WIP]", - "reference": "https://github.com/comfyuiblog/deepseek_prompt_generator_comfyui", - "files": [ - "https://github.com/comfyuiblog/deepseek_prompt_generator_comfyui" - ], - "install_type": "git-clone", - "description": "Prompt Expansion for Stable Diffusion, using Deepseek API.\nNOTE: The files in the repo are not organized." - }, - { - "author": "risunobushi", - "title": "ComfyUI_HEXtoRGB", - "reference": "https://github.com/risunobushi/ComfyUI_HEXtoRGB", - "files": [ - "https://github.com/risunobushi/ComfyUI_HEXtoRGB" - ], - "install_type": "git-clone", - "description": "NODES: Hex to RGB Converter" - }, - { - "author": "EmanueleUniroma2", - "title": "ComfyUI-FLAC-to-WAV [WIP]", - "reference": "https://github.com/EmanueleUniroma2/ComfyUI-FLAC-to-WAV", - "files": [ - "https://github.com/EmanueleUniroma2/ComfyUI-FLAC-to-WAV" - ], - "install_type": "git-clone", - "description": "A custom node to convert flac files to wav inside comfy UI\nComfyUI Custom Node: FLAC to WAV Converter Welcome to the ComfyUI Custom Node: FLAC to WAV Converter repository! This project provides a custom node for ComfyUI that allows you to easily convert .flac audio files to .wav format, making it simpler to work with a variety of audio tools and applications.\nNOTE: The files in the repo are not organized." - }, - { - "author": "eyekayem", - "title": "comfyui_runway_gen3", - "reference": "https://github.com/eyekayem/comfyui_runway_gen3", - "files": [ - "https://github.com/eyekayem/comfyui_runway_gen3" - ], - "install_type": "git-clone", - "description": "NODES: Runway Video Gen, Runway Video Preview" - }, - { - "author": "StartHua", - "title": "Comfyui_CXH_joy_caption [SECURITY SCREENING]", - "reference": "https://github.com/StartHua/Comfyui_CXH_joy_caption", - "files": [ - "https://github.com/StartHua/Comfyui_CXH_joy_caption" - ], - "install_type": "git-clone", - "description": "Nodes:Joy_caption_load, Joy_caption\nNOTE:This nodepack has been transitioned to a security screening status due to policy." - }, - { - "author": "kijai", - "title": "ComfyUI-ComfyUI-Hunyuan3DWrapper [WIP]", - "reference": "https://github.com/kijai/ComfyUI-Hunyuan3DWrapper", - "files": [ - "https://github.com/kijai/ComfyUI-Hunyuan3DWrapper" - ], - "install_type": "git-clone", - "description": "Wrapper nodes for https://github.com/Tencent/Hunyuan3D-2, additional installation steps needed, please check the github repository" - }, - { - "author": "7BEII", - "title": "comfyui-promptbymood [WIP]", - "reference": "https://github.com/7BEII/Comfyui_PDuse", - "files": [ - "https://github.com/7BEII/Comfyui_PDuse" - ], - "install_type": "git-clone", - "description": "NODES: PD_json_group_fontsize, PD_Incremental_JSON, PD_removeword, PD_Image Crop Location, PD_ImageConcanate, PD_FileName_refixer\nNOTE: The files in the repo are not organized." - }, - { - "author": "RLW-Chars", - "title": "comfyui-promptbymood [WIP]", - "reference": "https://github.com/RLW-Chars/comfyui-promptbymood", - "files": [ - "https://github.com/RLW-Chars/comfyui-promptbymood" - ], - "install_type": "git-clone", - "description": "A plugin for ComfyUI to create random prompts.\nNOTE: The files in the repo are not organized." - }, - { - "author": "mohamedsobhi777", - "title": "ComfyUI-FramerComfy [WIP]", - "reference": "https://github.com/mohamedsobhi777/ComfyUI-FramerComfy", - "files": [ - "https://github.com/mohamedsobhi777/ComfyUI-FramerComfy" - ], - "install_type": "git-clone", - "description": "NODES: FramerComfy Input String/Number/Image/Float/Boolean/Image, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "naderzare", - "title": "comfyui-inodes", - "reference": "https://github.com/naderzare/comfyui-inodes", - "files": [ - "https://github.com/naderzare/comfyui-inodes" - ], - "install_type": "git-clone", - "description": "NODES: If-Else, Multiline Split, Azure AI API" - }, - { - "author": "sizzlebop", - "title": "ComfyUI LLM Prompt Enhancer [WIP]", - "reference": "https://github.com/pinkpixel-dev/comfyui-llm-prompt-enhancer", - "files": [ - "https://github.com/pinkpixel-dev/comfyui-llm-prompt-enhancer" - ], - "install_type": "git-clone", - "description": "A ComfyUI node for enhancing prompts using various LLM providers\nNOTE: The files in the repo are not organized." - }, - { - "author": "a-One-Fan", - "title": "ComfyUI-Blenderesque-Nodes [WIP]", - "reference": "https://github.com/a-One-Fan/ComfyUI-Blenderesque-Nodes", - "files": [ - "https://github.com/a-One-Fan/ComfyUI-Blenderesque-Nodes" - ], - "install_type": "git-clone", - "description": "Blender-like nodes for ComfyUI." - }, - { - "author": "yanhuifair", - "title": "comfyui-deepseek [WIP]", - "reference": "https://github.com/yanhuifair/comfyui-deepseek", - "files": [ - "https://github.com/yanhuifair/comfyui-deepseek" - ], - "install_type": "git-clone", - "description": "nodes for deepseek api\nNOTE: The files in the repo are not organized." - }, - { - "author": "IfnotFr", - "title": "ComfyUI-Ifnot-Pack", - "reference": "https://github.com/IfnotFr/ComfyUI-Ifnot-Pack", - "files": [ - "https://github.com/IfnotFr/ComfyUI-Ifnot-Pack" - ], - "install_type": "git-clone", - "description": "NODES: Face Crop, [w/A pack of custom nodes used in my projects. Not intended to be used by other persons as the usage is not documented. But if something interests you in this repository, go for it !]" - }, - { - "author": "KihongK", - "title": "ComfyUI-RoysNodes [WIP]", - "reference": "https://github.com/KihongK/comfyui-roysnodes", - "files": [ - "https://github.com/KihongK/comfyui-roysnodes" - ], - "install_type": "git-clone", - "description": "WIP custom nodes for Creation of AI images & videos" - }, - { - "author": "catboxanon", - "title": "ComfyUI-Pixelsmith [WIP]", - "reference": "https://github.com/catboxanon/ComfyUI-Pixelsmith", - "files": [ - "https://github.com/catboxanon/ComfyUI-Pixelsmith" - ], - "install_type": "git-clone", - "description": "NODES: Pixelsmith" - }, - { - "author": "hunterssl", - "title": "ComfyUI_SSLNodes", - "reference": "https://github.com/hunterssl/ComfyUI_SSLNodes", - "files": [ - "https://github.com/hunterssl/ComfyUI_SSLNodes" - ], - "install_type": "git-clone", - "description": "NODES: SSL Load Json, SSL Get Json Keys Count, SSL Load Checkpoint By Name, SSL Random Num In Loop, SSL Save Image Outside" - }, - { - "author": "ammahmoudi", - "title": "ComfyUI-Legendary-Nodes", - "reference": "https://github.com/ammahmoudi/ComfyUI-Legendary-Nodes", - "files": [ - "https://github.com/ammahmoudi/ComfyUI-Legendary-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Legendary Lora URL Loader, Legendary Lora URL Loader" - }, - { - "author": "yichengup", - "title": "Comfyui-NodeSpark", - "reference": "https://github.com/yichengup/Comfyui-NodeSpark", - "files": [ - "https://github.com/yichengup/Comfyui-NodeSpark" - ], - "install_type": "git-clone", - "description": "NODES: Image Circle Warp, Image Stretch, Image Wave Warp, Liquify Effect" - }, - { - "author": "kijai", - "title": "ComfyUI-VideoNoiseWarp [WIP]", - "reference": "https://github.com/kijai/ComfyUI-VideoNoiseWarp", - "files": [ - "https://github.com/kijai/ComfyUI-VideoNoiseWarp" - ], - "install_type": "git-clone", - "description": "Nodes to generate noise from video for [a/https://github.com/Eyeline-Research/Go-with-the-Flow](https://github.com/Eyeline-Research/Go-with-the-Flow)" - }, - { - "author": "muvich3n", - "title": "ComfyUI-Claude-I2T", - "reference": "https://github.com/muvich3n/ComfyUI-Claude-I2T", - "files": [ - "https://github.com/muvich3n/ComfyUI-Claude-I2T" - ], - "install_type": "git-clone", - "description": "NODES: Claude Image to Prompt Generator" - }, - { - "author": "maekawataiki", - "title": "ComfyUI-ALB-Login", - "reference": "https://github.com/maekawataiki/ComfyUI-ALB-Login", - "files": [ - "https://github.com/maekawataiki/ComfyUI-ALB-Login" - ], - "install_type": "git-clone", - "description": "Auth library to inspect token provided by ALB to protect ComfyUI." - }, - { - "author": "ArmandAlbert", - "title": "Kwai_font_comfyui", - "reference": "https://github.com/ArmandAlbert/Kwai_font_comfyui", - "files": [ - "https://github.com/ArmandAlbert/Kwai_font_comfyui" - ], - "install_type": "git-clone", - "description": "NODES: Kwaifont_Resnet50_Runner, Kwaifont_Resnet50_Loader, Kwaifont_Resnet101_Runner, Kwaifont_Resnet101_Loader, Kwaifont_Image_Cropper" - }, - { - "author": "PATATAJEC", - "title": "Patatajec-Nodes [WIP]", - "reference": "https://github.com/PATATAJEC/ComfyUI-PatatajecNodes", - "files": [ - "https://github.com/PATATAJEC/ComfyUI-PatatajecNodes" - ], - "install_type": "git-clone", - "description": "NODES: Path Tool, Color Match Falloff, Sequence Content Zoom, Sequence Blend, Color Picker" - }, - { - "author": "sourceful-official", - "title": "comfyui-sourceful-official", - "reference": "https://github.com/sourceful-official/comfyui-sourceful-official", - "files": [ - "https://github.com/sourceful-official/comfyui-sourceful-official" - ], - "description": "NODES: SourcefulOfficialComfyuiIncontextThreePanels, FalFluxLoraSourcefulOfficial, FalIcLightV2SourcefulOfficial", - "install_type": "git-clone" - }, - { - "author": "Alvaroeai", - "title": "ComfyUI-SunoAI-Mds", - "reference": "https://github.com/Alvaroeai/ComfyUI-SunoAI-Mds", - "files": [ - "https://github.com/Alvaroeai/ComfyUI-SunoAI-Mds" - ], - "install_type": "git-clone", - "description": "NODES: Suno Generate, Suno Download, Suno Proxy Generate, Suno Proxy Download" - }, - { - "author": "parmarjh", - "title": "ComfyUI-MochiWrapper-I2V [WIP]", - "reference": "https://github.com/parmarjh/ComfyUI-MochiWrapper-I2V", - "files": [ - "https://github.com/parmarjh/ComfyUI-MochiWrapper-I2V" - ], - "install_type": "git-clone", - "description": "ComfyUI wrapper nodes for [a/Mochi](https://github.com/genmoai/models) video generator" - }, - { - "author": "Symbiomatrix", - "title": "Comfyui-Sort-Files", - "reference": "https://github.com/Symbiomatrix/Comfyui-Sort-Files", - "files": [ - "https://github.com/Symbiomatrix/Comfyui-Sort-Files" - ], - "install_type": "git-clone", - "description": "Monkeypatch file sort to date modified or custom instead of lexicographic." - }, - { - "author": "x3bits", - "title": "ComfyUI-Power-Flow [UNSAFE]", - "reference": "https://github.com/x3bits/ComfyUI-Power-Flow", - "files": [ - "https://github.com/x3bits/ComfyUI-Power-Flow" - ], - "install_type": "git-clone", - "description": "A ComfyUI nodepackage that introduces common programming logic to enhance the flexibility of ComfyUI workflows. It supports features such as function definition and execution, 'for' loops, 'while' loops, and Python code execution.\n[w/This extension allows the execution of arbitrary Python code from a workflow.]" - }, - { - "author": "EmilioPlumed", - "title": "ComfyUI-Math [WIP]", - "reference": "https://github.com/EmilioPlumed/ComfyUI-Math", - "files": [ - "https://github.com/EmilioPlumed/ComfyUI-Math" - ], - "install_type": "git-clone", - "description": "Custom nodes that take 2 float inputs and calculates greatest common denominator and least common multiple, returning them as ints.\nNOTE: The files in the repo are not organized." - }, - { - "author": "mliand", - "title": "ComfyUI-Calendar-Node [WIP]", - "reference": "https://github.com/mliand/ComfyUI-Calendar-Node", - "files": [ - "https://github.com/mliand/ComfyUI-Calendar-Node" - ], - "install_type": "git-clone", - "description": "A custom node for Comfyui to create a Calendar like grid\nNOTE: The files in the repo are not organized." - }, - { - "author": "phamngoctukts", - "title": "ComyUI-Tupham", - "reference": "https://github.com/phamngoctukts/ComyUI-Tupham", - "files": [ - "https://github.com/phamngoctukts/ComyUI-Tupham" - ], - "install_type": "git-clone", - "description": "NODES: Ghép Ảnh, Multi Prompt v2.0, Condition Upscale, Multi sampler, Run node selected" - }, - { - "author": "5x00", - "title": "ComfyUI-Prompt-Plus [WIP]", - "reference": "https://github.com/5x00/ComfyUI-Prompt-Plus", - "files": [ - "https://github.com/5x00/ComfyUI-Prompt-Plus" - ], - "install_type": "git-clone", - "description": "Prompt Plus is a collection of LLM and VLM nodes that make prompting easier for image and video generation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "aria1th", - "title": "ComfyUI-CairoSVG", - "reference": "https://github.com/aria1th/ComfyUI-CairoSVG", - "files": [ - "https://github.com/aria1th/ComfyUI-CairoSVG" - ], - "install_type": "git-clone", - "description": "NODES: VectorizedUpscaleScaling, VectorizedUpscaleSize" - }, - { - "author": "gitmylo", - "title": "FlowNodes [WIP]", - "reference": "https://github.com/gitmylo/FlowNodes", - "files": [ - "https://github.com/gitmylo/FlowNodes" - ], - "install_type": "git-clone", - "description": "A ComfyUI nodepack containing nodes for basic programming logic." - }, - { - "author": "chengzeyi", - "title": "Comfy-WaveSpeed [WIP]", - "reference": "https://github.com/chengzeyi/Comfy-WaveSpeed", - "files": [ - "https://github.com/chengzeyi/Comfy-WaveSpeed" - ], - "install_type": "git-clone", - "description": "The all in one inference optimization solution for ComfyUI, universal, flexible, and fast." - }, - { - "author": "zyd232", - "title": "ComfyUI-zyd232-Nodes", - "reference": "https://github.com/zyd232/ComfyUI-zyd232-Nodes", - "files": [ - "https://github.com/zyd232/ComfyUI-zyd232-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Image Pixels Compare, Save Preview Images" - }, - { - "author": "yanhuifair", - "title": "ComfyUI-FairLab", - "reference": "https://github.com/yanhuifair/ComfyUI-FairLab", - "files": [ - "https://github.com/yanhuifair/ComfyUI-FairLab" - ], - "install_type": "git-clone", - "description": "NODES: CLIP Text Encode Translated, Translate String, Load Image From Folder, Save String To Folder, Fix UTF-8 String, String Combine, String Field, Download Image, Save Images To Folder, Save Image To Folder, Image Resize, ..." - }, - { - "author": "nomcycle", - "title": "ComfyUI_Cluster [WIP]", - "reference": "https://github.com/nomcycle/ComfyUI_Cluster", - "files": [ - "https://github.com/nomcycle/ComfyUI_Cluster" - ], - "install_type": "git-clone", - "description": "Very early W.I.P of clustered ComfyUI inference." - }, - { - "author": "waynepimpzhang", - "title": "FindBrightestSpot [WIP]", - "reference": "https://github.com/waynepimpzhang/comfyui-opencv-brightestspot", - "files": [ - "https://github.com/waynepimpzhang/comfyui-opencv-brightestspot" - ], - "install_type": "git-clone", - "description": "Analyze the image to find the x and y coordinates of the brightest point.\nNOTE: The files in the repo are not organized." - }, - { - "author": "power88", - "title": "ComfyUI-PDiD-Nodes [WIP]", - "reference": "https://github.com/power88/ComfyUI-PDiD-Nodes", - "files": [ - "https://github.com/power88/ComfyUI-PDiD-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Get Image Size, Check Character Tag, Nearest SDXL Resolution divided by 64, Get Image Main Color, Blend Images, List Operations, Make Image Gray.\nNOTE: not working" - }, - { - "author": "FinetunersAI", - "title": "ComfyUI Finetuners [WIP]", - "reference": "https://github.com/FinetunersAI/finetuners", - "files": [ - "https://github.com/FinetunersAI/finetuners" - ], - "install_type": "git-clone", - "description": "A collection of utility nodes for ComfyUI to enhance your workflow.\nNOTE: The files in the repo are not organized." - }, - { - "author": "sourceful-official", - "title": "ComfyUI_InstructPixToPixConditioningLatent [WIP]", - "reference": "https://github.com/sourceful-official/ComfyUI_InstructPixToPixConditioningLatent", - "files": [ - "https://github.com/sourceful-official/ComfyUI_InstructPixToPixConditioningLatent" - ], - "description": "ComfyUI-ComfyUI_InstructPixToPixConditioningLatent\nNOTE:invalid pyproject.toml", - "install_type": "git-clone" - }, - { - "author": "fritzprix", - "title": "ComfyUI-LLM-Utils [WIP]", - "reference": "https://github.com/fritzprix/ComfyUI-LLM-Utils", - "files": [ - "https://github.com/fritzprix/ComfyUI-LLM-Utils" - ], - "install_type": "git-clone", - "description": "A collection of utility nodes for ComfyUI focused on text and LLM-related operations\nNOTE: The files in the repo are not organized." - }, - { - "author": "ciga2011", - "title": "ComfyUI-AppGen [UNSAFE]", - "reference": "https://github.com/ciga2011/ComfyUI-AppGen", - "files": [ - "https://github.com/ciga2011/ComfyUI-AppGen" - ], - "install_type": "git-clone", - "description": "A ComfyUI nodepack designed to generate and edit Single Page Applications (SPAs) using natural language.[w/This extension allows arbitrary JavaScript code to be executed through the execution of workflows.]" - }, - { - "author": "DraconicDragon", - "title": "ComfyUI e621 booru Toolkit", - "reference": "https://github.com/DraconicDragon/ComfyUI_e621_booru_toolkit", - "files": [ - "https://github.com/DraconicDragon/ComfyUI_e621_booru_toolkit" - ], - "install_type": "git-clone", - "description": "WIP. Nodes: Fetch e621/danbooru image and/or tags etc from a given URL; Get the Wiki entry for a tag through a button press." - }, - { - "author": "Grey3016", - "title": "Save2Icon", - "reference": "https://github.com/Grey3016/Save2Icon", - "files": [ - "https://github.com/Grey3016/Save2Icon" - ], - "install_type": "git-clone", - "description": "NODES: Save2Icon" - }, - { - "author": "Chargeuk", - "title": "ComfyUI-vts-nodes [WIP]", - "reference": "https://github.com/Chargeuk/ComfyUI-vts-nodes", - "files": [ - "https://github.com/Chargeuk/ComfyUI-vts-nodes" - ], - "install_type": "git-clone", - "description": "NODES: Clean Text, Color Mask To Mask, Conditioning Set Batch Mask, Merge Delimited Text, Reduce Batch Size, Text To Batch Prompt, To Text, " - }, - { - "author": "ryanontheinside", - "title": "ComfyUI_YoloNasObjectDetection_Tensorrt [WIP]", - "reference": "https://github.com/ryanontheinside/ComfyUI_YoloNasObjectDetection_Tensorrt", - "files": [ - "https://github.com/ryanontheinside/ComfyUI_YoloNasObjectDetection_Tensorrt" - ], - "install_type": "git-clone", - "description": "ComfyUI YOLO NAS Object Detection with TensorRT" - }, - { - "author": "steelan9199", - "title": "ComfyUI-Teeth [UNSAFE]", - "reference": "https://github.com/steelan9199/ComfyUI-Teeth", - "files": [ - "https://github.com/steelan9199/ComfyUI-Teeth" - ], - "install_type": "git-clone", - "description": "Run Python code, Outline, List, Four-quadrant grid, Nine-square grid[w/This extension poses a risk of executing arbitrary commands through workflow execution. Please be cautious.]" - }, - { - "author": "aiden1020", - "title": "ComfyUI_Artcoder [WIP]", - "reference": "https://github.com/aiden1020/ComfyUI_Artcoder", - "files": [ - "https://github.com/aiden1020/ComfyUI_Artcoder" - ], - "install_type": "git-clone", - "description": "This project is a custom node for ComfyUI that uses [a/ArtCoder](https://arxiv.org/abs/2011.07815) [CVPR 2021] to refine videos generated by [a/AnimateDiff](https://arxiv.org/abs/2307.04725) [ICLR2024 Spotlight] or the other video. The node is to transform these videos into functional QR code videos that can be scanned.\nNOTE: The files in the repo are not organized." - }, - { - "author": "A4P7J1N7M05OT", - "title": "ComfyUI-ManualSigma", - "reference": "https://github.com/A4P7J1N7M05OT/ComfyUI-ManualSigma", - "files": [ - "https://github.com/A4P7J1N7M05OT/ComfyUI-ManualSigma" - ], - "install_type": "git-clone", - "description": "NODES: Manual Sigma" - }, - { - "author": "neverbiasu", - "title": "ComfyUI-StereoCrafter [WIP]", - "reference": "https://github.com/neverbiasu/ComfyUI-StereoCrafter", - "files": [ - "https://github.com/neverbiasu/ComfyUI-StereoCrafter" - ], - "install_type": "git-clone", - "description": "NODES: Depth Splatting Model Loader, Depth Splatting Node, Inpainting Inference Node" - }, - { - "author": "watarika", - "title": "ComfyUI-exit [UNSAFE]", - "reference": "https://github.com/watarika/ComfyUI-exit", - "files": [ - "https://github.com/watarika/ComfyUI-exit" - ], - "install_type": "git-clone", - "description": "Custom node to handle text.[w/This custom node includes a custom node that can terminate ComfyUI.]" - }, - { - "author": "watarika", - "title": "ComfyUI-Text-Utility [UNSAFE]", - "reference": "https://github.com/watarika/ComfyUI-Text-Utility", - "files": [ - "https://github.com/watarika/ComfyUI-Text-Utility" - ], - "install_type": "git-clone", - "description": "Custom node to handle text.[w/This nodepack contains a custom node that poses a security risk by providing the ability to read text from arbitrary paths.]" - }, - { - "author": "mehbebe", - "title": "ComfyLoraGallery [WIP]", - "reference": "https://github.com/mehbebe/ComfyLoraGallery", - "files": [ - "https://github.com/mehbebe/ComfyLoraGallery" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that will provide a gallery style lora selector similar to the 'lora' tab in Automatic1111." - }, - { - "author": "karthikg-09", - "title": "ComfyUI-KG09 [WIP]", - "reference": "https://github.com/karthikg-09/ComfyUI-3ncrypt", - "files": [ - "https://github.com/karthikg-09/ComfyUI-3ncrypt" - ], - "install_type": "git-clone", - "description": "NODES: Save Image+[w/The web extension of this nodepack modifies part of ComfyUI's asset files.]" - }, - { - "author": "glamorfleet0i", - "title": "ComfyUI Firewall", - "reference": "https://github.com/glamorfleet0i/ComfyUI-Firewall", - "files": [ - "https://github.com/glamorfleet0i/ComfyUI-Firewall" - ], - "install_type": "git-clone", - "description": "A very basic firewall-like middleware that restricts access to your ComfyUI server based on a list of specified IP addresses. As this is configured as middleware, the firewall will restrict both the web UI and any API endpoints." - }, - { - "author": "warshanks", - "title": "Shank-Tools", - "reference": "https://github.com/warshanks/Shank-Tools", - "files": [ - "https://github.com/warshanks/Shank-Tools" - ], - "install_type": "git-clone", - "description": "NODES: Tile Calculator, Resolution Divider, Height & Width" - }, - { - "author": "BaronVonBoolean", - "title": "ComfyUI-FileOps [UNSAFE]", - "reference": "https://github.com/BaronVonBoolean/ComfyUI-FileOps", - "files": [ - "https://github.com/BaronVonBoolean/ComfyUI-FileOps" - ], - "install_type": "git-clone", - "description": "NODES: File Mv, File Path, File Dir.\n[w/This is dangerous as it provides the ability to manipulate arbitrary user files.]" - }, - { - "author": "JissiChoi", - "title": "ComfyUI-Jissi-List [WIP]", - "reference": "https://github.com/JissiChoi/ComfyUI-Jissi-List", - "files": [ - "https://github.com/JissiChoi/ComfyUI-Jissi-List" - ], - "install_type": "git-clone", - "description": "Data List Management for ComfyUI\nNOTE: The files in the repo are not organized." - }, - { - "author": "Maxim-Dey", - "title": "ComfyUI-MS_Tools [WIP]", - "reference": "https://github.com/Maxim-Dey/ComfyUI-MaksiTools", - "files": [ - "https://github.com/Maxim-Dey/ComfyUI-MaksiTools" - ], - "install_type": "git-clone", - "description": "NODES: MS Time Measure NodeMaksiTools" - }, - { - "author": "krich-cto", - "title": "ComfyUI Flow Control [UNSTABLE]", - "reference": "https://github.com/krich-cto/ComfyUI-Flow-Control", - "files": [ - "https://github.com/krich-cto/ComfyUI-Flow-Control" - ], - "install_type": "git-clone", - "description": "This is an Extension for ComfyUI. This project will help you control the flow logic via many controls.[w/Installing this custom node currently causes a conflict with the UnetLoaderGGUF of ComfyUI-GGUF.]" - }, - { - "author": "dihan", - "title": "ComfyUI Random Keypoints for InstantID [WIP]", - "reference": "https://github.com/dihan/comfyui-random-kps", - "files": [ - "https://github.com/dihan/comfyui-random-kps" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that generates random facial keypoints compatible with InstantID.\nNOTE: The files in the repo are not organized." - }, - { - "author": "emranemran", - "title": "ComfyUI-FasterLivePortrait", - "reference": "https://github.com/emranemran/ComfyUI-FasterLivePortrait", - "files": [ - "https://github.com/emranemran/ComfyUI-FasterLivePortrait" - ], - "install_type": "git-clone", - "description": "Improve mouth tracking with live AI Video" - }, - { - "author": "kandy", - "title": "ComfyUI-KAndy", - "reference": "https://github.com/kandy/ComfyUI-KAndy", - "files": [ - "https://github.com/kandy/ComfyUI-KAndy" - ], - "install_type": "git-clone", - "description": "NODES: Civit Prompt API, Load Image From Url, Civit Images API, KAndyNoiseCondition, KAndyImagesByCss" - }, - { - "author": "StartHua", - "title": "Comfyui_leffa", - "reference": "https://github.com/StartHua/Comfyui_leffa", - "files": [ - "https://github.com/StartHua/Comfyui_leffa" - ], - "install_type": "git-clone", - "description": "NODES: CXH_Leffa_Viton_Load, CXH_Leffa_Viton_Run" - }, - { - "author": "logtd", - "title": "ComfyUI-HunyuanLoom [WIP]", - "id": "comfyui-42lux", - "reference": "https://github.com/logtd/ComfyUI-HunyuanLoom", - "files": [ - "https://github.com/logtd/ComfyUI-HunyuanLoom" - ], - "install_type": "git-clone", - "description": "A set of nodes to edit videos using the Hunyuan Video model" - }, - { - "author": "watarika", - "title": "ComfyUI-exit [UNSAFE]", - "reference": "https://github.com/watarika/ComfyUI-exit", - "files": [ - "https://github.com/watarika/ComfyUI-exit" - ], - "install_type": "git-clone", - "description": "A custom node that terminates ComfyUI after a specified number of seconds. Use this node if you want Google Colab to automatically terminate after mass generation. It is necessary to disconnect and delete the Google Colab runtime on the Notebook side." - }, - { - "author": "backearth1", - "title": "Comfyui-MiniMax-Video [WIP]", - "reference": "https://github.com/backearth1/Comfyui-MiniMax-Video", - "files": [ - "https://github.com/backearth1/Comfyui-MiniMax-Video" - ], - "install_type": "git-clone", - "description": "A ComfyUI extension that integrates MiniMax AI's image-to-video and text-to-video generation capabilities, allowing users to easily convert static images into dynamic videos.\nNOTE: The files in the repo are not organized." - }, - { - "author": "FinetunersAI", - "title": "Fast Group Link [WIP]", - "id": "fast-group-link", - "reference": "https://github.com/FinetunersAI/comfyui-fast-group-link", - "files": [ - "https://github.com/FinetunersAI/comfyui-fast-group-link" - ], - "install_type": "git-clone", - "description": "Link and control ComfyUI groups with a simple ON/OFF toggle. Control multiple groups at once with an easy-to-use interface.\nNOTE: The files in the repo are not organized." - }, - { - "author": "kijai", - "title": "ComfyUI-MMAudio [WIP]", - "reference": "https://github.com/kijai/ComfyUI-MMAudio", - "files": [ - "https://github.com/kijai/ComfyUI-MMAudio" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes to use [a/MMAudio](https://github.com/hkchengrex/MMAudio)" - }, - { - "author": "kuschanow", - "title": "ComfyUI-SD-Slicer", - "reference": "https://github.com/kuschanow/ComfyUI-SD-Slicer", - "files": [ - "https://github.com/kuschanow/ComfyUI-SD-Slicer" - ], - "install_type": "git-clone", - "description": "NODES: Slicer" - }, - { - "author": "ralonsobeas", - "title": "ComfyUI-HDRConversion [WIP]", - "reference": "https://github.com/ralonsobeas/ComfyUI-HDRConversion", - "files": [ - "https://github.com/ralonsobeas/ComfyUI-HDRConversion" - ], - "install_type": "git-clone", - "description": "NODES: Generate HDR image" - }, - { - "author": "Matrix-King-Studio", - "title": "ComfyUI-MoviePy", - "reference": "https://github.com/Matrix-King-Studio/ComfyUI-MoviePy", - "files": [ - "https://github.com/Matrix-King-Studio/ComfyUI-MoviePy" - ], - "install_type": "git-clone", - "description": "NODES: Image Clip Node, Audio Duration Node, Save Video Node" - }, - { - "author": "oxysoft", - "title": "ComfyUI-uiapi", - "reference": "https://github.com/oxysoft/ComfyUI-uiapi", - "files": [ - "https://github.com/oxysoft/ComfyUI-uiapi" - ], - "install_type": "git-clone", - "description": "UIAPI is an intermediate and frontend plugin which allow communicating with the Comfy webui through server connection. This saves the need to export a workflow.json and instead directly sending a queue command to the frontend. This way, the user can experiment in realtime as they are running some professional industry or rendering software which uses UIAPI / ComfyUI as a backend. There is no way to switch seamlessly between UIAPI and regular server connection - though as of late summer 2023 it was inferior to use the server connection because the server would constantly unload models and start from scratch, and the schema of the workfow json was completely different and much less convenient, losing crucial information for efficient querying of nodes and assigning data dynamically." - }, - { - "author": "hotpot-killer", - "title": "ComfyUI_AlexNodes", - "reference": "https://github.com/hotpot-killer/ComfyUI_AlexNodes", - "files": [ - "https://github.com/hotpot-killer/ComfyUI_AlexNodes" - ], - "install_type": "git-clone", - "description": "NODES: InstructPG - editing images with text prompt, ...\nNOTE: The files in the repo are not organized." - }, - { - "author": "pschroedl", - "title": "ComfyUI-StreamDiffusion", - "reference": "https://github.com/pschroedl/ComfyUI-StreamDiffusion", - "files": [ - "https://github.com/pschroedl/ComfyUI-StreamDiffusion" - ], - "install_type": "git-clone", - "description": "NODES: StreamDiffusionConfig, StreamDiffusionAccelerationSampler, StreamDiffusionLoraLoader, StreamDiffusionAccelerationConfig, StreamDiffusionSimilarityFilterConfig, StreamDiffusionModelLoader, ..." - }, - { - "author": "Junst", - "title": "ComfyUI-PNG2SVG2PNG", - "reference": "https://github.com/Junst/ComfyUI-PNG2SVG2PNG", - "files": [ - "https://github.com/Junst/ComfyUI-PNG2SVG2PNG" - ], - "description": "NODES:PNG2SVG2PNG", - "install_type": "git-clone" - }, - { - "author": "animEEEmpire", - "title": "ComfyUI-Animemory-Loader", - "reference": "https://github.com/animEEEmpire/ComfyUI-Animemory-Loader", - "files": [ - "https://github.com/animEEEmpire/ComfyUI-Animemory-Loader" - ], - "install_type": "git-clone", - "description": "AniMemory-alpha Custom Node for ComfyUI" - }, - { - "author": "ShahFaisalWani", - "title": "ComfyUI-Mojen-Nodeset", - "reference": "https://github.com/ShahFaisalWani/ComfyUI-Mojen-Nodeset", - "files": [ - "https://github.com/ShahFaisalWani/ComfyUI-Mojen-Nodeset" - ], - "install_type": "git-clone", - "description": "A collection of powerful, versatile, and community-driven custom nodes for ComfyUI, designed to elevate AI workflows!" - }, - { - "author": "grimli333", - "title": "ComfyUI_Grim", - "reference": "https://github.com/grimli333/ComfyUI_Grim", - "files": [ - "https://github.com/grimli333/ComfyUI_Grim" - ], - "install_type": "git-clone", - "description": "NODES: Generate a unique filename and folder name, Format Strings with Two Inputs" - }, - { - "author": "risunobushi", - "title": "ComfyUI_FocusMask", - "reference": "https://github.com/risunobushi/ComfyUI_FocusMask", - "files": [ - "https://github.com/risunobushi/ComfyUI_FocusMask" - ], - "install_type": "git-clone", - "description": "NODES: Extract Focus Mask" - }, - { - "author": "RicherdLee", - "title": "comfyui-oss-image-save [WIP]", - "reference": "https://github.com/RicherdLee/comfyui-oss-image-save", - "files": [ - "https://github.com/RicherdLee/comfyui-oss-image-save" - ], - "install_type": "git-clone", - "description": "NODES: SaveImageOSS." - }, - { - "author": "Matrix-King-Studio", - "title": "ComfyUI-MoviePy", - "reference": "https://github.com/Matrix-King-Studio/ComfyUI-MoviePy", - "files": [ - "https://github.com/Matrix-King-Studio/ComfyUI-MoviePy" - ], - "install_type": "git-clone", - "description": "NODES: Image Clip Node, Audio Duration Node, Save Video Node,..." - }, - { - "author": "Big Idea Technology", - "title": "ComfyUI-Movie-Tools [WIP]", - "reference": "https://github.com/Big-Idea-Technology/ComfyUI-Movie-Tools", - "files": [ - "https://github.com/Big-Idea-Technology/ComfyUI-Movie-Tools" - ], - "install_type": "git-clone", - "description": "Movie Tools is a set of custom nodes, designed to simplify saving and loading batches of images with enhanced functionality like subfolder management and batch image handling." - }, - { - "author": "ArthusLiang", - "title": "comfyui-face-remap [WIP]", - "reference": "https://github.com/ArthusLiang/comfyui-face-remap", - "files": [ - "https://github.com/ArthusLiang/comfyui-face-remap" - ], - "install_type": "git-clone", - "description": "NODES: FaceRemap\nNOTE: The files in the repo are not organized." - }, - { - "author": "trithemius", - "title": "ComfyUI-SmolVLM [WIP]", - "reference": "https://github.com/mamorett/ComfyUI-SmolVLM", - "files": [ - "https://github.com/mamorett/ComfyUI-SmolVLM" - ], - "install_type": "git-clone", - "description": "Nodes to use SmolVLM for image tagging and captioning.\nNOTE: The files in the repo are not organized." - }, - { - "author": "anze", - "title": "ComfyUI-OIDN [WIP]", - "reference": "https://github.com/Anze-/ComfyUI-OIDN", - "files": [ - "https://github.com/Anze-/ComfyUI-OIDN" - ], - "install_type": "git-clone", - "description": "ComyUI wrapper for Intel OIDN image denoising\nWARNING! : this is a development repo, usage in production environments is not advised! Bugs are to be expected." - }, - { - "author": "hay86", - "title": "ComfyUI AceNodes [UNSAFE]", - "reference": "https://github.com/hay86/ComfyUI_AceNodes", - "files": [ - "https://github.com/hay86/ComfyUI_AceNodes" - ], - "install_type": "git-clone", - "description": "Some useful custom nodes that are not included in ComfyUI core yet.\nNOTE: Vulnerability discovered. Not being managed." - }, - { - "author": "dowands", - "title": "AddMaskForICLora", - "reference": "https://github.com/dowands/ComfyUI-AddMaskForICLora", - "files": [ - "https://github.com/dowands/ComfyUI-AddMaskForICLora" - ], - "install_type": "git-clone", - "description": "NODES: Add Mask For IC Lora x" - }, - { - "author": "exectails", - "title": "Scripting", - "id": "et_scripting [UNSAFE]", - "reference": "https://github.com/exectails/comfyui-et_scripting", - "files": [ - "https://github.com/exectails/comfyui-et_scripting" - ], - "install_type": "git-clone", - "description": "Nodes that can be used to write Python scripts directly on a node. Useful for quick prototyping and testing, at the cost of security.[w/This extension allows the execution of arbitrary Python code from a workflow.]" - }, - { - "author": "AIFSH", - "title": "UltralightDigitalHuman-ComfyUI", - "reference": "https://github.com/AIFSH/UltralightDigitalHuman-ComfyUI", - "files": [ - "https://github.com/AIFSH/UltralightDigitalHuman-ComfyUI" - ], - "install_type": "git-clone", - "description": "a custom node for [a/Ultralight-Digital-Human](https://github.com/anliyuan/Ultralight-Digital-Human)\nNOTE: The files in the repo are not organized." - }, - { - "author": "StartHua", - "title": "Comfyui_Flux_Style_Ctr [WIP]", - "reference": "https://github.com/StartHua/Comfyui_Flux_Style_Ctr", - "files": [ - "https://github.com/StartHua/Comfyui_Flux_Style_Ctr" - ], - "install_type": "git-clone", - "description": "NODES:CXH_StyleModelApply\nNOTE: The files in the repo are not organized." - }, - { - "author": "miragecoa", - "title": "ComfyUI-LLM-Evaluation [WIP]", - "reference": "https://github.com/miragecoa/ComfyUI-LLM-Evaluation", - "files": [ - "https://github.com/miragecoa/ComfyUI-LLM-Evaluation" - ], - "install_type": "git-clone", - "description": "NODES:Load File, Select Item by Index, Select Item by Key, JSONToListNode, MathOperationNode, F1ScoreNode, AccuracyNode, ..." - }, - { - "author": "WASasquatch", - "title": "ASTERR [UNSAFE]", - "id": "asterr", - "reference": "https://github.com/WASasquatch/ASTERR", - "files": [ - "https://github.com/WASasquatch/ASTERR" - ], - "install_type": "git-clone", - "description": "Abstract Syntax Trees Evaluated Restricted Run (ASTERR) is a Python Script executor for ComfyUI. [w/Warning:ASTERR runs Python Code from a Web Interface! It is highly recommended to run this in a closed-off environment, as it could have potential security risks.]" - }, - { - "author": "BenjaMITM", - "title": "ComfyUI_On_The_Fly_Wildcards [WIP]", - "reference": "https://github.com/BenjaMITM/ComfyUI_On_The_Fly_Wildcards", - "files": [ - "https://github.com/BenjaMITM/ComfyUI_On_The_Fly_Wildcards" - ], - "install_type": "git-clone", - "description": "NODES:Wildcard Creator, Wildcard Loader, Wildcard Selector, Display String.\nNOTE: The files in the repo are not organized." - }, - { - "author": "celll1", - "title": "cel_sampler [WIP]", - "reference": "https://github.com/celll1/cel_sampler", - "files": [ - "https://github.com/celll1/cel_sampler" - ], - "install_type": "git-clone", - "description": "NODES:Latent Value Tracker\nNOTE: The files in the repo are not organized." - }, - { - "author": "DataCTE", - "title": "ComfyUI-DataVoid-nodes [WIP]", - "reference": "https://github.com/DataCTE/ComfyUI-DataVoid-nodes", - "files": [ - "https://github.com/DataCTE/ComfyUI-DataVoid-nodes" - ], - "install_type": "git-clone", - "description": "A collection of custom nodes for ComfyUI focused on model merging and style adaptation.[w/It may cause a lot of node conflicts with comfyui_ipadapter_plus.]" - }, - { - "author": "minhtuannhn", - "title": "comfyui-gemini-studio [WIP]", - "reference": "https://github.com/minhtuannhn/comfyui-gemini-studio", - "files": [ - "https://github.com/minhtuannhn/comfyui-gemini-studio" - ], - "install_type": "git-clone", - "description": "comfyui-gemini-studio[w/This extension uses the legacy method of copying JS.]" - }, - { - "author": "artem-konevskikh", - "title": "ComfyUI Video Processing Nodes [WIP]", - "reference": "https://github.com/artem-konevskikh/comfyui-split-merge-video", - "files": [ - "https://github.com/artem-konevskikh/comfyui-split-merge-video" - ], - "install_type": "git-clone", - "description": "Custom nodes for ComfyUI that add video splitting and merging capabilities with crossfade transitions." - }, - { - "author": "Poseidon-fan", - "title": "ComfyUI-fileCleaner [UNSAFE]", - "reference": "https://github.com/Poseidon-fan/ComfyUI-fileCleaner", - "files": [ - "https://github.com/Poseidon-fan/ComfyUI-fileCleaner" - ], - "install_type": "git-clone", - "description": "In production environments, images are usually saved on storage servers such as S3, rather than local folders. So the method of placing images in local folders using ComfyUI's native LoadImage and SaveImage nodes cannot be used as a deployment service method, but can only be used as a temporary storage place for images. This requires a way to delete images from the input and output folders.\nThis node is used to delete images from the input and output folders. It is recommended to use this node through api call in the backend after the image processing is completed.[w/Users can use the file deletion feature through the workflow.]" - }, - { - "author": "yorkane", - "title": "Comfy UI Robe Nodes [UNSAFE]", - "reference": "https://github.com/RobeSantoro/ComfyUI-RobeNodes", - "files": [ - "https://github.com/RobeSantoro/ComfyUI-RobeNodes" - ], - "install_type": "git-clone", - "description": "NODES: List Video Path Node, List Image Path Node\nThis is a collection of utility nodes for the ComfyUI stable diffusion client that provides enhanced file path handling capabilities.[w/Users will be able to access images from arbitrary paths through the workflow.]" - }, - { - "author": "Clybius", - "title": "ComfyUI-FluxDeCLIP", - "reference": "https://github.com/Clybius/ComfyUI-FluxDeCLIP", - "files": [ - "https://github.com/Clybius/ComfyUI-FluxDeCLIP" - ], - "install_type": "git-clone", - "description": "NODES:FluxDeCLIPCheckpointLoader" - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "ComfyUI-BiRefNet-ZHO [BROKEN]", - "id": "birefnet", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO", - "files": [ - "https://github.com/ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO" - ], - "install_type": "git-clone", - "description": "Better version for [a/BiRefNet](https://github.com/zhengpeng7/birefnet) in ComfyUI | Both img and video.\nNOTE: You need to do [a/manual patch](https://github.com/ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO/issues/20)" - }, - { - "author": "trashgraphicard", - "title": "Albedo-Sampler-for-ComfyUI", - "reference": "https://github.com/trashgraphicard/Albedo-Sampler-for-ComfyUI", - "files": [ - "https://github.com/trashgraphicard/Albedo-Sampler-for-ComfyUI" - ], - "install_type": "git-clone", - "description": "NODES:Sample Image, Make Seamless Tile" - }, - { - "author": "Anze-", - "title": "ComfyUI_deepDeband [WIP]", - "reference": "https://github.com/Anze-/ComfyUI_deepDeband", - "files": [ - "https://github.com/Anze-/ComfyUI_deepDeband" - ], - "install_type": "git-clone", - "description": "ComyUI wrapper for RaymondLZhou/deepDeband image and video debanding\nNOTE: The files in the repo are not organized." - }, - { - "author": "bmad4ever", - "title": "Bmad Nodes [UNSAFE]", - "id": "bmad", - "reference": "https://github.com/bmad4ever/comfyui_bmad_nodes", - "files": [ - "https://github.com/bmad4ever/comfyui_bmad_nodes" - ], - "install_type": "git-clone", - "description": "This custom node offers the following functionalities: API support for setting up API requests, computer vision primarily for masking or collages, and general utility to streamline workflow setup or implement essential missing features.\nNOTE: Vulnerability discovered. Not being managed." - }, - { - "author": "suncat2ps", - "title": "ComfyUI-SaveImgNextcloud", - "reference": "https://github.com/suncat2ps/ComfyUI-SaveImgNextcloud", - "files": [ - "https://github.com/suncat2ps/ComfyUI-SaveImgNextcloud" - ], - "install_type": "git-clone", - "description": "NODES: Save Image to Nextcloud" - }, - { - "author": "DoctorDiffusion", - "title": "ComfyUI-Flashback", - "reference": "https://github.com/DoctorDiffusion/ComfyUI-Flashback", - "files": [ - "https://github.com/DoctorDiffusion/ComfyUI-Flashback" - ], - "description": "NODES:Latent Export, Latent Import, Latent Loop", - "install_type": "git-clone" - }, - { - "author": "sswink", - "title": "comfyui-lingshang", - "reference": "https://github.com/sswink/comfyui-lingshang", - "files": [ - "https://github.com/sswink/comfyui-lingshang" - ], - "description": "NODES:LS_SaveImageToOss, LS_LoadMaskFromUrl, LS_DigImageByMask, LS_ALY_Seg_Utils, LS_ALY_UploadToOssAndGetUrl, LS_GrowMaskWithBlur, LS_ALY_Seg_Body_Utils, LS_ALY_Seg_Common_Utils, LS_ALY_Seg_Clothes_Utils, LS_ALY_Seg_Body_Utils_Return_crop, ...", - "install_type": "git-clone" - }, - { - "author": "AICodeFactory", - "title": "ComfyUI-Viva", - "reference": "https://github.com/AICodeFactory/ComfyUI-Viva", - "files": [ - "https://github.com/AICodeFactory/ComfyUI-Viva" - ], - "description": "NODES:HttpTrigger (Viva), HttpTrigger (Image), HttpTrigger (Common)", - "install_type": "git-clone" - }, - { - "author": "LogicAI", - "title": "ComfyUI-MagicAI [UNSAFE]", - "reference": "https://github.com/lcolok/ComfyUI-MagicAI", - "files": [ - "https://github.com/lcolok/ComfyUI-MagicAI" - ], - "install_type": "git-clone", - "description": "NODES:Mask Size Calculator (MagicAI), Universal Mask Converter (MagicAI), Python Execution (MagicAI), Extract JSON From Text Node(MagicAI)\n[w/This extension allows the execution of arbitrary Python code from a workflow.]" - }, - { - "author": "aria1th", - "title": "ComfyUI-SkipCFGSigmas", - "reference": "https://github.com/aria1th/ComfyUI-SkipCFGSigmas", - "files": [ - "https://github.com/aria1th/ComfyUI-SkipCFGSigmas" - ], - "install_type": "git-clone", - "description": "NODES: CFGControl_SKIPCFG" - }, - { - "author": "Clelstyn", - "title": "ComfyUI-Inpaint_with_Detailer", - "reference": "https://github.com/Clelstyn/ComfyUI-Inpaint_with_Detailer", - "files": [ - "https://github.com/Clelstyn/ComfyUI-Inpaint_with_Detailer" - ], - "install_type": "git-clone", - "description": "NODES:Masked Resize Image, Paste Masked Image, Filter And Blur Mask" - }, - { - "author": "Looking-Glass", - "title": "LKG-ComfyUI", - "reference": "https://github.com/Looking-Glass/LKG-ComfyUI", - "files": [ - "https://github.com/Looking-Glass/LKG-ComfyUI" - ], - "install_type": "git-clone", - "description": "NODES:Side by Side Node, Bridge Preview Node, Load Folder, Scale Maintain Aspect Ratio Node, " - }, - { - "author": "xiaoyumu", - "title": "ComfyUI-XYNodes", - "reference": "https://github.com/xiaoyumu/ComfyUI-XYNodes", - "files": [ - "https://github.com/xiaoyumu/ComfyUI-XYNodes" - ], - "install_type": "git-clone", - "description": "Nodes:PrimitiveBBOX." - }, - { - "author": "ainanoha", - "title": "etm_comfyui_nodes", - "reference": "https://github.com/ainanoha/etm_comfyui_nodes", - "files": [ - "https://github.com/ainanoha/etm_comfyui_nodes" - ], - "install_type": "git-clone", - "description": "NODES:LETM Save Image, ETM Load Image From Local" - }, - { - "author": "m-ai-studio", - "title": "mai-prompt-progress", - "reference": "https://github.com/m-ai-studio/mai-prompt-progress", - "files": [ - "https://github.com/m-ai-studio/mai-prompt-progress" - ], - "install_type": "git-clone", - "description": "ComfyUI extensions for sending prompt progress to webhook" - }, - { - "author": "neeltheninja", - "title": "ComfyUI-TempFileDeleter [UNSAFE]", - "reference": "https://github.com/neeltheninja/ComfyUI-TempFileDeleter", - "files": [ - "https://github.com/neeltheninja/ComfyUI-TempFileDeleter" - ], - "install_type": "git-clone", - "description": "This node is designed to streamline your workflow in ComfyUI by efficiently cleaning up temporary files on each run. This node takes no input. You can specify 'on' or 'off' in the node itself, or just bypass to not use use it.[w/This node can delete any files in the folder mentioned in 'folder_path' in the node. Be aware of this and change the folder path correctly before running any workflow with this node. I will NOT be responsible for wrongly deleted files because you didn't read this beforehand.]" - }, - { - "author": "kylegrover", - "title": "comfyui-python-cowboy [UNSAFE]", - "reference": "https://github.com/kylegrover/comfyui-python-cowboy", - "files": [ - "https://github.com/kylegrover/comfyui-python-cowboy" - ], - "install_type": "git-clone", - "description": "run python code in comfyui\nuses codemirror for nice syntax highlighting\nNOTE: based on ComfyUI-nidefawl[w/This node is an unsafe node that includes the capability to execute arbitrary python script.]" - }, - { - "author": "kijai", - "title": "ComfyUI-MochiWrapper [WIP]", - "reference": "https://github.com/kijai/ComfyUI-MochiWrapper", - "files": [ - "https://github.com/kijai/ComfyUI-MochiWrapper" - ], - "install_type": "git-clone", - "description": "ComfyUI wrapper nodes for [a/Mochi](https://github.com/genmoai/models) video generator" - }, - { - "author": "leadbreak", - "title": "Face Aging [WIP]", - "reference": "https://github.com/leadbreak/comfyui-faceaging", - "files": [ - "https://github.com/leadbreak/comfyui-faceaging" - ], - "install_type": "git-clone", - "description": "This is a comfyui custom node version of [a/Age Transformation](https://github.com/yuval-alaluf/SAM).\nNOTE: The files in the repo are not organized." - }, - { - "author": "downlifted", - "title": "ComfyUI_BWiZ_Nodes [WIP]", - "reference": "https://github.com/downlifted/ComfyUI_BWiZ_Nodes", - "files": [ - "https://github.com/downlifted/ComfyUI_BWiZ_Nodes" - ], - "install_type": "git-clone", - "description": "NODES:CaptainWebhook, CaptainWebhook-Email, CaptainWebhook-Push, BWIZ_AdvancedLoadImageBatch, BWIZ_ErrorDetector, BWIZ_HFRepoBatchLoader, BWIZ_NotificationSound.\nNOTE: The files in the repo are not organized." - }, - { - "author": "blurymind", - "title": "cozy-fireplace [WIP]", - "reference": "https://github.com/blurymind/cozy-fireplace", - "files": [ - "https://github.com/blurymind/cozy-fireplace" - ], - "install_type": "git-clone", - "description": "Cozy fireplace is a ComfyUI workflow prompter that brings a localhost server frontend for existing workflows created in ComfyUi. Just place your favorite or lovingly crafted workflows in a folder and cozy fireplace will let you select and run any of them (export them as API type in comfyui) It's a cozy UI that scales all the way down to mobile phone devices - to let you prompt your beefy pc at home with your smartphone." - }, - { - "author": "lordwedggie", - "title": "xcpNodes [WIP]", - "reference": "https://github.com/lordwedggie/xcpNodes", - "files": [ - "https://github.com/lordwedggie/xcpNodes" - ], - "install_type": "git-clone", - "description": "Slider nodes based on Smirnov75's codes [a/https://github.com/Smirnov75/ComfyUI-mxToolkit](https://github.com/Smirnov75/ComfyUI-mxToolkit)\nNOTE: The files in the repo are not organized." - }, - { - "author": "kxh", - "title": "ComfyUI-ImageUpscaleWithModelMultipleTimes", - "reference": "https://github.com/kxh/ComfyUI-ImageUpscaleWithModelMultipleTimes", - "files": [ - "https://github.com/kxh/ComfyUI-ImageUpscaleWithModelMultipleTimes" - ], - "install_type": "git-clone", - "description": "Upscale image with model multiple times !" - }, - { - "author": "rouxianmantou", - "title": "comfyui-rxmt-nodes", - "reference": "https://github.com/rouxianmantou/comfyui-rxmt-nodes", - "files": [ - "https://github.com/rouxianmantou/comfyui-rxmt-nodes" - ], - "install_type": "git-clone", - "description": "NODES: Check Value Type, Why Prompt Text" - }, - { - "author": "SirVeggie", - "title": "SirVeggie/Custom nodes for ComfyUI", - "reference": "https://github.com/SirVeggie/comfyui-sv-nodes", - "files": [ - "https://github.com/SirVeggie/comfyui-sv-nodes" - ], - "install_type": "git-clone", - "description": "NODES:SV-SimpleText, SV-PromptProcessing, SV-PromptProcessingRecursive, SV-PromptProcessingAdvanced, SV-PromptProcessingEncode,..." - }, - { - "author": "artisanalcomputing", - "title": "artcpu-custom-nodes", - "reference": "https://github.com/artisanalcomputing/ComfyUI-Custom-Nodes", - "files": [ - "https://github.com/artisanalcomputing/ComfyUI-Custom-Nodes" - ], - "install_type": "git-clone", - "description": "NODES:Random Video Mixer, Spotify Canvas Generator, Video Writer\ncustom comfyui nodes for audio/visual purposes# ComfyUI-Custom-Nodes" - }, - { - "author": "kxh", - "title": "ComfyUI-sam2", - "reference": "https://github.com/kxh/ComfyUI-sam2", - "files": [ - "https://github.com/kxh/ComfyUI-sam2" - ], - "install_type": "git-clone", - "description": "use semantic tag to segment any element in an image, output a mask.\nNOTE: Repo name is conflicting with neverbiasu/ComfyUI-SAM2" - }, - { - "author": "AIFSH", - "title": "UtilNodes-ComfyUI [WIP]", - "reference": "https://github.com/AIFSH/UtilNodes-ComfyUI", - "files": [ - "https://github.com/AIFSH/UtilNodes-ComfyUI" - ], - "install_type": "git-clone", - "description": "here put custom input nodes such as text,video...\nNOTE: The files in the repo are not organized." - }, - { - "author": "alchemist-novaro", - "title": "ComfyUI-Simple-Image-Tools [WIP]", - "reference": "https://github.com/alchemist-novaro/ComfyUI-Simple-Image-Tools", - "files": [ - "https://github.com/alchemist-novaro/ComfyUI-Simple-Image-Tools" - ], - "install_type": "git-clone", - "description": "Get mask from image based on alpha (Get Mask From Alpha)\nNOTE: The files in the repo are not organized." - }, - { - "author": "galoreware", - "title": "ComfyUI-GaloreNodes [WIP]", - "reference": "https://github.com/galoreware/ComfyUI-GaloreNodes", - "files": [ - "https://github.com/galoreware/ComfyUI-GaloreNodes" - ], - "install_type": "git-clone", - "description": "Color and Image related nodes for ComfyUI." - }, - { - "author": "lgldlk", - "title": "ComfyUI-img-tiler", - "reference": "https://github.com/lgldlk/ComfyUI-img-tiler", - "files": [ - "https://github.com/lgldlk/ComfyUI-img-tiler" - ], - "install_type": "git-clone", - "description": "NODES:TilerImage, TilerSelect, TileMaker, ImageListTileMaker" - }, - { - "author": "wilzamguerrero", - "title": "Comfyui-zZzZz [UNSAFE]", - "reference": "https://github.com/wilzamguerrero/Comfyui-zZzZz", - "files": [ - "https://github.com/wilzamguerrero/Comfyui-zZzZz" - ], - "install_type": "git-clone", - "description": "NODES:Download Z, Compress Z, Move Z, Delete Z, Rename Z, Create Z, Infinite Z, Share Screen Z" - }, - { - "author": "alchemist-novaro", - "title": "Affine Transform ComfyUI Node [WIP]", - "reference": "https://github.com/alchemist-novaro/ComfyUI-Affine-Transform", - "files": [ - "https://github.com/alchemist-novaro/ComfyUI-Affine-Transform" - ], - "install_type": "git-clone", - "description": "This node output the image that are transfromed by affine matrix what is made according to 4 points of output.\nNOTE: The files in the repo are not organized." - }, - { - "author": "ruka-game", - "title": "ComfyUI RukaLib [WIP]", - "reference": "https://github.com/ruka-game/rukalib_comfyui", - "files": [ - "https://github.com/ruka-game/rukalib_comfyui" - ], - "install_type": "git-clone", - "description": "NODES: Ruka Prompt Enhancer, Ruka Debug Probe.\nMy utilities for comfy (WIP / ollama is required for LLM nodes)" - }, - { - "author": "MythicalChu", - "title": "ComfyUI-APG_ImYourCFGNow", - "reference": "https://github.com/MythicalChu/ComfyUI-APG_ImYourCFGNow", - "files": [ - "https://github.com/MythicalChu/ComfyUI-APG_ImYourCFGNow" - ], - "install_type": "git-clone", - "description": "Use this node like a RescaleCFG node, ... modelIn -> ThisNode -> ModelOut ... -> KSampler\n'scale' acts like your CFG, your CFG doesn't do anything anymore white this node is active. See paper [a/https://arxiv.org/pdf/2410.02416](https://arxiv.org/pdf/2410.02416) for instructions about the other parameters. (Pages 20-21)" - }, - { - "author": "okg21", - "title": "VLLMVisionChatNode", - "reference": "https://github.com/okg21/VLLMVisionChatNode", - "files": [ - "https://raw.githubusercontent.com/okg21/VLLMVisionChatNode/refs/heads/main/VLLMVisionChatNode.py" - ], - "pip": ["openai", "numpy"], - "install_type": "copy", - "description": "This platform extension provides ZhipuAI nodes, enabling you to configure a workflow for online video generation." - }, - { - "author": "netanelben", - "title": "comfyui-photobooth-customnode", - "reference": "https://github.com/netanelben/comfyui-photobooth-customnode", - "files": [ - "https://github.com/netanelben/comfyui-photobooth-customnode" - ], - "install_type": "git-clone", - "description": "comfyui-photobooth-customnode" - }, - { - "author": "netanelben", - "title": "comfyui-text2image-customnode", - "reference": "https://github.com/netanelben/comfyui-text2image-customnode", - "files": [ - "https://github.com/netanelben/comfyui-text2image-customnode" - ], - "install_type": "git-clone", - "description": "comfyui-text2image-customnode" - }, - { - "author": "netanelben", - "title": "comfyui-camera2image-customnode", - "reference": "https://github.com/netanelben/comfyui-camera2image-customnode", - "files": [ - "https://github.com/netanelben/comfyui-camera2image-customnode" - ], - "install_type": "git-clone", - "description": "comfyui-camera2image-customnode" - }, - { - "author": "netanelben", - "title": "comfyui-image2image-customnode", - "reference": "https://github.com/netanelben/comfyui-image2image-customnode", - "files": [ - "https://github.com/netanelben/comfyui-image2image-customnode" - ], - "install_type": "git-clone", - "description": "comfyui-image2image-customnode" - }, - { - "author": "JayLyu", - "title": "ComfyUI_BaiKong_Node", - "id": "baikong", - "reference": "https://github.com/JayLyu/ComfyUI_BaiKong_Node", - "files": [ - "https://github.com/JayLyu/ComfyUI_BaiKong_Node" - ], - "install_type": "git-clone", - "description": "Nodes for advanced color manipulation and image processing: BK Img To Color, BK Color Selector, BK Color Contrast, BK Color Limit, BK Color Luminance, BK Gradient Image, and BK Image Aspect Filter.\n[w/requirements.txt is broken.]" - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "ComfyUI Llama 3.1 [WIP]", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Llama-3-2", - "files": [ - "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Llama-3-2" - ], - "install_type": "git-clone", - "description": "Using Llama-3-1 in ComfyUI" - }, - { - "author": "netanelben", - "title": "comfyui-text2image-customnode [WIP]", - "reference": "https://github.com/netanelben/comfyui-text2image-customnode", - "files": [ - "https://github.com/netanelben/comfyui-text2image-customnode" - ], - "install_type": "git-clone", - "description": "text2image web extension" - }, - { - "author": "leeguandong", - "title": "ComfyUI_AliControlnetInpainting [WIP]", - "reference": "https://github.com/leeguandong/ComfyUI_AliControlnetInpainting", - "files": [ - "https://github.com/leeguandong/ComfyUI_AliControlnetInpainting" - ], - "install_type": "git-clone", - "description": "ComfyUI nodes to use AliControlnetInpainting" - }, - { - "author": "jordancoult", - "title": "ComfyUI_HelpfulNodes", - "reference": "https://github.com/jordancoult/ComfyUI_HelpfulNodes", - "files": [ - "https://github.com/jordancoult/ComfyUI_HelpfulNodes" - ], - "install_type": "git-clone", - "description": "NODES: Prep Crop Around Keypoints" - }, - { - "author": "ashishsaini", - "title": "comfyui_segformer_b2_sleeves", - "reference": "https://github.com/ashishsaini/comfyui-segment-clothing-sleeves", - "files": [ - "https://github.com/ashishsaini/comfyui-segment-clothing-sleeves" - ], - "install_type": "git-clone", - "description": "NODES:segformer_b2_sleeves" - }, - { - "author": "io-club", - "title": "ComfyUI-LuminaNext [WIP]", - "reference": "https://github.com/io-club/ComfyUI-LuminaNext", - "files": [ - "https://github.com/io-club/ComfyUI-LuminaNext" - ], - "install_type": "git-clone", - "description": "NODES: GemmaClipLoader" - }, - { - "author": "shadowcz007", - "title": "Comfyui-EzAudio [WIP]", - "reference": "https://github.com/shadowcz007/Comfyui-EzAudio", - "files": [ - "https://github.com/shadowcz007/Comfyui-EzAudio" - ], - "install_type": "git-clone", - "description": "NODES: EZ Generate Audio, EZ Load Model\nNOTE: The files in the repo are not organized." - }, - { - "author": "neo0801", - "title": "my-comfy-node", - "reference": "https://github.com/neo0801/my-comfy-node", - "files": [ - "https://github.com/neo0801/my-comfy-node" - ], - "install_type": "git-clone", - "description": "NODES:Deep Mosaic Get Image Mosaic Mask, Deep Mosaic Get Video Mosaic Mask, Deep Mosaic Remove Image Mosaic, Deep Mosaic Remove Video Mosaic" - }, - { - "author": "nikkuexe", - "title": "List Data Helper Nodes", - "reference": "https://github.com/paulhoux/Smart-Prompting", - "files": [ - "https://github.com/paulhoux/Smart-Prompting" - ], - "install_type": "git-clone", - "description": "Custom nodes for ComfyUI, allowing you to more easily manipulate text and create good prompts.[w/The use of outdated front extension techniques results in remnants being left behind during uninstallation.]" - }, - { - "author": "nikkuexe", - "title": "List Data Helper Nodes", - "reference": "https://github.com/nikkuexe/ComfyUI-ListDataHelpers", - "files": [ - "https://github.com/nikkuexe/ComfyUI-ListDataHelpers" - ], - "install_type": "git-clone", - "description": "A set of custom nodes for handling lists in ComfyUI." - }, - { - "author": "Fannovel16", - "title": "ComfyUI-AppIO", - "reference": "https://github.com/Fannovel16/ComfyUI-AppIO", - "files": [ - "https://github.com/Fannovel16/ComfyUI-AppIO" - ], - "install_type": "git-clone", - "description": "NODES:AppIO_StringInput, AppIO_ImageInput, AppIO_StringOutput, AppIO_ImageOutput" - }, - { - "author": "seancheung", - "title": "comfyui-creative-nodes", - "reference": "https://github.com/seancheung/comfyui-creative-nodes", - "files": [ - "https://github.com/seancheung/comfyui-creative-nodes" - ], - "install_type": "git-clone", - "description": "NODES:Stop Flow, Skip From Flow, Skip To Flow, Resolution Selector, ResolutionXL Selector" - }, - { - "author": "AlexXi19", - "title": "ComfyUI-OpenAINode", - "reference": "https://github.com/AlexXi19/ComfyUI-OpenAINode", - "files": [ - "https://github.com/AlexXi19/ComfyUI-OpenAINode" - ], - "install_type": "git-clone", - "description": "ComfyUI-OpenAINode is a user-friendly node that serves as an interface to the OpenAI Models.[w/Repo name conflict with Electrofried/ComfyUI-OpenAINode]" - }, - { - "author": "IgPoly", - "title": "ComfyUI-igTools", - "reference": "https://github.com/IgPoly/ComfyUI-igTools", - "files": [ - "https://github.com/IgPoly/ComfyUI-igTools" - ], - "install_type": "git-clone", - "description": "NODES:IGT Simple Tiles Calc" - }, - { - "author": "Ryota", - "title": "Ryota's Nodes", - "reference": "https://github.com/lichenhao/Comfyui_Ryota", - "files": [ - "https://github.com/lichenhao/Comfyui_Ryota" - ], - "install_type": "git-clone", - "description": "NODES:CombineTexts, FontLoader, DrawText, TxtFileLoader, SaveTxtFile, SwitchModelClip, SwitchAnyInputs, Reroute2, Reroute3" - }, - { - "author": "Soppatorsk", - "title": "comfyui_img_to_ascii [WIP]", - "reference": "https://github.com/Soppatorsk/comfyui_img_to_ascii", - "files": [ - "https://github.com/Soppatorsk/comfyui_img_to_ascii" - ], - "install_type": "git-clone", - "description": "Basic functionality for converting an image to ASCII art returned as a png image based on [a/ascii_magic](https://github.com/LeandroBarone/python-ascii_magic)" - }, - { - "author": "Lilien86", - "title": "Comfyui_Latent_Interpolation [WIP]", - "reference": "https://github.com/Lilien86/Comfyui_Latent_Interpolation", - "files": [ - "https://github.com/Lilien86/Comfyui_Latent_Interpolation" - ], - "install_type": "git-clone", - "description": "Hey everyone it's my Custom ComfyUI Nodes Pack repository! This project contains a collection of custom nodes designed to extend the functionality of ComfyUI. These nodes offer capabilities and new creative possibilities, especially in the realms of latent space manipulation and interpolation.\nNOTE: The files in the repo are not organized." - }, - { - "author": "haodman", - "title": "ComfyUI_Rain", - "reference": "https://github.com/haodman/ComfyUI_Rain", - "files": [ - "https://github.com/haodman/ComfyUI_Rain" - ], - "install_type": "git-clone", - "description": "NODES:Rain_ValueSwitch, Rain_Math, Rain_IntToFloat, Rain_ImageSize." - }, - { - "author": "bananasss00", - "title": "Comfyui-PyExec [UNSAFE]", - "reference": "https://github.com/bananasss00/Comfyui-PyExec", - "files": [ - "https://github.com/bananasss00/Comfyui-PyExec" - ], - "install_type": "git-clone", - "description": "Nodes:PyExec.[w/This node allows access to arbitrary files through the workflow, which could pose a security threat.]" - }, - { - "author": "jgbrblmd", - "title": "ComfyUI-ComfyFluxSize [WIP]", - "reference": "https://github.com/jgbrblmd/ComfyUI-ComfyFluxSize", - "files": [ - "https://github.com/jgbrblmd/ComfyUI-ComfyFluxSize" - ], - "install_type": "git-clone", - "description": "Nodes:ComfyFlux Size\nNOTE: The files in the repo are not organized." - }, - { - "author": "yojimbodayne", - "title": "ComfyUI-Dropbox-API [WIP]", - "reference": "https://github.com/yojimbodayne/ComfyUI-Dropbox-API", - "files": [ - "https://github.com/yojimbodayne/ComfyUI-Dropbox-API" - ], - "install_type": "git-clone", - "description": "This custom nodepackage for ComfyUI allows users to interact with Dropbox API, enabling image, text, and video uploads, downloads, and management directly from ComfyUI workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "ilovejohnwhite", - "title": "Kolors Awesome Prompts [WIP]", - "reference": "https://github.com/ilovejohnwhite/Tracer", - "files": [ - "https://github.com/ilovejohnwhite/Tracer" - ], - "install_type": "git-clone", - "description": "Nodes:Image Load TTK, SuckerPunch, LinkMasterNode, PixelPerfectResolution, ImageGenResolutionFromImage, ImageGenResolutionFromLatent, HintImageEnchance\nNOTE: The files in the repo are not organized." - }, - { - "author": "shuanshtalon468uan", - "title": "ComfyUI-Rpg-Architect [WIP]", - "reference": "https://github.com/talon468/ComfyUI-Rpg-Architect", - "files": [ - "https://github.com/talon468/ComfyUI-Rpg-Architect" - ], - "install_type": "git-clone", - "description": "Custom Node for ComfyUI to create RPG Characters\nNOTE: The files in the repo are not organized." - }, - { - "author": "shuanshuan", - "title": "ComfyUI_CheckPointLoader_Ext [WIP]", - "reference": "https://github.com/shuanshuan/ComfyUI_CheckPointLoader_Ext", - "files": [ - "https://github.com/shuanshuan/ComfyUI_CheckPointLoader_Ext" - ], - "install_type": "git-clone", - "description": "NODES:Checkpoint Loader Ext" - }, - { - "author": "123jimin", - "title": "ComfyUI MobileForm [WIP]", - "reference": "https://github.com/123jimin/ComfyUI-MobileForm", - "files": [ - "https://github.com/123jimin/ComfyUI-MobileForm" - ], - "install_type": "git-clone", - "description": "MobileForm is an extension for ComfyUI, providing simple form for any workflows, suitable for use on mobile phones.[w/Currently MobileForm is in a PoC state; expect bugs and breaking changes.]" - }, - { - "author": "go-package-lab", - "title": "ComfyUI-Tools-Video-Combine [WIP]", - "reference": "https://github.com/go-package-lab/ComfyUI-Tools-Video-Combine", - "files": [ - "https://github.com/go-package-lab/ComfyUI-Tools-Video-Combine" - ], - "install_type": "git-clone", - "description": "NODES:LoadAudioUrl, VideoWatermark" - }, - { - "author": "zhongpei", - "title": "Comfyui_image2prompt", - "id": "img2prompt", - "reference": "https://github.com/zhongpei/Comfyui_image2prompt", - "files": [ - "https://github.com/zhongpei/Comfyui_image2prompt" - ], - "install_type": "git-clone", - "description": "Nodes:Image to Text, Loader Image to Text Model.[w/This custom node may break dependencies by reinstalling the torch package.]" - }, - { - "author": "APZmedia", - "title": "comfyui-textools [WIP]", - "reference": "https://github.com/APZmedia/comfyui-textools", - "files": [ - "https://github.com/APZmedia/comfyui-textools" - ], - "install_type": "git-clone", - "description": "ComfyUI-textools is a collection of custom nodes designed for use with ComfyUI. These nodes enhance text processing capabilities, including applying rich text overlays on images and cleaning file names for safe and consistent file management.\nNOTE: The files in the repo are not organized." - }, - { - "author": "Comfy Org", - "title": "ComfyUI_devtools [WIP]", - "reference": "https://github.com/Comfy-Org/ComfyUI_devtools", - "files": [ - "https://github.com/Comfy-Org/ComfyUI_devtools" - ], - "install_type": "git-clone", - "description": "ComfyUI developer tools (Custom Node)" - }, - { - "author": "Sakura-nee", - "title": "ComfyUI_Save2Discord", - "reference": "https://github.com/Sakura-nee/ComfyUI_Save2Discord", - "files": [ - "https://github.com/Sakura-nee/ComfyUI_Save2Discord" - ], - "install_type": "git-clone", - "description": "Nodes:Send Generated Image To Discord Webhook.\nNOTE: The files in the repo are not organized." - }, - { - "author": "ThisModernDay", - "title": "ComfyUI Instructor Ollama", - "reference": "https://github.com/ThisModernDay/ComfyUI-InstructorOllama", - "files": [ - "https://github.com/ThisModernDay/ComfyUI-InstructorOllama" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI Nodes for interacting with Ollama using the Instructor. Library to provide structured output from your LLM. To use this properly, you would need a running Ollama server reachable from the host that is running ComfyUI.\nNOTE: The files in the repo are not organized, which may lead to update issues." - }, - { - "author": "gioferreira", - "title": "ComfyUI-Molde-Utils", - "reference": "https://github.com/gioferreira/ComfyUI-Molde-Utils", - "files": [ - "https://github.com/gioferreira/ComfyUI-Molde-Utils" - ], - "install_type": "git-clone", - "description": "ComfyUI-Molde-Utils is a utility library designed to provide various helper functions for working with UI elements. This project includes modules for handling bezier curves and color conversions.\nNOTE: The files in the repo are not organized, which may lead to update issues." - }, - { - "author": "kijai", - "title": "ComfyUI nodes for VEnhancer [WIP]", - "reference": "https://github.com/kijai/ComfyUI-VEnhancer", - "files": [ - "https://github.com/kijai/ComfyUI-VEnhancer" - ], - "install_type": "git-clone", - "description": "Original repo: [a/https://github.com/Vchitect/VEnhancer](https://github.com/Vchitect/VEnhancer)" - }, - { - "author": "jimstudt", - "title": "Jim's ComfyUI Nodes [WIP]", - "reference": "https://github.com/jimstudt/ComfyUI-Jims-Nodes", - "files": [ - "https://github.com/jimstudt/ComfyUI-Jims-Nodes" - ], - "install_type": "git-clone", - "description": "NODES: Zoom and Enhance Nodes, Text To String List, Choose String, Define Word, Lookup Word, Substitute Words, Dictionary to JSON, JSON file to Dictionary, JSON to Dictionary, Load Image And Info From Path, CubbyHack, Image to Solid Background" - }, - { - "author": "ChrisColeTech", - "title": "ComfyUI-Get-Random-File [UNSAFE]", - "reference": "https://github.com/ChrisColeTech/ComfyUI-Get-Random-File", - "files": [ - "https://github.com/ChrisColeTech/ComfyUI-Get-Random-File" - ], - "install_type": "git-clone", - "description": "Gets a random file from a directory. Returns the filpath as a STRING. [w/This node allows access to arbitrary files through the workflow, which could pose a security threat.]" - }, - { - "author": "neeltheninja", - "title": "ComfyUI-TextOverlay", - "reference": "https://github.com/neeltheninja/ComfyUI-TextOverlay", - "files": [ - "https://github.com/neeltheninja/ComfyUI-TextOverlay" - ], - "install_type": "git-clone", - "description": "A custom node for ComfyUI that adds text overlay to images, with options for text and background color, opacity, vertical positioning, and custom font selection. [w/Name conflict with munkyfoot/ComfyUI-TextOverlay. Cannot install simulatenously.]" - }, - { - "author": "comfyanonymous", - "title": "ComfyUI_bitsandbytes_NF4 [EXPERIMENTAL]", - "reference": "https://github.com/comfyanonymous/ComfyUI_bitsandbytes_NF4", - "files": [ - "https://github.com/comfyanonymous/ComfyUI_bitsandbytes_NF4" - ], - "install_type": "git-clone", - "description": "A quickly written custom node that uses code from Forge to support the nf4 flux dev checkpoint and nf4 flux schnell checkpoint.\nRequires installing bitsandbytes.\nMake sure your ComfyUI is updated.\nThe node is: CheckpointLoaderNF4, just plug it in your flux workflow instead of the regular one.[w/NF4 checckpoint doesn't support LoRA.]" - }, - { - "author": "kijai", - "title": "ComfyUI-EasyAnimateWrapper [WIP]", - "reference": "https://github.com/kijai/ComfyUI-EasyAnimateWrapper", - "files": [ - "https://github.com/kijai/ComfyUI-EasyAnimateWrapper" - ], - "install_type": "git-clone", - "description": "EasyAnimateWrapper for ComfyUI" - }, - { - "author": "logtd", - "title": "ComfyUI-Veevee [WIP]", - "reference": "https://github.com/logtd/ComfyUI-Veevee", - "files": [ - "https://github.com/logtd/ComfyUI-Veevee" - ], - "install_type": "git-clone", - "description": "A Video2Video framework for text2image models in ComfyUI. Supports SD1.5 and SDXL." - }, - { - "author": "chrisdreid", - "title": "ComfyUI_EnvAutopsyAPI Debugger [UNSAFE]", - "id": "chrisdreid", - "reference": "https://github.com/chrisdreid/ComfyUI_EnvAutopsyAPI", - "files": [ - "https://github.com/chrisdreid/ComfyUI_EnvAutopsyAPI" - ], - "install_type": "git-clone", - "description": "A powerful debugging tool designed to provide in-depth analysis of your environment and dependencies by exposing API endpoints. This tool allows you to inspect environment variables, pip packages, python info and dependency trees, making it easier to diagnose and resolve issues in your ComfyUI setup.[w/This tool may expose sensitive system information if used on a public server]" - }, - { - "author": "denislov", - "title": "Comfyui_AutoSurvey", - "reference": "https://github.com/denislov/Comfyui_AutoSurvey", - "files": [ - "https://github.com/denislov/Comfyui_AutoSurvey" - ], - "install_type": "git-clone", - "description": "Nodes:AutoSurvey, WriteOutline, WriteSection, ChatModel, QueryKnowledge, ManageDatabase, AddDoc2Knowledge" - }, - { - "author": "leoleelxh", - "title": "ComfyUI-MidjourneyNode-leoleexh", - "reference": "https://github.com/leoleelxh/ComfyUI-MidjourneyNode-leoleexh", - "files": [ - "https://github.com/leoleelxh/ComfyUI-MidjourneyNode-leoleexh" - ], - "install_type": "git-clone", - "description": "This node allows ComfyUI to easily integrate with Midjourney, utilizing the ultra-high quality of Midjourney and the powerful control of SD to provide more convenient capabilities for AIGC.\nNOTE: This node relies on the midjourney proxy project and requires API deployment in advance. For detailed installation, please refer to the instructions of the project. https://github.com/novicezk/midjourney-proxy" - }, - { - "author": "kijai", - "title": "ComfyUI-FollowYourEmojiWrapper [WIP]", - "reference": "https://github.com/kijai/ComfyUI-FollowYourEmojiWrapper", - "files": [ - "https://github.com/kijai/ComfyUI-FollowYourEmojiWrapper" - ], - "install_type": "git-clone", - "description": "Original repo: [a/https://github.com/mayuelala/FollowYourEmoji](https://github.com/mayuelala/FollowYourEmoji)" - }, - { - "author": "haomole", - "title": "Comfyui-SadTalker", - "reference": "https://github.com/haomole/Comfyui-SadTalker", - "files": [ - "https://github.com/haomole/Comfyui-SadTalker" - ], - "install_type": "git-clone", - "description": "[a/SadTalker](https://github.com/OpenTalker/SadTalker) for ComfyUI" - }, - { - "author": "hotpizzatactics", - "title": "ComfyUI-WaterMark-Detector", - "id": "watermark-detector", - "reference": "https://github.com/hotpizzatactics/ComfyUI-WaterMark-Detector", - "files": [ - "https://github.com/hotpizzatactics/ComfyUI-WaterMark-Detector" - ], - "install_type": "git-clone", - "description": "Nodes:CLAHE Enhancement, High Pass Filter, Edge Detection, Combine Enhancements, Adaptive Thresholding, Morphological Operations, Gray Color Enhancement, Improved Gray Color Enhancement, Texture Enhancement, Denoising Filter, Flexible Combine Enhancements." - }, - { - "author": "BetaDoggo", - "title": "ComfyUI-LogicGates", - "id": "logicgates", - "reference": "https://github.com/BetaDoggo/ComfyUI-LogicGates", - "files": [ - "https://github.com/BetaDoggo/ComfyUI-LogicGates" - ], - "install_type": "git-clone", - "description": "Binary Nodes, Byte Nodes, ..." - }, - { - "author": "shadowcz007", - "title": "comfyui-hydit", - "reference": "https://github.com/shadowcz007/comfyui-hydit-lowvram", - "files": [ - "https://github.com/shadowcz007/comfyui-hydit-lowvram" - ], - "install_type": "git-clone", - "description": "HunYuan Diffusers Nodes" - }, - { - "author": "walterFeng", - "title": "ComfyUI-Image-Utils", - "reference": "https://github.com/walterFeng/ComfyUI-Image-Utils", - "files":[ - "https://github.com/walterFeng/ComfyUI-Image-Utils" - ], - "install_type":"git-clone", - "description":"Nodes: Calculate Image Brightness" - }, - { - "author": "zml-ai", - "title": "comfyui-hydit", - "reference": "https://github.com/zml-ai/comfyui-hydit", - "files":[ - "https://github.com/zml-ai/comfyui-hydit" - ], - "install_type":"git-clone", - "description":"The ComfyUI code is under review in the official repository. Meanwhile, a temporary version is available below for immediate community use. We welcome users to try our workflow and appreciate any inquiries or suggestions." - }, - { - "author": "melMass", - "title": "ComfyUI-Lygia", - "id": "lygia", - "reference": "https://github.com/melMass/ComfyUI-Lygia", - "files": [ - "https://github.com/melMass/ComfyUI-Lygia" - ], - "install_type": "git-clone", - "description": "NODES: LygiaProgram, LygiaUniforms" - }, - { - "author": "SpaceWarpStudio", - "title": "ComfyUI_Remaker_FaceSwap", - "reference": "https://github.com/SpaceWarpStudio/ComfyUI_Remaker_FaceSwap", - "files": [ - "https://github.com/SpaceWarpStudio/ComfyUI_Remaker_FaceSwap" - ], - "install_type": "git-clone", - "description": "Nodes:Remaker Face Swap" - }, - { - "author": "VisionExp", - "title": "ve_custom_comfyui_nodes", - "reference": "https://github.com/VisionExp/ve_custom_comfyui_nodes", - "files": [ - "https://github.com/VisionExp/ve_custom_comfyui_nodes" - ], - "install_type": "git-clone", - "description": "Nodes:LoadImgFromInputUrl" - }, - { - "author": "StartHua", - "title": "Comfyui_CXH_CRM", - "id": "csdmt-cxh", - "reference": "https://github.com/StartHua/Comfyui_CSDMT_CXH", - "files": [ - "https://github.com/StartHua/Comfyui_CSDMT_CXH" - ], - "install_type": "git-clone", - "description": "Node:CSD_Makeup\nNOTE:You need to download [a/pre-trained model file](https://github.com/StartHua/Comfyui_CSDMT_CXH)." - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "ComfyUI-AuraSR-ZHO", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-AuraSR-ZHO", - "files": [ - "https://github.com/ZHO-ZHO-ZHO/ComfyUI-AuraSR-ZHO" - ], - "install_type": "git-clone", - "description": "AuraSR in ComfyUI for img & video\n[w/If the custom_nodes path is not under ComfyUI, be careful as it may not install properly.]" - }, - { - "author": "tom-doerr", - "title": "DSPy Nodes [WIP]", - "reference": "https://github.com/tom-doerr/dspy_nodes", - "files": [ - "https://github.com/tom-doerr/dspy_nodes" - ], - "install_type": "git-clone", - "description": "This is an attempt to make all DSPy features available in ComfyUI. Using an UI to devlop DSPy programs should be way faster since it makes it easier to see what is happening and allows to quickly iterate on the DSPy program structure." - }, - { - "author": "Grant-CP", - "title": "ComfyUI-LivePortraitKJ-MPS", - "reference": "https://github.com/Grant-CP/ComfyUI-LivePortraitKJ-MPS", - "files": [ - "https://github.com/Grant-CP/ComfyUI-LivePortraitKJ-MPS" - ], - "install_type": "git-clone", - "description": "If you wish to incorporate these changes into your repo, feel free to open an issue and ask. The commits should be pretty clear, and I also label almost all changes with #HACK so a full text search will work too.\nPlease let me know if you decide to incorporate any of these changes into your LivePortrait implementation so I can direct people to you repository. I do not intend to maintain this repo.\nSome operations are simply not supported on MPS and I didn't rewrite them. Most of my changes are just to .cuda calls and that sort of thing. Many operations are still done on CPU, so don't expect awesome performance." - }, - { - "author": "thderoo", - "title": "_topfun_s_nodes", - "reference": "https://github.com/thderoo/ComfyUI-_topfun_s_nodes", - "files": [ - "https://github.com/thderoo/ComfyUI-_topfun_s_nodes" - ], - "install_type": "git-clone", - "description": "Nodes:Conditioning Perturbation" - }, - { - "author": "willblaschko", - "title": "ComfyUI-Unload-Models", - "reference": "https://github.com/willblaschko/ComfyUI-Unload-Models", - "files": [ - "https://github.com/willblaschko/ComfyUI-Unload-Models" - ], - "install_type": "git-clone", - "description": "This repository provides developers with a way to better manage their ComfyUI model memory. It includes nodes that allow developers to either unload all models or unload one model at a time. These nodes are designed as pass-through nodes, so they can be used anywhere in the flow. The nodes can be found in the 'Unload Model' section.[w/These are massive hammers, and it could be possible to break things, please don't use them if you need finesse.]" - }, - { - "author": "AIFSH", - "title": "ComfyUI-OpenDIT [WIP]", - "id": "opendit", - "reference": "https://github.com/AIFSH/ComfyUI-OpenDIT", - "files": [ - "https://github.com/AIFSH/ComfyUI-OpenDIT" - ], - "install_type": "git-clone", - "description": "make [a/OpenDIT](https://github.com/NUS-HPC-AI-Lab/OpenDiT) avaliable in ComfyUI" - }, - { - "author": "alexisrolland", - "title": "alexisrolland/ComfyUI-AuraSR", - "id": "aurasr-alexisrolland", - "reference": "https://github.com/alexisrolland/ComfyUI-AuraSR", - "files": [ - "https://github.com/alexisrolland/ComfyUI-AuraSR" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes to run [a/fal-ai/AuraSR](https://huggingface.co/fal-ai/AuraSR) model.[w/This node cannot be installed simultaneously with AIFSH/ComfyUI-AuraSR due to overlapping repository names.]" - }, - { - "author": "linhusyung", - "title": "ComfyUI Build and Train Your Network [WIP]", - "id": "cfgpp", - "reference": "https://github.com/linhusyung/comfyui-Build-and-train-your-network", - "files": [ - "https://github.com/linhusyung/comfyui-Build-and-train-your-network" - ], - "install_type": "git-clone", - "description": "Stable Diffusion is an image generation technique based on diffusion models. Its core idea involves simulating diffusion processes by iteratively adding noise and using neural networks to predict and remove the noise, thereby generating high-quality images. This approach is not limited to image generation; with appropriate network architecture and training data, it can be adapted for various other tasks. The application of neural networks extends beyond image generation. By adjusting network structures and loss functions, neural networks can also perform tasks such as classification and regression. This flexibility makes neural networks a powerful tool for handling a wide range of machine learning tasks. This project aims to expand custom neural network layers (such as linear layers, convolutional layers, etc.) within ComfyUI and provide simplified task training functionalities. Through this project, users can easily construct custom neural network layers and perform training in ComfyUI using a graphical interface." - }, - { - "author": "Fucci-Mateo", - "title": "ComfyUI-Airtable [WIP]", - "id": "airtable", - "reference": "https://github.com/Fucci-Mateo/ComfyUI-Airtable", - "files": [ - "https://github.com/Fucci-Mateo/ComfyUI-Airtable" - ], - "install_type": "git-clone", - "description": "A simple node to load image from local path or http url. You can find this node from 'image' category." - }, - { - "author": "majorsauce", - "title": "comfyui_indieTools [WIP]", - "id": "indie-tools", - "reference": "https://github.com/majorsauce/comfyui_indieTools", - "files": [ - "https://github.com/majorsauce/comfyui_indieTools" - ], - "install_type": "git-clone", - "description": "Nodes:[Indie] Cut by Mask, [Indie] Paste Image, [Indie] Local Scale, [Indie] Solidify, [Indie] Yolo Detector.[w/Install may fail due to invliad requirements.txt file]" - }, - { - "author": "AIFSH", - "title": "ComfyUI-ViViD", - "id": "vivid", - "reference": "https://github.com/AIFSH/ComfyUI-ViViD", - "files": [ - "https://github.com/AIFSH/ComfyUI-ViViD" - ], - "install_type": "git-clone", - "description": "a comfyui custom node for ViViD" - }, - { - "author": "nat-chan", - "title": "comfyui-paint", - "reference": "https://github.com/nat-chan/comfyui-paint", - "files": [ - "https://github.com/nat-chan/comfyui-paint" - ], - "install_type": "git-clone", - "description": "comfyui-paint\n[w/You need to clone submodule manually after clone. There is permission issue.]" - }, - { - "author": "prabinpebam", - "title": "anyPython [UNSAFE]", - "reference": "https://github.com/prabinpebam/anyPython", - "files": [ - "https://github.com/prabinpebam/anyPython" - ], - "install_type": "git-clone", - "description": "This node was inspired by AnyNode. I wanted to have a node where I can paste any python script and execute it. That way I can use this node in combination with a Custom node like the Ollama node that can generate the python code and feed into this node. This also makes it much easier to debug or modify the code iteratively. As of the current version, I've created separate nodes for no input, 1 input and 2 inputs. The input also currently takes only sting as input. Let me know in the discussion how you would use this node.\n[w/This extension allows the execution of arbitrary Python code from a workflow.]" - }, - { - "author": "kijai", - "title": "ComfyUI DiffSynth wrapper nodes", - "id": "diffsynth-wrapper", - "reference": "https://github.com/kijai/ComfyUI-DiffSynthWrapper", - "files": [ - "https://github.com/kijai/ComfyUI-DiffSynthWrapper" - ], - "install_type": "git-clone", - "description": "Currently only the new extended SVD model 'ExVideo' is supported.\nOriginal repo:[a/https://github.com/modelscope/DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio)" - }, - { - "author": "AllenEdgarPoe", - "title": "ComfyUI-Xorbis-nodes [WIP]", - "id": "xorbis", - "reference": "https://github.com/AllenEdgarPoe/ComfyUI-Xorbis-nodes", - "files": [ - "https://github.com/AllenEdgarPoe/ComfyUI-Xorbis-nodes" - ], - "install_type": "git-clone", - "description": "This repository is for MuseumX Update. We use ComfyUI as our framework, and the nodes are built for my comfort.\nNOTE: The files in the repo are not organized." - }, - { - "author": "mikeymcfish", - "title": "LaserCutterFull and Deptherize Nodes", - "id": "fishtools", - "reference": "https://github.com/mikeymcfish/FishTools", - "files": [ - "https://github.com/mikeymcfish/FishTools" - ], - "install_type": "git-clone", - "description": "This repository contains two custom nodes, LaserCutterFull and Deptherize, designed for use in image processing workflows. The LaserCutterFull node processes input images to generate layers for laser cutting, while the Deptherize node converts SVG data into a depth map image." - }, - { - "author": "pzzmyc", - "title": "comfyui-sd3-simple-simpletuner", - "id": "simpletuner", - "reference": "https://github.com/pzzmyc/comfyui-sd3-simple-simpletuner", - "files": [ - "https://github.com/pzzmyc/comfyui-sd3-simple-simpletuner" - ], - "install_type": "git-clone", - "description": "Nodes:sd3 simple simpletuner by hhy." - }, - { - "author": "horidream", - "title": "ComfyUI-Horidream", - "id": "horidream", - "reference": "https://github.com/horidream/ComfyUI-Horidream", - "files": [ - "https://github.com/horidream/ComfyUI-Horidream" - ], - "install_type": "git-clone", - "description": "Nodes:Pass Through With Sound." - }, - { - "author": "kijai", - "title": "ComfyUI-DiffusersSD3Wrapper", - "id": "diffusers-sd3-wrapper", - "reference": "https://github.com/kijai/ComfyUI-DiffusersSD3Wrapper", - "files": [ - "https://github.com/kijai/ComfyUI-DiffusersSD3Wrapper" - ], - "install_type": "git-clone", - "description": "Nodes:Load SD3DiffusersPipeline, SD3 ControlNet Sampler" - }, - { - "author": "AustinMroz", - "title": "ComfyUI-SD3-Medium-CN-Diffusers [WIP]", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-SD3-Medium-CN-Diffusers", - "files": [ - "https://github.com/AustinMroz/ComfyUI-WorkflowCheckpointing" - ], - "install_type": "git-clone", - "description": "ComfyUI SD3-Medium ControlNet (Diffusers)" - }, - { - "author": "redhottensors", - "title": "ComfyUI-ODE", - "id": "ode", - "reference": "https://github.com/redhottensors/ComfyUI-ODE", - "files": [ - "https://github.com/redhottensors/ComfyUI-ODE" - ], - "install_type": "git-clone", - "description": "ODE Solvers for ComfyUI\nThis node enables use of torchdiffeq ODE solvers with models. Intended for use with Stable Diffusion 3 and similar flow models." - }, - { - "author": "maruhidd", - "title": "Transparent Background for ComfyUI", - "id": "transparent-bg", - "reference": "https://github.com/maruhidd/ComfyUI_Transparent-Background", - "files": [ - "https://github.com/maruhidd/ComfyUI_Transparent-Background" - ], - "install_type": "git-clone", - "description": "Nodes:Remove Background, Fill Transparent" - }, - { - "author": "ejektaflex", - "title": "ComfyUI - Ty", - "id": "ty-nodes", - "reference": "https://github.com/ejektaflex/ComfyUI-Ty", - "files": [ - "https://github.com/ejektaflex/ComfyUI-Ty" - ], - "install_type": "git-clone", - "description": "Nodes:Lora Block Weight Regex Loader" - }, - { - "author": "jtydhr88", - "title": "ComfyUI-Unique3D [WIP]", - "id": "unique3d", - "reference": "https://github.com/jtydhr88/ComfyUI-Unique3D", - "files": [ - "https://github.com/jtydhr88/ComfyUI-Unique3D" - ], - "install_type": "git-clone", - "description": "ComfyUI Unique3D is custom nodes that running [a/AiuniAI/Unique3D](https://github.com/AiuniAI/Unique3D) into ComfyUI." - }, - { - "author": "kycg", - "title": "comfyui-Kwtoolset", - "id": "kwtoolset", - "reference": "https://github.com/kycg/comfyui-Kwtoolset", - "files": [ - "https://github.com/kycg/comfyui-Kwtoolset" - ], - "install_type": "git-clone", - "description": "Nodes:KwtoolsetLoraLoaderwithpreview, KwtoolsetCheckpointLoaderwithpreview, KwtoolsetLoadCheckpointsBatch, KwtoolsetGrowMaskPlus, KwtoolsetGetHipMask, KwtoolsetGetHipMasktest, KwtoolsetGetImageSize, KWPositiveString, KWNagetiveString, KWanywhereString, KwtoolsetChangeOpenpose, ..." - }, - { - "author": "mashb1t", - "title": "ComfyUI mashb1t nodes", - "id": "mashb1t", - "reference": "https://github.com/mashb1t/comfyui-nodes-mashb1t", - "files": [ - "https://github.com/mashb1t/comfyui-nodes-mashb1t" - ], - "install_type": "git-clone", - "description": "This Python script is an optional add-on to the Comfy UI stable diffusion client." - }, - { - "author": "immersiveexperience", - "title": "ie-comfyui-color-nodes", - "reference": "https://github.com/immersiveexperience/ie-comfyui-color-nodes", - "files": [ - "https://github.com/immersiveexperience/ie-comfyui-color-nodes" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes for simple color correction." - }, - { - "author": "LZpenguin", - "title": "ComfyUI-Text", - "id": "comfy-text", - "reference": "https://github.com/LZpenguin/ComfyUI-Text", - "files": [ - "https://github.com/LZpenguin/ComfyUI-Text" - ], - "install_type": "git-clone", - "description": "Nodes:Add_text_by_mask.[w/This custom node cannot be installed simultaneously as it has the same repository name as MarkoCa1/ComfyUI-Text.]" - }, - { - "author": "norgeous", - "title": "UI Builder [WIP]", - "id": "norgeous", - "reference": "https://github.com/norgeous/ComfyUI-UI-Builder", - "files": [ - "https://github.com/norgeous/ComfyUI-UI-Builder" - ], - "install_type": "git-clone", - "description": "Alternative configurable React UI overlay for Comfy UI." - }, - { - "author": "Shinsplat", - "title": "ComfyUI-Shinsplat [UNSAFE]", - "id": "shinsplat", - "reference": "https://github.com/Shinsplat/ComfyUI-Shinsplat", - "files": [ - "https://github.com/Shinsplat/ComfyUI-Shinsplat" - ], - "install_type": "git-clone", - "description": "Nodes: Clip Text Encode (Shinsplat), Clip Text Encode SDXL (Shinsplat), Lora Loader (Shinsplat).\n[w/This extension poses a risk of executing arbitrary commands through workflow execution. Please be cautious.]" - }, - { - "author": "hy134300", - "title": "comfyui-hydit", - "reference": "https://github.com/hy134300/comfyui-hydit", - "files": [ - "https://github.com/hy134300/comfyui-hydit" - ], - "install_type": "git-clone", - "description": "This repository contains a customized node and workflow designed specifically for HunYuan DIT. The official tests conducted on DDPM, DDIM, and DPMMS have consistently yielded results that align with those obtained through the Diffusers library. However, it's important to note that we cannot assure the consistency of results from other ComfyUI native samplers with the Diffusers inference. We cordially invite users to explore our workflow and are open to receiving any inquiries or suggestions you may have." - }, - { - "author": "corbin-hayden13", - "title": "ComfyUI-Better-Dimensions", - "id": "better-dim", - "reference": "https://github.com/corbin-hayden13/ComfyUI-Better-Dimensions", - "files": [ - "https://github.com/corbin-hayden13/ComfyUI-Better-Dimensions" - ], - "install_type": "git-clone", - "description": "Nodes:BetterImageDimensions, SDXLDimensions, PureRatio" - }, - { - "author": "endman100", - "title": "ComfyUI-augmentation", - "id": "augmentation", - "reference": "https://github.com/endman100/ComfyUI-augmentation", - "files": [ - "https://github.com/endman100/ComfyUI-augmentation" - ], - "install_type": "git-clone", - "description": "Nodes:RamdomFlipImage (endman100)" - }, - { - "author": "endman100", - "title": "ComfyUI Nodes: SaveConditioning and LoadConditioning", - "id": "save-load-conditioning", - "reference": "https://github.com/endman100/ComfyUI-SaveAndLoadPromptCondition", - "files": [ - "https://github.com/endman100/ComfyUI-SaveAndLoadPromptCondition" - ], - "install_type": "git-clone", - "description": "The SaveConditioning node is designed to save conditioning data to binary files. This is useful for storing and reusing conditioning information across different sessions or applications.\n[w/This node can only handle very limited conditioning at the text prompt level.]" - }, - { - "author": "kijai", - "title": "ComfyUI-CV-VAE", - "id": "cv-vae", - "reference": "https://github.com/kijai/ComfyUI-CV-VAE", - "files": [ - "https://github.com/kijai/ComfyUI-CV-VAE" - ], - "install_type": "git-clone", - "description": "Nodes:CV_VAE_Load, CV_VAE_Encode, CV_VAE_Decode" - }, - { - "author": "GentlemanHu", - "title": "ComfyUI Notifier", - "id": "notifier", - "reference": "https://github.com/GentlemanHu/ComfyUI-Notifier", - "files": [ - "https://github.com/GentlemanHu/ComfyUI-Notifier" - ], - "install_type": "git-clone", - "description": "Nodes:GentlemanHu_Notifier" - }, - { - "author": "jimmm-ai", - "title": "TimeUi a ComfyUI Timeline Node System [WIP]", - "id": "timeline", - "reference": "https://github.com/jimmm-ai/TimeUi-a-ComfyUi-Timeline-Node", - "files": [ - "https://github.com/jimmm-ai/TimeUi-a-ComfyUi-Timeline-Node" - ], - "install_type": "git-clone", - "description": "I've been working on the UX/UI of a timeline custom node system for ComfyUI over the past two weeks. The goal is to create a timeline similar to video/animation editing tools, without relying on traditional timeframe code. You can effortlessly add, delete, or rearrange rows, providing a streamlined user experience." - }, - { - "author": "StartHua", - "title": "Comfyui_CXH_CRM", - "id": "cxh-crm", - "reference": "https://github.com/StartHua/Comfyui_CXH_CRM", - "files": [ - "https://github.com/StartHua/Comfyui_CXH_CRM" - ], - "install_type": "git-clone", - "description": "Nodes:CRM" - }, - { - "author": "comfypod", - "title": "ComfyUI-Comflow", - "id": "comflow", - "reference": "https://github.com/comfypod/ComfyUI-Comflow", - "files": [ - "https://github.com/comfypod/ComfyUI-Comflow" - ], - "install_type": "git-clone", - "description": "ComfyUI-Comflow." - }, - { - "author": "FoundD-oka", - "title": "ComfyUI KISEKAE-OOTD", - "id": "kisekae-ootd", - "reference": "https://github.com/FoundD-oka/ComfyUI-kisekae-OOTD", - "files": [ - "https://github.com/FoundD-oka/ComfyUI-kisekae-OOTD" - ], - "install_type": "git-clone", - "description": "Nodes:LoadOOTDPipeline, LoadOOTDPipelineHub, LoadOOTDPipelineHub." - }, - { - "author": "bruce007lee", - "title": "comfyui-tiny-utils", - "id": "tiny-utils", - "reference": "https://github.com/bruce007lee/comfyui-tiny-utils", - "files": [ - "https://github.com/bruce007lee/comfyui-tiny-utils" - ], - "install_type": "git-clone", - "description": "Nodes:FaceAlign, FaceAlignImageProcess, FaceAlignMaskProcess, ImageFillColorByMask, CropImageByMask, LoadImageAdvance, ImageTransposeAdvance, ImageSAMMask" - }, - { - "author": "brycegoh", - "title": "brycegoh/comfyui-custom-nodes", - "reference": "https://github.com/brycegoh/comfyui-custom-nodes", - "files": [ - "https://github.com/brycegoh/comfyui-custom-nodes" - ], - "install_type": "git-clone", - "description": "Nodes:MaskAreaComparisonSegment, FillMaskedArea, OCRAndMask, CombineTwoImageIntoOne" - }, - { - "author": "LykosAI", - "title": "ComfyUI Nodes for Inference.Core", - "id": "inference-core", - "reference": "https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes", - "files": [ - "https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes" - ], - "install_type": "git-clone", - "description": "Primary Nodes for Inference.Core and Stability Matrix. With a focus on not impacting startup performance and using fully qualified Node names. [w/This custom node is likely to conflict with many other nodes.]" - }, - { - "author": "tracerstar", - "title": "comfyui-p5js-node", - "id": "p5js", - "reference": "https://github.com/tracerstar/comfyui-p5js-node", - "files": [ - "https://github.com/tracerstar/comfyui-p5js-node" - ], - "install_type": "git-clone", - "description": "A simple proof of concept node to pass a p5js canvas through ComfyUI for img2img generation use." - }, - { - "author": "chaojie", - "title": "ComfyUI-mobvoi-openapi", - "id": "mobvoi-openapi", - "reference": "https://github.com/chaojie/ComfyUI-mobvoi-openapi", - "files": [ - "https://github.com/chaojie/ComfyUI-mobvoi-openapi" - ], - "install_type": "git-clone", - "description": "Nodes:MobvoiOpenapiMetamanText, MobvoiOpenapiMetamanAudio, MobvoiOpenapiTts, HtmlViewer, OssUploadImage, OssUploadAudio" - }, - { - "author": "immersiveexperience", - "title": "ie-comfyui-color-nodes", - "id": "ie-color-nodes", - "reference": "https://github.com/immersiveexperience/ie-comfyui-color-nodes", - "files": [ - "https://github.com/immersiveexperience/ie-comfyui-color-nodes" - ], - "install_type": "git-clone", - "description": "Custom ComfyUI nodes for simple color correction." - }, - { - "author": "beyastard", - "title": "ComfyUI_BeySoft", - "reference": "https://github.com/beyastard/ComfyUI_BeySoft", - "files": [ - "https://github.com/beyastard/ComfyUI_BeySoft" - ], - "install_type": "git-clone", - "description": "Nodes:BeySoft" - }, - { - "author": "christian-byrne", - "title": "🌌 Infinite Parallax Nodes [WIP]", - "reference": "https://github.com/christian-byrne/infinite-zoom-parallax-nodes", - "files": [ - "https://github.com/christian-byrne/infinite-zoom-parallax-nodes" - ], - "install_type": "git-clone", - "description": "Nodes:Parallax Config, Load Parallax Frame, Save Parallax Object Layers, Layer Shifter for Parallax Outpainting, Save Parallax Frame, Shrink and Pad for Outpainting, Create Infinite Zoom Video" - }, - { - "author": "flyingdogsoftware", - "title": "Gyre for ComfyUI", - "id": "gyre", - "reference": "https://github.com/flyingdogsoftware/gyre_for_comfyui", - "files": [ - "https://github.com/flyingdogsoftware/gyre_for_comfyui" - ], - "install_type": "git-clone", - "description": "Nodes:BackgroundRemoval, GyreLoopStart, GyreLoopEnd, GyreIfElse" - }, - { - "author": "githubYiheng", - "title": "comfyui_median_filter", - "id": "median-filter", - "reference": "https://github.com/githubYiheng/comfyui_median_filter", - "files": [ - "https://github.com/githubYiheng/comfyui_median_filter" - ], - "install_type": "git-clone", - "description": "Nodes:Apply Median Filter. [w/This has been updated to be equivalent to the comfyui_kmeans_filter node. Mistake?]" - }, - { - "author": "nat-chan", - "title": "comfyui-exec [UNSAFE]", - "id": "evalnode", - "reference": "https://github.com/nat-chan/comfyui-exec", - "files": [ - "https://github.com/nat-chan/comfyui-exec" - ], - "install_type": "git-clone", - "description": "Nodes:EvalNode [w/Please do not use the node that executes arbitrary code and outputs in any type, as it is dangerous.]" - }, - { - "author": "haofanwang", - "title": "ComfyUI-InstantStyle", - "id": "instantstyle", - "reference": "https://github.com/haofanwang/ComfyUI-InstantStyle", - "files": [ - "https://github.com/haofanwang/ComfyUI-InstantStyle" - ], - "install_type": "git-clone", - "description": "Nodes:PromptLoader, BaseModelLoader, InstantStyleLoader, InstantStyleGenerationNode" - }, - { - "author": "jp0215", - "title": "comfyUI_padding-resize_node", - "reference": "https://github.com/jp0215/comfyUI_padding-resize_node", - "files": [ - "https://raw.githubusercontent.com/jp0215/comfyUI_padding-resize_node/main/PaddingNode.py", - "https://raw.githubusercontent.com/jp0215/comfyUI_padding-resize_node/main/ResizeNode.py" - ], - "install_type": "copy", - "description": "Padding image to 8x: input image and mask, if the side length is not an integer multiple of 8, expand the side length to the smallest multiple of 8 greater than the original side length. Output padding image and mask. Resize to the origin: input the generated image and the original image, crop the generated image to the size of the original image, output the cropped image." - }, - { - "author": "Quasimondo", - "title": "ComfyUI-QuasimondoNodes [WIP]", - "id": "quasimondo-nodes", - "reference": "https://github.com/Quasimondo/ComfyUI-QuasimondoNodes", - "files": [ - "https://github.com/Quasimondo/ComfyUI-QuasimondoNodes" - ], - "install_type": "git-clone", - "description": "Nodes:Custom Shader, Spring Mesh" - }, - { - "author": "TSFSean", - "title": "ComfyUI-TSFNodes", - "id": "tsfnodes", - "reference": "https://github.com/TSFSean/ComfyUI-TSFNodes", - "files": [ - "https://github.com/TSFSean/ComfyUI-TSFNodes" - ], - "install_type": "git-clone", - "description": "Nodes:GyroOSC" - }, - { - "author": "blib-la", - "title": "ComfyUI-Captain-Extensions", - "id": "captain", - "reference": "https://github.com/blib-la/ComfyUI-Captain-Extensions", - "files": [ - "https://github.com/blib-la/ComfyUI-Captain-Extensions" - ], - "install_type": "git-clone", - "description": "ComfyUI extensions for better [a/Captain](https://github.com/blib-la/captain) integration." - }, - { - "author": "ejektaflex", - "title": "ComfyUI-Ty", - "reference": "https://github.com/ejektaflex/ComfyUI-Ty", - "files": [ - "https://github.com/ejektaflex/ComfyUI-Ty" - ], - "install_type": "git-clone", - "description": "Nodes:Lora Block Weight Regex Loader" - }, - { - "author": "christian-byrne", - "title": "Python Interpreter ComfyUI Node [UNSAFE]", - "reference": "https://github.com/christian-byrne/python-interpreter-node", - "files": [ - "https://github.com/christian-byrne/python-interpreter-node" - ], - "install_type": "git-clone", - "description": "For debugging, parsing data, generating random values, converting types, testing custom nodes faster.\nReference and use variables in the code using the same names as the inputs in the UI\nWrapper class around tensors with operator overloading for doing common image manipulation tasks.I might remove this aspect\n[w/This extension allows you to run programs through Python code in your workflow, which may not be secure. Use with caution.]" - }, - { - "author": "sofakid", - "title": "dandy [UNSAFE]", - "reference": "https://github.com/sofakid/dandy", - "files": [ - "https://github.com/sofakid/dandy" - ], - "install_type": "git-clone", - "description": "Dandy is a JavaScript bridge for ComfyUI. It includes everything you need to make JavaScript enabled extensions, or just load and code in little editor nodes right in ComfyUI.[w/This code can cause security issues because it allows for the execution of arbitrary JavaScript input.]" - }, - { - "author": "shadowcz007", - "title": "ComfyUI-PuLID [TEST]", - "reference": "https://github.com/shadowcz007/ComfyUI-PuLID-Test", - "files": [ - "https://github.com/shadowcz007/ComfyUI-PuLID-Test" - ], - "install_type": "git-clone", - "description": "[a/PuLID](https://github.com/ToTheBeginning/PuLID) ComfyUI native implementation." - }, - { - "author": "sangeet", - "title": "Simple Frontend For ComfyUI workflow [TEST]", - "reference": "https://github.com/sangeet/testui", - "files": [ - "https://github.com/sangeet/testui" - ], - "install_type": "git-clone", - "description": "A simple base front-end for text-to-image workflow in ComfyUI. Meant to serve as a base to be modified for future complex workflows" - }, - { - "author": "jtscmw01", - "title": "ComfyUI-DiffBIR", - "id": "diffbir", - "reference": "https://github.com/jtscmw01/ComfyUI-DiffBIR", - "files": [ - "https://github.com/jtscmw01/ComfyUI-DiffBIR" - ], - "install_type": "git-clone", - "description": "This extension provides [a/DiffBIR](https://github.com/XPixelGroup/DiffBIR) feature." - }, - { - "author": "ericbeyer", - "title": "guidance_interval", - "reference": "https://github.com/ericbeyer/guidance_interval", - "files": [ - "https://github.com/ericbeyer/guidance_interval" - ], - "install_type": "git-clone", - "description": "Nodes:Guidance Interval\nNOTE: Because the sampling function is replaced, you must restart after executing this custom node to restore the original state." - }, - { - "author": "oztrkoguz", - "title": "Kosmos2_BBox_Cutter Models", - "reference": "https://github.com/oztrkoguz/ComfyUI_Kosmos2_BBox_Cutter", - "files": [ - "https://github.com/oztrkoguz/ComfyUI_Kosmos2_BBox_Cutter" - ], - "install_type": "git-clone", - "description": "Nodes:KosmosLoader, Kosmos2SamplerSimple, Write" - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "ComfyUI-PuLID-ZHO [WIP]", - "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-PuLID-ZHO", - "files": [ - "https://github.com/ZHO-ZHO-ZHO/ComfyUI-PuLID-ZHO" - ], - "install_type": "git-clone", - "description": "Unofficial implementation of [a/PuLID](https://github.com/ToTheBeginning/PuLID)(diffusers) for ComfyUI" - }, - { - "author": "longgui0318", - "title": "comfyui-one-more-step [WIP]", - "reference": "https://github.com/longgui0318/comfyui-one-more-step", - "files": [ - "https://github.com/longgui0318/comfyui-one-more-step" - ], - "install_type": "git-clone", - "description": "[a/(OMS)mhh0318/OneMoreStep](https://github.com/mhh0318/OneMoreStep) comfyui support ." - }, - { - "author": "unknown", - "title": "CLIPTextEncodeAndEnhancev4 (shirazdesigner)", - "reference": "https://github.com/shirazdesigner/CLIPTextEncodeAndEnhancev4", - "files": [ - "https://github.com/shirazdesigner/CLIPTextEncodeAndEnhancev4" - ], - "install_type": "git-clone", - "description": "Nodes:CLIPTextEncodeAndEnhance.\nNOTE:Translation:This is a wrapper that simply makes it easy to install an existing node via git." - }, - { - "author": "Video3DGenResearch", - "title": "ComfyUI Batch Input Node", - "reference": "https://github.com/Video3DGenResearch/comfyui-batch-input-node", - "files": [ - "https://github.com/Video3DGenResearch/comfyui-batch-input-node" - ], - "install_type": "git-clone", - "description": "Nodes:BatchInputText" - }, - { - "author": "kijai", - "title": "ComfyUI nodes to use DeepSeek-VL", - "reference": "https://github.com/kijai/ComfyUI-DeepSeek-VL", - "files": [ - "https://github.com/kijai/ComfyUI-DeepSeek-VL" - ], - "install_type": "git-clone", - "description": "[a/https://huggingface.co/deepseek-ai](https://huggingface.co/deepseek-ai)" - }, - { - "author": "GentlemanHu", - "title": "ComfyUI-Notifier", - "reference": "https://github.com/GentlemanHu/ComfyUI-Notifier", - "files": [ - "https://github.com/GentlemanHu/ComfyUI-Notifier" - ], - "install_type": "git-clone", - "description": "Nodes:GentlemanHu_Notifier" - }, - { - "author": "nat-chan", - "title": "Transceiver📡", - "reference": "https://github.com/nat-chan/transceiver", - "files": [ - "https://github.com/nat-chan/transceiver" - ], - "install_type": "git-clone", - "description": "Why? When processing a large number of requests, the SaveImage and LoadImage nodes may be IO-limited, and using shared memory improves performance by passing images only through memory access, not through IO." - }, - { - "author": "DrMWeigand", - "title": "ComfyUI_LineBreakInserter", - "reference": "https://github.com/DrMWeigand/ComfyUI_LineBreakInserter", - "files": [ - "https://github.com/DrMWeigand/ComfyUI_LineBreakInserter" - ], - "install_type": "git-clone", - "description": "Nodes:Line Break Inserter" - }, - { - "author": "WilliamStanford", - "title": "visuallabs_comfyui_nodes", - "reference": "https://github.com/WilliamStanford/ComfyUI-VisualLabs", - "files": [ - "https://github.com/WilliamStanford/ComfyUI-VisualLabs" - ], - "install_type": "git-clone", - "description": "Nodes:PointStringFromFloatArray" - }, - { - "author": "bruce007lee", - "title": "comfyui-cleaner", - "reference": "https://github.com/bruce007lee/comfyui-cleaner", - "files": [ - "https://github.com/bruce007lee/comfyui-cleaner" - ], - "install_type": "git-clone", - "description": "Nodes:cleaner" - }, - { - "author": "ExponentialML", - "title": "ComfyUI_LiveDirector (WIP)", - "reference": "https://github.com/ExponentialML/ComfyUI_LiveDirector", - "files": [ - "https://github.com/ExponentialML/ComfyUI_LiveDirector" - ], - "install_type": "git-clone", - "description": "Experimental method to use reference video to drive motion in generations without training in ComfyUI." - }, - { - "author": "hy134300", - "title": "comfyui-hb-node", - "reference": "https://github.com/hy134300/comfyui-hb-node", - "files": [ - "https://github.com/hy134300/comfyui-hb-node" - ], - "install_type": "git-clone", - "description": "Nodes:sound voice, text concat, latent to list, movie generate, movie batch, hy save image, generate story" - }, - { - "author": "gameltb", - "title": "io_comfyui", - "reference": "https://github.com/gameltb/io_comfyui", - "files": [ - "https://github.com/gameltb/io_comfyui" - ], - "install_type": "git-clone", - "description": "Let Blender work with ComfyUI by ComfyScript. This addon is still in development." - }, - { - "author": "Jiffies-64", - "title": "ComfyUI-SaveImagePlus", - "reference": "https://github.com/Jiffies-64/ComfyUI-SaveImagePlus", - "files": [ - "https://github.com/Jiffies-64/ComfyUI-SaveImagePlus" - ], - "install_type": "git-clone", - "description": "Nodes:SaveImagePlus" - }, - { - "author": "kadirnar", - "title": "ComfyUI-Adapter [WIP]", - "reference": "https://github.com/kadirnar/ComfyUI-Adapter", - "files": [ - "https://github.com/kadirnar/ComfyUI-Adapter" - ], - "install_type": "git-clone", - "description": "WIP" - }, - { - "author": "Beinsezii", - "title": "comfyui-amd-go-fast", - "reference": "https://github.com/Beinsezii/comfyui-amd-go-fast", - "files": [ - "https://github.com/Beinsezii/comfyui-amd-go-fast" - ], - "install_type": "git-clone", - "description": "This contains all-in-one 'principled' nodes for T2I, I2I, refining, and scaling. Additionally it has many tools for directly manipulating the color of latents, high res fix math, and scripted image post-processing." - }, - { - "author": "sugarkwork", - "title": "comfyui_psd [WIP]", - "reference": "https://github.com/sugarkwork/comfyui_psd", - "files": [ - "https://github.com/sugarkwork/comfyui_psd" - ], - "install_type": "git-clone", - "description": "Not working yet." - }, - { - "author": "SadaleNet", - "title": "ComfyUI Port for Google's Prompt-to-Prompt", - "reference": "https://github.com/SadaleNet/ComfyUI-Prompt-To-Prompt", - "files": [ - "https://github.com/SadaleNet/ComfyUI-Prompt-To-Prompt" - ], - "install_type": "git-clone", - "description": "This is a PoC port of [a/Google's Prompt-to-Prompt](https://github.com/google/prompt-to-prompt/) to ComfyUI. It isn't feature complete. But it's good enough for evaluating if prompt-to-prompt is of any good." - }, - { - "author": "stavsap", - "title": "ComfyUI Ollama [WIP]", - "reference": "https://github.com/stavsap/ComfyUI-React-SDK", - "files": [ - "https://github.com/stavsap/ComfyUI-React-SDK" - ], - "install_type": "git-clone", - "description": "This project is for building React application as an overlay upon ComfyUI.\nProviding and ability to provide desired UI with ComfyUI API and workflows.\nInspired by: [a/https://github.com/cubiq/Comfy_Dungeon](https://github.com/cubiq/Comfy_Dungeon)" - }, - { - "author": "cubiq", - "title": "Comfy Dungeon [WIP]", - "reference": "https://github.com/cubiq/Comfy_Dungeon", - "files": [ - "https://github.com/cubiq/Comfy_Dungeon" - ], - "install_type": "git-clone", - "description": "Build D&D Character Portraits with ComfyUI.\nIMPORTANT: At the moment this is mostly a tech demo to show how to build a web app on top of ComfyUI. The code is very messy and the application doesn't guaratee consistent results." - }, - { - "author": "dfl", - "title": "comfyui-stylegan", - "reference": "https://github.com/dfl/comfyui-stylegan", - "files": [ - "https://github.com/dfl/comfyui-stylegan" - ], - "install_type": "git-clone", - "description": "Generator for StyleGAN 3" - }, - { - "author": "A719689614", - "title": "ComfyUI_AC_FUNV8Beta1", - "reference": "https://github.com/A719689614/ComfyUI_AC_FUNV8Beta1", - "files": [ - "https://github.com/A719689614/ComfyUI_AC_FUNV8Beta1" - ], - "install_type": "git-clone", - "description": "Nodes:AC_Super_Controlnet/Checkpoint/Loras/Lora&LCM/KSampler/UpKSampler/SaveImage/PreviewImage/CKPT&LCM/CLIPEN/EmptLatent, AC_FUN_SUPER_LARGE, AC_Super_Come_Ckpt, AC_Super_Come_Lora" - }, - { - "author": "houdinii", - "title": "comfy-magick [WIP]", - "reference": "https://github.com/houdinii/comfy-magick", - "files": [ - "https://github.com/houdinii/comfy-magick" - ], - "install_type": "git-clone", - "description": "This is a way to implement ImageMagick functionality in ComfyUI, which is generally PIL (pillow) based. I'm not sure the best way to handle this, as batch images make it a lot more complex, but the general idea will be two nodes to translate the IMAGE type, a torch.tensor of shape [batch, height, width, channels], or [1, 600, 800, 3] for a single 800x600 image, into/from a wand Image object." - }, - { - "author": "tjorbogarden", - "title": "my-useful-comfyui-custom-nodes", - "reference": "https://github.com/tjorbogarden/my-useful-comfyui-custom-nodes", - "files": [ - "https://github.com/tjorbogarden/my-useful-comfyui-custom-nodes" - ], - "install_type": "git-clone", - "description": "Nodes:My-Image Sizer, KSamplerSDXLAdvanced." - }, - { - "author": "DeTK", - "title": "ComfyUI Node Switcher", - "reference": "https://github.com/DeTK/ComfyUI-Switch", - "files": [ - "https://github.com/DeTK/ComfyUI-Switch" - ], - "install_type": "git-clone", - "description": "Nodes:NodeSwitch." - }, - { - "author": "GrindHouse66", - "title": "GH Tools for ComfyUI", - "reference": "https://github.com/GrindHouse66/ComfyUI-GH_Tools", - "files": [ - "https://github.com/GrindHouse66/ComfyUI-GH_Tools" - ], - "install_type": "git-clone", - "description": "Nodes:GH Tools Image Sizer, GH Tools Simple Scale. Simple quality of life Tools for ComfyUI. Basically, If it makes my life easier, it will be here. The list will grow over time." - }, - { - "author": "Beinsezii", - "title": "comfyui-amd-go-fast", - "reference": "https://github.com/Beinsezii/comfyui-amd-go-fast", - "files": [ - "https://github.com/Beinsezii/comfyui-amd-go-fast" - ], - "install_type": "git-clone", - "description": "See details: [a/link](https://github.com/Beinsezii/comfyui-amd-go-fast?tab=readme-ov-file)" - }, - { - "author": "SeedV", - "title": "ComfyUI-SeedV-Nodes [UNSAFE]", - "reference": "https://github.com/SeedV/ComfyUI-SeedV-Nodes", - "files": [ - "https://github.com/SeedV/ComfyUI-SeedV-Nodes" - ], - "install_type": "git-clone", - "description": "Nodes:Script.\n[w/This extension poses a risk of executing arbitrary commands through workflow execution. Please be cautious.]" - }, - { - "author": "mut-ex", - "title": "ComfyUI GLIGEN GUI Node", - "reference": "https://github.com/mut-ex/comfyui-gligengui-node", - "files": [ - "https://github.com/mut-ex/comfyui-gligengui-node" - ], - "install_type": "git-clone", - "description": "This is a simple, straightforward ComfyUI node to be used along with the [a/GLIGEN GUI](https://github.com/mut-ex/gligen-gui) I developed.\nNOTE:[a/Make sure you have the GLIGEN GUI up and running](https://github.com/mut-ex/gligen-gui/tree/main)" - }, - { - "author": "unanan", - "title": "ComfyUI-Dist [WIP]", - "reference": "https://github.com/unanan/ComfyUI-Dist", - "files": [ - "https://github.com/unanan/ComfyUI-Dist" - ], - "install_type": "git-clone", - "description": "For distributed processing ComfyUI workflows within a local area network.\nNot Finished Yet." - }, - { - "author": "NicholasKao1029", - "title": "comfyui-hook", - "reference": "https://github.com/NicholasKao1029/comfyui-hook", - "files": [ - "https://github.com/NicholasKao1029/comfyui-hook" - ], - "install_type": "git-clone", - "description": "This extension provides additional API" - }, - { - "author": "Extraltodeus", - "title": "Conditioning-token-experiments-for-ComfyUI", - "reference": "https://github.com/Extraltodeus/Conditioning-token-experiments-for-ComfyUI", - "files": [ - "https://github.com/Extraltodeus/Conditioning-token-experiments-for-ComfyUI" - ], - "install_type": "git-clone", - "description": "I made these nodes for experimenting so it's far from perfect but at least it is entertaining!\nIt uses cosine similarities or smallest euclidean distances to find the closest tokens." - }, - { - "author": "shadowcz007", - "title": "comfyui-llamafile [WIP]", - "reference": "https://github.com/shadowcz007/comfyui-sd-prompt-mixlab", - "files": [ - "https://github.com/shadowcz007/comfyui-sd-prompt-mixlab" - ], - "install_type": "git-clone", - "description": "This node is an experimental node aimed at exploring the collaborative way of human-machine creation." - }, - { - "author": "gameltb", - "title": "ComfyUI paper playground", - "reference": "https://github.com/gameltb/ComfyUI_paper_playground", - "files": [ - "https://github.com/gameltb/ComfyUI_paper_playground" - ], - "install_type": "git-clone", - "description": "Evaluate some papers in ComfyUI, just playground.\nNOTE: Various models need to be installed, so please visit the repository to check." - }, - { - "author": "huizhang0110", - "title": "ComfyUI_Easy_Nodes_hui", - "reference": "https://github.com/huizhang0110/ComfyUI_Easy_Nodes_hui", - "files": [ - "https://github.com/huizhang0110/ComfyUI_Easy_Nodes_hui" - ], - "install_type": "git-clone", - "description": "Nodes:EasyEmptyLatentImage" - }, - { - "author": "tuckerdarby", - "title": "ComfyUI-TDNodes [WIP]", - "reference": "https://github.com/tuckerdarby/ComfyUI-TDNodes", - "files": [ - "https://github.com/tuckerdarby/ComfyUI-TDNodes" - ], - "install_type": "git-clone", - "description": "Nodes:KSampler (RAVE), KSampler (TF), Object Tracker, KSampler Batched, Video Tracker Prompt, TemporalNet Preprocessor, Instance Tracker Prompt, Instance Diffusion Loader, Hand Tracker Node" - }, - { - "author": "shadowcz007", - "title": "comfyui-CLIPSeg", - "reference": "https://github.com/shadowcz007/comfyui-CLIPSeg", - "files": [ - "https://github.com/shadowcz007/comfyui-CLIPSeg" - ], - "install_type": "git-clone", - "description": "Download [a/CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : models/clipseg" - }, - { - "author": "stutya", - "title": "ComfyUI-Terminal [UNSAFE]", - "reference": "https://github.com/stutya/ComfyUI-Terminal", - "files": [ - "https://github.com/stutya/ComfyUI-Terminal" - ], - "install_type": "git-clone", - "description": "Run Terminal Commands from ComfyUI.\n[w/This extension poses a risk of executing arbitrary commands through workflow execution. Please be cautious.]" - }, - { - "author": "marcueberall", - "title": "ComfyUI-BuildPath", - "reference": "https://github.com/marcueberall/ComfyUI-BuildPath", - "files": [ - "https://github.com/marcueberall/ComfyUI-BuildPath" - ], - "install_type": "git-clone", - "description": "Nodes: Build Path Adv." - }, - { - "author": "LotzF", - "title": "ComfyUI simple ChatGPT completion [UNSAFE]", - "reference": "https://github.com/LotzF/ComfyUI-Simple-Chat-GPT-completion", - "files": [ - "https://github.com/LotzF/ComfyUI-Simple-Chat-GPT-completion" - ], - "install_type": "git-clone", - "description": "A simple node to request ChatGPT completions. [w/Do not share your workflows including the API key! I'll take no responsibility for your leaked keys.]" - }, - { - "author": "17Retoucher", - "title": "ComfyUI_Fooocus", - "reference": "https://github.com/17Retoucher/ComfyUI_Fooocus", - "files": [ - "https://github.com/17Retoucher/ComfyUI_Fooocus" - ], - "install_type": "git-clone", - "description": "Custom nodes that help reproduce image generation in Fooocus." - }, - { - "author": "BadCafeCode", - "title": "execution-inversion-demo-comfyui", - "reference": "https://github.com/BadCafeCode/execution-inversion-demo-comfyui", - "files": [ - "https://github.com/BadCafeCode/execution-inversion-demo-comfyui" - ], - "install_type": "git-clone", - "description": "execution-inversion-demo-comfyui" - }, - { - "author": "prodogape", - "title": "ComfyUI-clip-interrogator [WIP]", - "reference": "https://github.com/prodogape/ComfyUI-clip-interrogator", - "files": [ - "https://github.com/prodogape/ComfyUI-clip-interrogator" - ], - "install_type": "git-clone", - "description": "Unofficial ComfyUI extension of clip-interrogator" - }, - { - "author": "poisenbery", - "title": "NudeNet-Detector-Provider [WIP]", - "reference": "https://github.com/poisenbery/NudeNet-Detector-Provider", - "files": [ - "https://github.com/poisenbery/NudeNet-Detector-Provider" - ], - "install_type": "git-clone", - "description": "BBOX Detector Provider for NudeNet. Bethesda version of NudeNet V3 detector provider to work with Impact Pack ComfyUI." - }, - { - "author": "LarryJane491", - "title": "ComfyUI-ModelUnloader", - "reference": "https://github.com/LarryJane491/ComfyUI-ModelUnloader", - "files": [ - "https://github.com/LarryJane491/ComfyUI-ModelUnloader" - ], - "install_type": "git-clone", - "description": "A simple custom node that unloads all models. Useful for developers or users who want to free some memory." - }, - { - "author": "MrAdamBlack", - "title": "CheckProgress [WIP]", - "reference": "https://github.com/MrAdamBlack/CheckProgress", - "files": [ - "https://github.com/MrAdamBlack/CheckProgress" - ], - "install_type": "git-clone", - "description": "I was looking for a node to put in place to ensure my prompt etc where going as expected before the rest of the flow executed. To end the session, I just return the input image as None (see expected error). Recommend using it alongside PreviewImage, then output to the rest of the flow and Save Image." - }, - { - "author": "birnam", - "title": "Gen Data Tester [WIP]", - "reference": "https://github.com/birnam/ComfyUI-GenData-Pack", - "files": [ - "https://github.com/birnam/ComfyUI-GenData-Pack" - ], - "install_type": "git-clone", - "description": "This answers the itch for being able to easily paste [a/CivitAI.com](https://civitai.com/) generated data (or other simple metadata) into Comfy in a way that makes it easy to test with multiple checkpoints." - }, - { - "author": "nidefawl", - "title": "ComfyUI-nidefawl [UNSAFE]", - "reference": "https://github.com/nidefawl/ComfyUI-nidefawl", - "files": [ - "https://github.com/nidefawl/ComfyUI-nidefawl" - ], - "install_type": "git-clone", - "description": "Nodes:PythonScript, BlendImagesWithBoundedMasks, CropImagesWithMasks, VAELoaderDataType, ModelSamplerTonemapNoiseTest, gcLatentTunnel, ReferenceOnlySimple, EmptyImageWithColor, MaskFromColor, SetLatentCustomNoise, LatentToImage, ImageToLatent, LatentScaledNoise, DisplayAnyType, SamplerCustomCallback, CustomCallback, SplitCustomSigmas, SamplerDPMPP_2M_SDE_nidefawl, LatentPerlinNoise.
    [w/This node is an unsafe node that includes the capability to execute arbitrary python script.]" - }, - { - "author": "foglerek", - "title": "comfyui-cem-tools", - "reference": "https://github.com/foglerek/comfyui-cem-tools", - "files": [ - "https://github.com/foglerek/comfyui-cem-tools" - ], - "install_type": "git-clone", - "description": "Nodes:ProcessImageBatch" - }, - { - "author": "komojini", - "title": "ComfyUI_Prompt_Template_CustomNodes", - "reference": "https://github.com/komojini/ComfyUI_Prompt_Template_CustomNodes", - "files": [ - "https://raw.githubusercontent.com/komojini/ComfyUI_Prompt_Template_CustomNodes/main/prompt_with_template.py" - ], - "install_type": "copy", - "description": "Nodes:Prompt with Template" - }, - { - "author": "talesofai", - "title": "comfyui-supersave [WIP]", - "reference": "https://github.com/talesofai/comfyui-supersave", - "files": [ - "https://github.com/talesofai/comfyui-supersave" - ], - "install_type": "git-clone", - "description": "WIP" - }, - { - "author": "Sai-ComfyUI", - "title": "ComfyUI-MS-Nodes [WIP]", - "reference": "https://github.com/Sai-ComfyUI/ComfyUI-MS-Nodes", - "files": [ - "https://github.com/Sai-ComfyUI/ComfyUI-MS-Nodes" - ], - "install_type": "git-clone", - "description": "WIP" - }, - { - "author": "eigenpunk", - "title": "ComfyUI-audio", - "reference": "https://github.com/eigenpunk/ComfyUI-audio", - "files": [ - "https://github.com/eigenpunk/ComfyUI-audio" - ], - "install_type": "git-clone", - "description": "generative audio tools for ComfyUI. highly experimental-expect things to break." - }, - { - "author": "Jaxkr", - "title": "comfyui-terminal-command [UNSAFE]", - "reference": "https://github.com/Jaxkr/comfyui-terminal-command", - "files": [ - "https://github.com/Jaxkr/comfyui-terminal-command" - ], - "install_type": "git-clone", - "description": "Nodes: Run Terminal Command. [w/This node is an unsafe node that includes the capability to execute terminal commands.]" - }, - { - "author": "BlueDangerX", - "title": "ComfyUI-BDXNodes [WIP]", - "reference": "https://github.com/BlueDangerX/ComfyUI-BDXNodes", - "files": [ - "https://github.com/BlueDangerX/ComfyUI-BDXNodes" - ], - "install_type": "git-clone", - "description": "Nodes: Node Jumper. Various quality of life testing nodes" - }, - { - "author": "IvanZhd", - "title": "comfyui-codeformer [WIP]", - "reference": "https://github.com/IvanZhd/comfyui-codeformer", - "files": [ - "https://github.com/IvanZhd/comfyui-codeformer" - ], - "install_type": "git-clone", - "description": "Nodes:Image Inverter" - }, - { - "author": "romeobuilderotti", - "title": "ComfyUI-EZ-Pipes", - "reference": "https://github.com/romeobuilderotti/ComfyUI-EZ-Pipes", - "files": [ - "https://github.com/romeobuilderotti/ComfyUI-EZ-Pipes" - ], - "install_type": "git-clone", - "description": "ComfyUI-EZ-Pipes is a set of custom pipe nodes for ComfyUI. It provides a set of Input/Edit/Output nodes for each pipe type." - }, - { - "author": "wormley", - "title": "comfyui-wormley-nodes", - "reference": "https://github.com/wormley/comfyui-wormley-nodes", - "files": [ - "https://github.com/wormley/comfyui-wormley-nodes" - ], - "install_type": "git-clone", - "description": "Nodes: CheckpointVAELoaderSimpleText, CheckpointVAESelectorText, LoRA_Tag_To_Stack" - }, - { - "author": "Brandelan", - "title": "ComfyUI_bd_customNodes", - "reference": "https://github.com/Brandelan/ComfyUI_bd_customNodes", - "files": [ - "https://github.com/Brandelan/ComfyUI_bd_customNodes" - ], - "install_type": "git-clone", - "description": "Nodes: BD Random Range, BD Settings, BD Sequencer." - }, - { - "author": "Jordach", - "title": "comfy-consistency-vae", - "reference": "https://github.com/Jordach/comfy-consistency-vae", - "files": [ - "https://github.com/Jordach/comfy-consistency-vae" - ], - "install_type": "git-clone", - "description": "Nodes: Comfy_ConsistencyVAE" - }, - { - "author": "gameltb", - "title": "ComfyUI_stable_fast", - "reference": "https://github.com/gameltb/ComfyUI_stable_fast", - "files": [ - "https://github.com/gameltb/ComfyUI_stable_fast" - ], - "install_type": "git-clone", - "description": "Nodes:ApplyStableFastUnet. Experimental usage of stable-fast." - }, - { - "author": "jn-jairo", - "title": "jn_node_suite_comfyui [WIP]", - "reference": "https://github.com/jn-jairo/jn_node_suite_comfyui", - "files": [ - "https://github.com/jn-jairo/jn_node_suite_comfyui" - ], - "install_type": "git-clone", - "description": "Image manipulation nodes, Temperature control nodes, Tiling nodes, Primitive and operation nodes, ..." - }, - { - "author": "laksjdjf", - "title": "ssd-1b-comfyui", - "reference": "https://github.com/laksjdjf/ssd-1b-comfyui", - "files": [ - "https://github.com/laksjdjf/ssd-1b-comfyui" - ], - "install_type": "git-clone", - "description": "Experimental node for SSD-1B. This node is not need for latest comfyui." - }, - { - "author": "flowtyone", - "title": "comfyui-flowty-lcm", - "reference": "https://github.com/flowtyone/comfyui-flowty-lcm", - "files": [ - "https://github.com/flowtyone/comfyui-flowty-lcm" - ], - "install_type": "git-clone", - "description": "This is a comfyui early testing node for LCM, adapted from [a/https://github.com/0xbitches/sd-webui-lcm](https://github.com/0xbitches/sd-webui-lcm). It uses the diffusers backend unfortunately and not comfy's model loading mechanism. But the intention here is just to be able to execute lcm inside comfy.\nNOTE: 0xbitches's 'Latent Consistency Model for ComfyUI' is original implementation." - }, - { - "author": "doucx", - "title": "ComfyUI_WcpD_Utility_Kit", - "reference": "https://github.com/doucx/ComfyUI_WcpD_Utility_Kit", - "files": [ - "https://github.com/doucx/ComfyUI_WcpD_Utility_Kit" - ], - "install_type": "git-clone", - "description": "Nodes: MergeStrings, ExecStrAsCode, RandnLatentImage. [w/NOTE: This extension includes the ability to execute code as a string in nodes. Be cautious during installation, as it can pose a security risk.]" - }, - { - "author": "WSJUSA", - "title": "pre-comfyui-stablsr", - "reference": "https://github.com/WSJUSA/Comfyui-StableSR", - "files": [ - "https://github.com/WSJUSA/Comfyui-StableSR" - ], - "install_type": "git-clone", - "description": "This is a development respository for debugging migration of StableSR to Comfyui" - }, - { - "author": "Dr.Lt.Data", - "title": "ComfyUI-Workflow-Component [WIP]", - "reference": "https://github.com/ltdrdata/ComfyUI-Workflow-Component", - "files": [ - "https://github.com/ltdrdata/ComfyUI-Workflow-Component" - ], - "install_type": "git-clone", - "description": "This extension provides the capability to use ComfyUI Workflow as a component and the ability to use the Image Refiner functionality based on components. NOTE: This is an experimental extension feature with no consideration for backward compatibility and can be highly unstable." - }, - { - "author": "Humming", - "title": "ComfyUI-Model-Downloader", - "reference": "https://github.com/Hanmingsan/ComfyUI-Model-Downloader", - "files": [ - "https://github.com/Hanmingsan/ComfyUI-Model-Downloader" - ], - "install_type": "git-clone", - "description": "This is a extention that provides model downloading features on remote hosts. However the code is still being actively developed, and stability is not guaranteed." - } - ] -} diff --git a/node_db/dev/model-list.json b/node_db/dev/model-list.json deleted file mode 100644 index 8e3e1dc4..00000000 --- a/node_db/dev/model-list.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "models": [] -} diff --git a/node_db/dev/scan.sh b/node_db/dev/scan.sh deleted file mode 100755 index ac8c4255..00000000 --- a/node_db/dev/scan.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -rm ~/.tmp/dev/*.py > /dev/null 2>&1 -python ../../scanner.py ~/.tmp/dev $* \ No newline at end of file diff --git a/node_db/forked/custom-node-list.json b/node_db/forked/custom-node-list.json deleted file mode 100644 index 58924308..00000000 --- a/node_db/forked/custom-node-list.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "custom_nodes": [ - { - "author": "Fossiel", - "title": "ComfyUI-MultiGPU-Patched", - "reference": "https://github.com/Fossiel/ComfyUI-MultiGPU-Patched", - "files": [ - "https://github.com/Fossiel/ComfyUI-MultiGPU-Patched" - ], - "install_type": "git-clone", - "description": "Patched fork of ComfyUI-MultiGPU providing universal .safetensors and GGUF multi-GPU distribution with DisTorch 2.0 engine, model-driven allocation options (bytes/ratio modes), WanVideoWrapper integration, and up to 10% faster GGUF inference. (Description by CC)" - }, - { - "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", - "reference": "https://github.com/joaomede/ComfyUI-Unload-Model-Fork", - "files": [ - "https://github.com/joaomede/ComfyUI-Unload-Model-Fork" - ], - "install_type": "git-clone", - "description": "For unloading a model or all models, using the memory management that is already present in ComfyUI. Copied from [a/https://github.com/willblaschko/ComfyUI-Unload-Models](https://github.com/willblaschko/ComfyUI-Unload-Models) but without the unnecessary extra stuff." - }, - { - "author": "SanDiegoDude", - "title": "ComfyUI-HiDream-Sampler [WIP]", - "reference": "https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler", - "files": [ - "https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler" - ], - "install_type": "git-clone", - "description": "A collection of enhanced nodes for ComfyUI that provide powerful additional functionality to your workflows.\nNOTE: The files in the repo are not organized." - }, - { - "author": "PramaLLC", - "title": "ComfyUI BEN - Background Erase Network", - "reference": "https://github.com/PramaLLC/BEN2_ComfyUI", - "files": [ - "https://github.com/PramaLLC/BEN2_ComfyUI" - ], - "install_type": "git-clone", - "description": "Remove backgrounds from images with [a/BEN2](https://huggingface.co/PramaLLC/BEN2) in ComfyUI\nOriginal repo: [a/https://github.com/DoctorDiffusion/ComfyUI-BEN](https://github.com/DoctorDiffusion/ComfyUI-BEN)" - }, - { - "author": "BlenderNeko", - "title": "ltdrdata/ComfyUI_TiledKSampler", - "reference": "https://github.com/ltdrdata/ComfyUI_TiledKSampler", - "files": [ - "https://github.com/ltdrdata/ComfyUI_TiledKSampler" - ], - "install_type": "git-clone", - "description": "PR for [a/https://github.com/BlenderNeko/ComfyUI_TiledKSampler/pull/59](https://github.com/BlenderNeko/ComfyUI_TiledKSampler/pull/59)" - }, - { - "author": "leeooo001", - "title": "ComfyUI-leo-Hamer", - "reference": "https://github.com/leeooo001/ComfyUI-leo-Hamer", - "files": [ - "https://github.com/leeooo001/ComfyUI-leo-Hamer" - ], - "install_type": "git-clone", - "description": "Unoffice Hamer-ComfyUI by leo\nNOTE:base on [a/hamer](https://github.com/geopavlakos/hamer)" - }, - { - "author": "leeooo001", - "title": "ComfyUI-leo-GVHMR", - "reference": "https://github.com/leeooo001/ComfyUI-leo-GVHMR", - "files": [ - "https://github.com/leeooo001/ComfyUI-leo-GVHMR" - ], - "install_type": "git-clone", - "description": "Unoffice Hamer-ComfyUI by leo\nNOTE:base on [a/GVHMR](https://github.com/zju3dv/GVHMR)" - }, - { - "author": "leeooo001", - "title": "RealisDance-ComfyUI", - "reference": "https://github.com/leeooo001/ComfyUI-leo-RealisDance", - "files": [ - "https://github.com/leeooo001/ComfyUI-leo-RealisDance" - ], - "install_type": "git-clone", - "description": "Unoffice RealisDance-ComfyUI by leo\nNOTE:base on [a/RealisDance](https://github.com/damo-cv/RealisDance), modified on [a/RealisDanceComfyui](https://github.com/AIFSH/RealisDance-ComfyUI)" - }, - { - "author": "jags111", - "title": "NyaamZ/efficiency-nodes-ED", - "reference": "https://github.com/NyaamZ/efficiency-nodes-ED", - "files": [ - "https://github.com/NyaamZ/efficiency-nodes-ED" - ], - "install_type": "git-clone", - "description": "This forked repo supports efficiency-nodes-comfyui. Additional features." - }, - { - "author": "SeaArtLab", - "title": "zer0int/ComfyUI-Long-CLIP", - "reference": "https://github.com/zer0int/ComfyUI-Long-CLIP", - "files": [ - "https://github.com/zer0int/ComfyUI-Long-CLIP" - ], - "install_type": "git-clone", - "description": "This forked repo supports FLUX.1 not only SD1.5, SDXL." - }, - { - "author": "meimeilook", - "title": "ComfyUI_IPAdapter_plus.old [backward compatbility]", - "reference": "https://github.com/meimeilook/ComfyUI_IPAdapter_plus.old", - "files": [ - "https://github.com/meimeilook/ComfyUI_IPAdapter_plus.old" - ], - "install_type": "git-clone", - "description": "This repo is created to provide backward compatibility for workflows configured with the old IPAdapter." - }, - { - "author": "ZHO-ZHO-ZHO", - "title": "Dr.Lt.Data/ComfyUI-YoloWorld-EfficientSAM", - "reference": "https://github.com/ltdrdata/ComfyUI-YoloWorld-EfficientSAM", - "files": [ - "https://github.com/ltdrdata/ComfyUI-YoloWorld-EfficientSAM" - ], - "install_type": "git-clone", - "description": "This fork includes [a/PR32](https://github.com/ZHO-ZHO-ZHO/ComfyUI-YoloWorld-EfficientSAM/pull/32)" - }, - { - "author": "ertu110", - "title": "sdxl_prompt_style", - "reference": "https://github.com/ertu110/sdxl_prompt_style", - "files": [ - "https://github.com/ertu110/sdxl_prompt_style" - ], - "install_type": "git-clone", - "description": "This project is a complete benchmark [a/https://github.com/twri/sdxl_prompt_styler](https://github.com/twri/sdxl_prompt_styler) A large amount of code inside comes from https://github.com/twri/sdxl_prompt_styler Project and [a/https://www.nodecafe.org/package/pythongosssss_ComfyUI-Custom-Scripts](https://www.nodecafe.org/package/pythongosssss_ComfyUI-Custom-Scripts) project\nThe functionality of this project is related to https://github.com/twri/sdxl_prompt_styler Highly overlapping, the only purpose of creating this project is because there are too many styles when selecting, resulting in a long and inconvenient dropdown box. Therefore, To address this issue, this project has added a secondary menu to the style." - }, - { - "author": "gustproof", - "title": "ComfyUI_IPAdapter_plus_Style_Components", - "reference": "https://github.com/gustproof/ComfyUI_IPAdapter_plus_Style_Components", - "files": [ - "https://github.com/gustproof/ComfyUI_IPAdapter_plus_Style_Components" - ], - "install_type": "git-clone", - "description": "Style Components is an IP-Adapter model conditioned on anime styles. The style embeddings can either be extracted from images or created manually. This repo currently only supports the SDXL model trained on AutismmixPony." - }, - { - "author": "gameltb", - "title": "comfyui-stablsr", - "reference": "https://github.com/gameltb/Comfyui-StableSR", - "files": [ - "https://github.com/gameltb/Comfyui-StableSR" - ], - "install_type": "git-clone", - "description": "This is a development respository for debugging migration of StableSR to ComfyUI\n\nNOTE:Forked from [https://github.com/gameltb/Comfyui-StableSR]\nPut the StableSR [a/webui_786v_139.ckpt](https://huggingface.co/Iceclear/StableSR/resolve/main/webui_768v_139.ckpt) model into Comyfui/models/stablesr/, Put the StableSR [a/stablesr_768v_000139.ckpt](https://huggingface.co/Iceclear/StableSR/resolve/main/stablesr_768v_000139.ckpt) model into Comyfui/models/checkpoints/" - }, - { - "author": "city96", - "title": "Efficient-Large-Model/Extra Models for ComfyUI", - "reference": "https://github.com/Efficient-Large-Model/ComfyUI_ExtraModels", - "files": [ - "https://github.com/Efficient-Large-Model/ComfyUI_ExtraModels" - ], - "install_type": "git-clone", - "description": "A forked version of ComfyUI_ExtraModels. (modified by Efficient-Large-Model)" - }, - { - "author": "Pablerdo", - "title": "ComfyUI-PSNodes", - "reference": "https://github.com/Pablerdo/ComfyUI-PSNodes", - "files": [ - "https://github.com/Pablerdo/ComfyUI-PSNodes" - ], - "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)" - } - ] -} \ No newline at end of file diff --git a/node_db/forked/extension-node-map.json b/node_db/forked/extension-node-map.json deleted file mode 100644 index 9e26dfee..00000000 --- a/node_db/forked/extension-node-map.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/node_db/forked/model-list.json b/node_db/forked/model-list.json deleted file mode 100644 index 8e3e1dc4..00000000 --- a/node_db/forked/model-list.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "models": [] -} diff --git a/node_db/forked/scan.sh b/node_db/forked/scan.sh deleted file mode 100755 index 5d8d8c48..00000000 --- a/node_db/forked/scan.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -source ../../../../venv/bin/activate -rm .tmp/*.py > /dev/null -python ../../scanner.py diff --git a/node_db/legacy/alter-list.json b/node_db/legacy/alter-list.json deleted file mode 100644 index 9e26dfee..00000000 --- a/node_db/legacy/alter-list.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/node_db/legacy/extension-node-map.json b/node_db/legacy/extension-node-map.json deleted file mode 100644 index 9e26dfee..00000000 --- a/node_db/legacy/extension-node-map.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/node_db/legacy/model-list.json b/node_db/legacy/model-list.json deleted file mode 100644 index 1f870f11..00000000 --- a/node_db/legacy/model-list.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "models": [ - { - "name": "Inswapper-fp16 (face swap) [REMOVED]", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface", - "description": "Checkpoint of the insightface swapper model\n(used by ComfyUI-FaceSwap, comfyui-reactor-node, CharacterFaceSwap,\nComfyUI roop and comfy_mtb)", - "reference": "https://github.com/facefusion/facefusion-assets", - "filename": "inswapper_128_fp16.onnx", - "url": "https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx", - "size": "277.7MB" - }, - { - "name": "Inswapper (face swap) [REMOVED]", - "type": "insightface", - "base": "inswapper", - "save_path": "insightface", - "description": "Checkpoint of the insightface swapper model\n(used by ComfyUI-FaceSwap, comfyui-reactor-node, CharacterFaceSwap,\nComfyUI roop and comfy_mtb)", - "reference": "https://github.com/facefusion/facefusion-assets", - "filename": "inswapper_128.onnx", - "url": "https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx", - "size": "555.3MB" - }, - { - "name": "pfg-novel-n10.pt", - "type": "PFG", - "base": "SD1.5", - "save_path": "custom_nodes/pfg-ComfyUI/models", - "description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)", - "reference": "https://huggingface.co/furusu/PFG", - "filename": "pfg-novel-n10.pt", - "url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-novel-n10.pt", - "size": "23.6MB" - }, - { - "name": "pfg-wd14-n10.pt", - "type": "PFG", - "base": "SD1.5", - "save_path": "custom_nodes/pfg-ComfyUI/models", - "description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)", - "reference": "https://huggingface.co/furusu/PFG", - "filename": "pfg-wd14-n10.pt", - "url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd14-n10.pt", - "size": "31.5MB" - }, - { - "name": "pfg-wd15beta2-n10.pt", - "type": "PFG", - "base": "SD1.5", - "save_path": "custom_nodes/pfg-ComfyUI/models", - "description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)", - "reference": "https://huggingface.co/furusu/PFG", - "filename": "pfg-wd15beta2-n10.pt", - "url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd15beta2-n10.pt", - "size": "31.5MB" - }, - { - "name": "shape_predictor_68_face_landmarks.dat [Face Analysis]", - "type": "Shape Predictor", - "base": "DLIB", - "save_path": "custom_nodes/comfyui_faceanalysis/dlib", - "description": "To use the Face Analysis for ComfyUI custom node, installation of this model is needed.", - "reference": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/tree/main", - "filename": "shape_predictor_68_face_landmarks.dat", - "url": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/shape_predictor_68_face_landmarks.dat", - "size": "99.7MB" - }, - { - "name": "dlib_face_recognition_resnet_model_v1.dat [Face Analysis]", - "type": "Face Recognition", - "base": "DLIB", - "save_path": "custom_nodes/comfyui_faceanalysis/dlib", - "description": "To use the Face Analysis for ComfyUI custom node, installation of this model is needed.", - "reference": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/tree/main", - "filename": "dlib_face_recognition_resnet_model_v1.dat", - "url": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/dlib_face_recognition_resnet_model_v1.dat", - "size": "22.5MB" - }, - { - "name": "ID-Animator/animator.ckpt", - "type": "ID-Animator", - "base": "SD1.5", - "save_path": "custom_nodes/comfyui_id_animator/models", - "description": "ID-Animator checkpoint", - "reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator", - "filename": "animator.ckpt", - "url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/animator.ckpt", - "size": "247.3MB" - }, - { - "name": "ID-Animator/mm_sd_v15_v2.ckpt", - "type": "ID-Animator", - "base": "SD1.5", - "save_path": "custom_nodes/comfyui_id_animator/models/animatediff_models", - "description": "AnimateDiff checkpoint for ID-Animator", - "reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator", - "filename": "mm_sd_v15_v2.ckpt", - "url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/mm_sd_v15_v2.ckpt", - "size": "1.82GB" - }, - { - "name": "ID-Animator/image_encoder", - "type": "ID-Animator", - "base": "SD1.5", - "save_path": "custom_nodes/comfyui_id_animator/models/image_encoder", - "description": "CLIP Image encoder for ID-Animator", - "reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator", - "filename": "model.safetensors", - "url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/image_encoder/model.safetensors", - "size": "2.53GB" - }, - { - "name": "Doubiiu/ToonCrafter model checkpoint", - "type": "checkpoint", - "base": "ToonCrafter", - "save_path": "custom_nodes/comfyui-tooncrafter/ToonCrafter/checkpoints/tooncrafter_512_interp_v1", - "description": "ToonCrafter checkpoint model for ComfyUI-ToonCrafter", - "reference": "https://huggingface.co/Doubiiu/ToonCrafter/tree/main", - "filename": "model.ckpt", - "url": "https://huggingface.co/Doubiiu/ToonCrafter/resolve/main/model.ckpt", - "size": "10.5GB" - }, - - { - "name": "BAAI/SegGPT", - "type": "SegGPT", - "base": "SegGPT", - "save_path": "custom_nodes/comfyui-seggpt", - "description": "SegGPT", - "reference": "https://huggingface.co/BAAI/SegGPT", - "filename": "seggpt_vit_large.pth", - "url": "https://huggingface.co/BAAI/SegGPT/resolve/main/seggpt_vit_large.pth", - "size": "1.48GB" - }, - { - "name": "kohya-ss/ControlNet-LLLite: SDXL Canny Anime", - "type": "controlnet", - "base": "SDXL", - "save_path": "custom_nodes/ControlNet-LLLite-ComfyUI/models", - "description": "An extremely compactly designed controlnet model (a.k.a. ControlNet-LLLite). Note: The model structure is highly experimental and may be subject to change in the future.", - "reference": "https://huggingface.co/kohya-ss/controlnet-lllite", - "filename": "controllllite_v01032064e_sdxl_canny_anime.safetensors", - "url": "https://huggingface.co/kohya-ss/controlnet-lllite/resolve/main/controllllite_v01032064e_sdxl_canny_anime.safetensors", - "size": "46.2MB" - } - ] -} diff --git a/node_db/new/alter-list.json b/node_db/new/alter-list.json deleted file mode 100644 index 072c3bb5..00000000 --- a/node_db/new/alter-list.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "items": [ - ] -} \ No newline at end of file diff --git a/node_db/new/model-list.json b/node_db/new/model-list.json deleted file mode 100644 index 8b696c97..00000000 --- a/node_db/new/model-list.json +++ /dev/null @@ -1,692 +0,0 @@ -{ - "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", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (tiny)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_tiny.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_tiny.pt", - "size": "149.0MB" - }, - { - "name": "sam2.1_hiera_small.pt", - "type": "sam2.1", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (small)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_small.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_small.pt", - "size": "176.0MB" - }, - { - "name": "sam2.1_hiera_base_plus.pt", - "type": "sam2.1", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (base+)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_base_plus.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_base_plus.pt", - "size": "309.0MB" - }, - { - "name": "sam2.1_hiera_large.pt", - "type": "sam2.1", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2.1 hiera model (large)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2.1_hiera_large.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt", - "size": "857.0MB" - }, - - { - "name": "sam2_hiera_tiny.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (tiny)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_tiny.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt", - "size": "149.0MB" - }, - { - "name": "sam2_hiera_small.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (small)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_small.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_small.pt", - "size": "176.0MB" - }, - { - "name": "sam2_hiera_base_plus.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (base+)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_base_plus.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_base_plus.pt", - "size": "309.0MB" - }, - { - "name": "sam2_hiera_large.pt", - "type": "sam2", - "base": "SAM", - "save_path": "sams", - "description": "Segmenty Anything SAM 2 hiera model (large)", - "reference": "https://github.com/facebookresearch/sam2#model-description", - "filename": "sam2_hiera_large.pt", - "url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt", - "size": "857.0MB" - }, - - { - "name": "Comfy-Org/omnigen2_fp16.safetensors", - "type": "diffusion_model", - "base": "OmniGen2", - "save_path": "default", - "description": "OmniGen2 diffusion model. This is required for using OmniGen2.", - "reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged", - "filename": "omnigen2_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/omnigen2_fp16.safetensors", - "size": "7.93GB" - }, - { - "name": "Comfy-Org/qwen_2.5_vl_fp16.safetensors", - "type": "clip", - "base": "qwen-2.5", - "save_path": "default", - "description": "text encoder for OmniGen2", - "reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged", - "filename": "qwen_2.5_vl_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/text_encoders/qwen_2.5_vl_fp16.safetensors", - "size": "7.51GB" - }, - - { - "name": "Latent Bridge Matching for Image Relighting", - "type": "diffusion_model", - "base": "LBM", - "save_path": "diffusion_models/LBM", - "description": "Latent Bridge Matching (LBM) Relighting model", - "reference": "https://huggingface.co/jasperai/LBM_relighting", - "filename": "LBM_relighting.safetensors", - "url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors", - "size": "5.02GB" - }, - - { - "name": "LTX-Video 13B Distilled v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Distilled version of the LTX-Video 13B model, providing improved efficiency while maintaining high-resolution quality.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-distilled.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled.safetensors", - "size": "28.6GB" - }, - { - "name": "LTX-Video 13B Distilled FP8 v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Quantized distilled version of the LTX-Video 13B model, optimized for even lower VRAM usage while maintaining quality.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-distilled-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-fp8.safetensors", - "size": "15.7GB" - }, - { - "name": "LTX-Video 13B Distilled LoRA v0.9.7", - "type": "lora", - "base": "LTX-Video", - "save_path": "loras", - "description": "A LoRA adapter that transforms the standard LTX-Video 13B model into a distilled version when loaded.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-distilled-lora128.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-lora128.safetensors", - "size": "1.33GB" - }, - { - "name": "lllyasviel/FramePackI2V_HY", - "type": "FramePackI2V", - "base": "FramePackI2V", - "save_path": "diffusers/lllyasviel", - "description": "[SNAPSHOT] This is the f1k1_x_g9_f1k1f2k2f16k4_td FramePack for HY. [w/You cannot download this item on ComfyUI-Manager versions below V3.18]", - "reference": "https://huggingface.co/lllyasviel/FramePackI2V_HY", - "filename": "", - "url": "lllyasviel/FramePackI2V_HY", - "size": "25.75GB" - }, - - { - "name": "LTX-Video Spatial Upscaler v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Spatial upscaler model for LTX-Video. This model enhances the spatial resolution of generated videos.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-spatial-upscaler-0.9.7.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-spatial-upscaler-0.9.7.safetensors", - "size": "505MB" - }, - { - "name": "LTX-Video Temporal Upscaler v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Temporal upscaler model for LTX-Video. This model enhances the temporal resolution and smoothness of generated videos.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-temporal-upscaler-0.9.7.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-temporal-upscaler-0.9.7.safetensors", - "size": "524MB" - }, - { - "name": "LTX-Video 13B v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "High-resolution quality LTX-Video 13B model.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-dev.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev.safetensors", - "size": "28.6GB" - }, - { - "name": "LTX-Video 13B FP8 v0.9.7", - "type": "checkpoint", - "base": "LTX-Video", - "save_path": "checkpoints/LTXV", - "description": "Quantized version of the LTX-Video 13B model, optimized for lower VRAM usage while maintaining high quality.", - "reference": "https://huggingface.co/Lightricks/LTX-Video", - "filename": "ltxv-13b-0.9.7-dev-fp8.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev-fp8.safetensors", - "size": "15.7GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_bf16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_e4m3fn)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (fp8_e4m3fn)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors", - "size": "16.4GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_scaled)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 480p 14B (fp8_scaled)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_480p_14B_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_scaled.safetensors", - "size": "16.4GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_bf16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp16.safetensors", - "size": "32.8GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_e4m3fn)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (fp8_e4m3fn)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors", - "size": "16.4GB" - }, - { - "name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_scaled)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for i2v 720p 14B (fp8_scaled)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_i2v_720p_14B_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_scaled.safetensors", - "size": "16.4GB" - }, - { - "name": "Comfy-Org/clip_vision_h.safetensors", - "type": "clip_vision", - "base": "clip_vision_h", - "save_path": "clip_vision", - "description": "clip_vision_h model for Wan2.1", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "clip_vision_h.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors", - "size": "1.26GB" - }, - - { - "name": "Comfy-Org/Wan2.1 t2v 1.3B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 1.3B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_1.3B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_bf16.safetensors", - "size": "2.84GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 1.3B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 1.3B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_1.3B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors", - "size": "2.84GB" - }, - - { - "name": "Comfy-Org/Wan2.1 t2v 14B (bf16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (bf16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_bf16.safetensors", - "size": "28.6GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 14B (fp16)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (fp16)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp16.safetensors", - "size": "28.6GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 14B (fp8_e4m3fn)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (fp8_e4m3fn)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_fp8_e4m3fn.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_e4m3fn.safetensors", - "size": "14.3GB" - }, - { - "name": "Comfy-Org/Wan2.1 t2v 14B (fp8_scaled)", - "type": "diffusion_model", - "base": "Wan2.1", - "save_path": "diffusion_models/Wan2.1", - "description": "Wan2.1 difussion model for t2v 14B (fp8_scaled)", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan2.1_t2v_14B_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_scaled.safetensors", - "size": "14.3GB" - }, - { - "name": "Comfy-Org/Wan2.1 VAE", - "type": "vae", - "base": "Wan2.1", - "save_path": "vae", - "description": "Wan2.1 VAE model", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "wan_2.1_vae.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors", - "size": "254MB" - }, - - - { - "name": "Comfy-Org/umt5_xxl_fp16.safetensors", - "type": "clip", - "base": "umt5_xxl", - "save_path": "text_encoders", - "description": "umt5_xxl_fp16 text encoder for Wan2.1", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "umt5_xxl_fp16.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp16.safetensors", - "size": "11.4GB" - }, - { - "name": "Comfy-Org/umt5_xxl_fp8_e4m3fn_scaled.safetensors", - "type": "clip", - "base": "umt5_xxl", - "save_path": "text_encoders", - "description": "umt5_xxl_fp8_e4m3fn_scaled text encoder for Wan2.1", - "reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged", - "filename": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors", - "size": "6.74GB" - }, - - { - "name": "Comfy-Org/hunyuan_video_image_to_video_720p_bf16.safetensors", - "type": "diffusion_model", - "base": "Hunyuan Video", - "save_path": "diffusion_models/hunyuan_video", - "description": "Huyuan Video Image2Video diffusion model. repackaged version.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "filename": "hunyuan_video_image_to_video_720p_bf16.safetensors", - "url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_image_to_video_720p_bf16.safetensors", - "size": "25.6GB" - }, - { - "name": "Comfy-Org/llava_llama3_vision.safetensors", - "type": "clip_vision", - "base": "LLaVA-Llama-3", - "save_path": "text_encoders", - "description": "llava_llama3_vision clip vison model. This is required for using Hunyuan Video Image2Video.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "filename": "llava_llama3_vision.safetensors", - "url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/clip_vision/llava_llama3_vision.safetensors", - "size": "649MB" - }, - - { - "name": "LTX-Video 2B v0.9.5 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.5.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.5.safetensors", - "size": "6.34GB" - }, - { - "name": "kolors/vae/diffusion_pytorch_model.fp16.safetensors", - "type": "VAE", - "base": "Kolors", - "save_path": "vae/kolors", - "description": "Kolors VAE", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors", - "filename": "diffusion_pytorch_model.fp16.safetensors", - "url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.fp16.safetensors", - "size": "167MB" - }, - { - "name": "kolors/vae/diffusion_pytorch_model.safetensors", - "type": "VAE", - "base": "Kolors", - "save_path": "vae/kolors", - "description": "Kolors VAE", - "reference": "https://huggingface.co/Kwai-Kolors/Kolors", - "filename": "diffusion_pytorch_model.safetensors", - "url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.safetensors", - "size": "335MB" - }, - - { - "name": "deepseek-ai/Janus-Pro-1B", - "type": "Janus-Pro", - "base": "Janus-Pro", - "save_path": "Janus-Pro", - "description": "[SNAPSHOT] Janus-Pro-1B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]", - "reference": "https://huggingface.co/deepseek-ai/Janus-Pro-1B", - "filename": "", - "url": "deepseek-ai/Janus-Pro-1B", - "size": "7.8GB" - }, - { - "name": "deepseek-ai/Janus-Pro-7B", - "type": "Janus-Pro", - "base": "Janus-Pro", - "save_path": "Janus-Pro", - "description": "[SNAPSHOT] Janus-Pro-7B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]", - "reference": "https://huggingface.co/deepseek-ai/Janus-Pro-7B", - "filename": "", - "url": "deepseek-ai/Janus-Pro-7B", - "size": "14.85GB" - }, - - { - "name": "Leoxing/pia.ckpt", - "type": "animatediff-pia", - "base": "SD1.x", - "save_path": "animatediff_models", - "description": "AnimateDiff-PIA Model", - "reference": "https://huggingface.co/Leoxing/PIA/tree/main", - "filename": "pia.ckpt", - "url": "https://huggingface.co/Leoxing/PIA/resolve/main/pia.ckpt", - "size": "1.67GB" - }, - - { - "name": "comfyanonymous/cosmos_cv8x8x8_1.0.safetensors", - "type": "VAE", - "base": "Cosmos-1.0", - "save_path": "default", - "description": "VAE model for Cosmos 1.0", - "reference": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/tree/main", - "filename": "cosmos_cv8x8x8_1.0.safetensors", - "url": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/resolve/main/vae/cosmos_cv8x8x8_1.0.safetensors", - "size": "211MB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Text2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Text2World Diffusion Model (7B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-7B-Text2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Text2World.safetensors", - "size": "14.5GB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Video2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Video2World Diffusion Model (7B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-7B-Video2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Video2World.safetensors", - "size": "14.5GB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Text2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Text2World Diffusion Model (14B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-14B-Text2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Text2World.safetensors", - "size": "28.5GB" - }, - { - "name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Video2World.safetensors", - "type": "diffusion_model", - "base": "Cosmos-1.0", - "save_path": "diffusion_models/cosmos-1.0", - "description": "Cosmos 1.0 Video2World Diffusion Model (14B)", - "reference": "https://huggingface.co/mcmonkey/cosmos-1.0", - "filename": "Cosmos-1_0-Diffusion-14B-Video2World.safetensors", - "url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Video2World.safetensors", - "size": "28.5GB" - }, - - { - "name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors", - "type": "clip", - "base": "LLaVA-Llama-3", - "save_path": "text_encoders", - "description": "llava_llama3_fp8_scaled text encoder model. This is required for using Hunyuan Video.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "filename": "llava_llama3_fp8_scaled.safetensors", - "url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/text_encoders/llava_llama3_fp8_scaled.safetensors", - "size": "9.09GB" - }, - { - "name": "Comfy-Org/llava_llama3_fp16.safetensors", - "type": "clip", - "base": "LLaVA-Llama-3", - "save_path": "text_encoders", - "description": "llava_llama3_fp16 text encoder model. This is required for using Hunyuan Video.", - "reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged", - "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" - } - ] -} diff --git a/node_db/tutorial/custom-node-list.json b/node_db/tutorial/custom-node-list.json deleted file mode 100644 index ce3a98f5..00000000 --- a/node_db/tutorial/custom-node-list.json +++ /dev/null @@ -1,386 +0,0 @@ -{ - "custom_nodes": [ - { - "author": "Comfy-Org", - "title": "ComfyUI React Extension Template", - "reference": "https://github.com/Comfy-Org/ComfyUI-React-Extension-Template", - "files": [ - "https://github.com/Comfy-Org/ComfyUI-React-Extension-Template" - ], - "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", - "reference": "https://github.com/Suzie1/ComfyUI_Guide_To_Making_Custom_Nodes", - "files": [ - "https://github.com/Suzie1/ComfyUI_Guide_To_Making_Custom_Nodes" - ], - "install_type": "git-clone", - "description": "There is a small node pack attached to this guide. This includes the init file and 3 nodes associated with the tutorials." - }, - { - "author": "bamboodia", - "title": "BAM Nodes", - "reference": "https://github.com/bamboodia/BAM_Nodes", - "files": [ - "https://github.com/bamboodia/BAM_Nodes" - ], - "install_type": "git-clone", - "description": "A collection of comfyui nodes that I have made for nothing more than educational purposes." - }, - { - "author": "BadCafeCode", - "title": "execution-inversion-demo-comfyui", - "reference": "https://github.com/BadCafeCode/execution-inversion-demo-comfyui", - "files": [ - "https://github.com/BadCafeCode/execution-inversion-demo-comfyui" - ], - "install_type": "git-clone", - "description": "These are demo nodes for [a/PR2666](https://github.com/comfyanonymous/ComfyUI/pull/2666)" - }, - { - "author": "ecjojo", - "title": "ecjojo_example_nodes", - "reference": "https://github.com/ecjojo/ecjojo-example-nodes", - "files": [ - "https://github.com/ecjojo/ecjojo-example-nodes" - ], - "install_type": "git-clone", - "description": "Welcome to ecjojo_example_nodes! This example is specifically designed for beginners who want to learn how to write a simple custom node.\nFeel free to modify this example and make it your own. Experiment with different features and functionalities to enhance your understanding of ComfyUI custom nodes. Don't be afraid to explore and customize the code to suit your needs.\nBy diving into this example and making it your own, you'll gain valuable hands-on experience in creating custom nodes in ComfyUI. Enjoy the process of learning and have fun with your custom node development journey!" - }, - { - "author": "dynamixar", - "title": "Atluris", - "reference": "https://github.com/dynamixar/Atluris", - "files": [ - "https://github.com/dynamixar/Atluris" - ], - "install_type": "git-clone", - "description": "Nodes:Random Line" - }, - { - "author": "et118", - "title": "ComfyUI-ElGogh-Nodes", - "reference": "https://github.com/et118/ComfyUI-ElGogh-Nodes", - "files": [ - "https://github.com/et118/ComfyUI-ElGogh-Nodes" - ], - "install_type": "git-clone", - "description": "Nodes:ElGogh Positive Prompt, ElGogh NEGATIVE Prompt, ElGogh Empty Latent Image, ElGogh Checkpoint Loader Simple" - }, - { - "author": "LarryJane491", - "title": "Custom-Node-Base", - "reference": "https://github.com/LarryJane491/Custom-Node-Base", - "files": [ - "https://github.com/LarryJane491/Custom-Node-Base" - ], - "install_type": "git-clone", - "description": "This project is an `empty` custom node that is already in its own folder. It serves as a base to build any custom node. Whenever you want to create a custom node, you can download that, put it in custom_nodes, then you just have to change the names and fill it with code!" - }, - { - "author": "foxtrot-roger", - "title": "comfyui-custom-nodes", - "reference": "https://github.com/foxtrot-roger/comfyui-custom-nodes", - "files": [ - "https://github.com/foxtrot-roger/comfyui-custom-nodes" - ], - "install_type": "git-clone", - "description": "Tutorial nodes" - }, - { - "author": "wailovet", - "title": "ComfyUI-WW", - "reference": "https://github.com/wailovet/ComfyUI-WW", - "files": [ - "https://github.com/wailovet/ComfyUI-WW" - ], - "install_type": "git-clone", - "description": "Nodes:WW_ImageResize" - }, - { - "author": "azure-dragon-ai", - "title": "ComfyUI-HPSv2-Nodes", - "reference": "https://github.com/azure-dragon-ai/ComfyUI-HPSv2-Nodes", - "files": [ - "https://github.com/azure-dragon-ai/ComfyUI-HPSv2-Nodes" - ], - "install_type": "git-clone", - "description": "Nodes:Loader, Image Processor, Text Processor, ImageScore" - }, - { - "author": "kappa54m", - "title": "ComfyUI-HPSv2-Nodes", - "reference": "https://github.com/kappa54m/ComfyUI_Usability", - "files": [ - "https://github.com/kappa54m/ComfyUI_Usability" - ], - "install_type": "git-clone", - "description": "Nodes:Load Image Dedup" - }, - { - "author": "IvanRybakov", - "title": "comfyui-node-int-to-string-convertor", - "reference": "https://github.com/IvanRybakov/comfyui-node-int-to-string-convertor", - "files": [ - "https://github.com/IvanRybakov/comfyui-node-int-to-string-convertor" - ], - "install_type": "git-clone", - "description": "Nodes:Int To String Convertor" - }, - { - "author": "yowipr", - "title": "ComfyUI-Manual", - "reference": "https://github.com/yowipr/ComfyUI-Manual", - "files": [ - "https://github.com/yowipr/ComfyUI-Manual" - ], - "install_type": "git-clone", - "description": "Nodes:M_Layer, M_Output" - }, - { - "author": "andrewharp", - "title": "ComfyUI Function Annotator", - "reference": "https://github.com/andrewharp/ComfyUI-Annotations", - "files": [ - "https://github.com/andrewharp/ComfyUI-Annotations" - ], - "install_type": "git-clone", - "description": "This module provides an annotation @ComfyFunc to streamline adding custom node types in ComfyUI. It processes your function's signature to create a wrapped function and custom node definition required for ComfyUI, eliminating all the boilerplate code. In most cases you can just add a @ComfyFunc(\"category\") annotation to your existing function." - }, - { - "author": "OuticNZ", - "title": "ComfyUI-Simple-Of-Complex", - "reference": "https://github.com/OuticNZ/ComfyUI-Simple-Of-Complex", - "files": [ - "https://github.com/OuticNZ/ComfyUI-Simple-Of-Complex" - ], - "install_type": "git-clone", - "description": "Keeping it simple for starting. Single branch for now and will add development branch later." - }, - { - "author": "jtong", - "title": "comfyui-jtong-workflow", - "reference": "https://github.com/jtong/comfyui-jtong-workflow", - "files": [ - "https://github.com/jtong/comfyui-jtong-workflow" - ], - "install_type": "git-clone", - "description": "Nodes:jtong.Highway, Example" - }, - { - "author": "thinkthinking", - "title": "ComfyUI-Ye", - "reference": "https://github.com/thinkthinking/ComfyUI-Ye", - "files": [ - "https://github.com/thinkthinking/ComfyUI-Ye" - ], - "install_type": "git-clone", - "description": "Nodes:Signature|Ye, CheckpointLoader|Ye, PrintHelloWorld|Ye." - }, - { - "author": "BoosterCore", - "title": "ComfyUI-BC-Experimental", - "reference": "https://github.com/BoosterCore/ComfyUI-BC-Experimental", - "files": [ - "https://github.com/BoosterCore/ComfyUI-BC-Experimental" - ], - "install_type": "git-clone", - "description": "Nodes:ClipTextEncodeBC, SaveAnyText, SimpleText" - }, - { - "author": "sonyeon-sj", - "title": "ComfyUI-easy_ImageSize_Selecter", - "reference": "https://github.com/sonyeon-sj/ComfyUI-easy_ImageSize_Selecter", - "files": [ - "https://github.com/sonyeon-sj/ComfyUI-easy_ImageSize_Selecter" - ], - "install_type": "git-clone", - "description": "Custom node for ComfyUI Select the image size from the preset and select Vertical and Horizontal to output Width and Height." - }, - { - "author": "boricuapab", - "title": "ComfyUI_BoricuapabWFNodePack", - "reference": "https://github.com/boricuapab/ComfyUI_BoricuapabWFNodePack", - "files": [ - "https://github.com/boricuapab/ComfyUI_BoricuapabWFNodePack" - ], - "install_type": "git-clone", - "description": "Learning how to make my own comfy ui custom nodes" - }, - { - "author": "mira-6", - "title": "mira-wildcard-node", - "reference": "https://github.com/mira-6/mira-wildcard-node", - "files": [ - "https://github.com/mira-6/mira-wildcard-node" - ], - "install_type": "git-clone", - "description": "Mira's Simple Wildcard Node" - }, - { - "author": "BetaDoggo", - "title": "ComfyUI Tetris", - "id": "tetris", - "reference": "https://github.com/BetaDoggo/ComfyUI-Tetris", - "files": [ - "https://github.com/BetaDoggo/ComfyUI-Tetris" - ], - "install_type": "git-clone", - "description": "The primitive node and dummy input are required because comfy doesn't accept requests with identical graphs. You'll also need a show text node. I like the one from ComfyUI-Custom-Scripts. I got the generic tetris remake from claude so it may or may not be ripped from somewhere else." - }, - { - "author": "FlyMyAI", - "title": "ComfyUI-ExampleNode", - "reference": "https://github.com/FlyMyAI/ComfyUI-ExampleNode", - "files": [ - "https://github.com/FlyMyAI/ComfyUI-ExampleNode" - ], - "install_type": "git-clone", - "description": "Node to provide convenient ComfyUI standard, supported by flymy_comfyui." - }, - { - "author": "Wanghanying", - "title": "ComfyUI_RAGDemo", - "reference": "https://github.com/Wanghanying/ComfyUI_RAGDemo", - "files": [ - "https://github.com/Wanghanying/ComfyUI_RAGDemo" - ], - "install_type": "git-clone", - "description": "RAG Demo for LLM" - }, - { - "author": "FelixTeutsch", - "title": "BachelorThesis", - "reference": "https://github.com/FelixTeutsch/BachelorThesis", - "files": [ - "https://github.com/FelixTeutsch/BachelorThesis" - ], - "install_type": "git-clone", - "description": "This is a ComfyUi custom node, that build a new UI on top of the already existing AI, to enable the use of custom controllers" - }, - { - "author": "jhj0517", - "title": "ComfyUI-CustomNodes-Template", - "reference": "https://github.com/jhj0517/ComfyUI-CustomNodes-Template", - "files": [ - "https://github.com/jhj0517/ComfyUI-CustomNodes-Template" - ], - "install_type": "git-clone", - "description": "This is the ComfyUI custom node template repository that anyone can use to create their own custom nodes." - }, - { - "author": "laogou666", - "title": "Comfyui_LG_Advertisement", - "reference": "https://github.com/LAOGOU-666/Comfyui_LG_Advertisement", - "files": [ - "https://github.com/LAOGOU-666/Comfyui_LG_Advertisement" - ], - "install_type": "git-clone", - "description": "A node for demonstration." - }, - { - "author": "amorano", - "title": "cozy_spoke", - "reference": "https://github.com/cozy-comfyui/cozy_spoke", - "files": [ - "https://github.com/cozy-comfyui/cozy_spoke" - ], - "install_type": "git-clone", - "description": "Example node communicating between ComfyUI Javascript and Python." - }, - { - "author": "amorano", - "title": "Cozy Link Toggle", - "id": "cozyLinkToggle", - "reference": "https://github.com/cozy-comfyui/cozy_link_toggle", - "files": [ - "https://github.com/cozy-comfyui/cozy_link_toggle" - ], - "install_type": "git-clone", - "description": "Example of using ComfyUI Toolbar to Toggle ComfyUI links on/off" - }, - { - "author": "xhiroga", - "title": "ComfyUI-TypeScript-CustomNode", - "reference": "https://github.com/xhiroga/ComfyUI-TypeScript-CustomNode", - "files": [ - "https://github.com/xhiroga/ComfyUI-TypeScript-CustomNode" - ], - "install_type": "git-clone", - "description": "This project is generated from xhiroga/ComfyUI-TypeScript-CustomNode" - }, - { - "author": "zentrocdot", - "title": "ComfyUI-Turtle_Graphics_Demos", - "reference": "https://github.com/zentrocdot/ComfyUI-Turtle_Graphics_Demo", - "files": [ - "https://github.com/zentrocdot/ComfyUI-Turtle_Graphics_Demo" - ], - "description": "ComfyUI node for creating some Turtle Graphic demos.", - "install_type": "git-clone" - }, - { - "author": "cozy-comfyui", - "title": "cozy_ex_dynamic", - "reference": "https://github.com/cozy-comfyui/cozy_ex_dynamic", - "files": [ - "https://github.com/cozy-comfyui/cozy_ex_dynamic" - ], - "description": "Dynamic Node examples for ComfyUI", - "install_type": "git-clone" - }, - { - "author": "Jonathon-Doran", - "title": "remote-combo-demo", - "reference": "https://github.com/Jonathon-Doran/remote-combo-demo", - "files": [ - "https://github.com/Jonathon-Doran/remote-combo-demo" - ], - "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." - }, - { - "author": "ganlvtech", - "title": "ComfyUI-CustomModelPatcher", - "reference": "https://github.com/ganlvtech/ComfyUI-CustomModelPatcher", - "files": [ - "https://github.com/ganlvtech/ComfyUI-CustomModelPatcher" - ], - "install_type": "git-clone", - "description": "Demonstrates GPU memory management techniques for external models like onnxruntime and InsightFace in ComfyUI by pre-allocating VRAM. (Description by CC)" - } - ] -} \ No newline at end of file diff --git a/node_db/tutorial/extension-node-map.json b/node_db/tutorial/extension-node-map.json deleted file mode 100644 index d5b0c984..00000000 --- a/node_db/tutorial/extension-node-map.json +++ /dev/null @@ -1,573 +0,0 @@ -{ - "https://github.com/BadCafeCode/execution-inversion-demo-comfyui": [ - [ - "AccumulateNode", - "AccumulationGetItemNode", - "AccumulationGetLengthNode", - "AccumulationHeadNode", - "AccumulationSetItemNode", - "AccumulationTailNode", - "AccumulationToListNode", - "BoolOperationNode", - "ComponentInput", - "ComponentMetadata", - "ComponentOutput", - "DebugPrint", - "ExecutionBlocker", - "FloatConditions", - "ForLoopClose", - "ForLoopOpen", - "IntConditions", - "IntMathOperation", - "InversionDemoAdvancedPromptNode", - "InversionDemoLazyConditional", - "InversionDemoLazyIndexSwitch", - "InversionDemoLazyMixImages", - "InversionDemoLazySwitch", - "ListToAccumulationNode", - "MakeListNode", - "StringConditions", - "ToBoolNode", - "WhileLoopClose", - "WhileLoopOpen" - ], - { - "title_aux": "execution-inversion-demo-comfyui" - } - ], - "https://github.com/BetaDoggo/ComfyUI-Tetris": [ - [ - "Tetris" - ], - { - "title_aux": "ComfyUI Tetris" - } - ], - "https://github.com/BoosterCore/ComfyUI-BC-Experimental": [ - [ - "ClipTextEncodeBC", - "ClipTextEncodeBCA", - "FluxEmptyLatentSize", - "LoraWithTriggerWord", - "SaveAnyText", - "SimpleText" - ], - { - "title_aux": "ComfyUI-BC-Experimental" - } - ], - "https://github.com/FlyMyAI/ComfyUI-ExampleNode": [ - [ - "ExampleT2IFMANode" - ], - { - "title_aux": "ComfyUI-ExampleNode" - } - ], - "https://github.com/IvanRybakov/comfyui-node-int-to-string-convertor": [ - [ - "Int To String" - ], - { - "title_aux": "comfyui-node-int-to-string-convertor" - } - ], - "https://github.com/LarryJane491/Custom-Node-Base": [ - [ - "My First Node" - ], - { - "title_aux": "Custom-Node-Base" - } - ], - "https://github.com/OuticNZ/ComfyUI-Simple-Of-Complex": [ - [ - "Pipe From Parameters", - "Pipe To Parameters", - "Prompt Tidy", - "Text Switch 2 Way", - "Text With Context" - ], - { - "title_aux": "ComfyUI-Simple-Of-Complex" - } - ], - "https://github.com/Suzie1/ComfyUI_Guide_To_Making_Custom_Nodes": [ - [ - "Concatenate Hello World", - "Hello World Overlay Text", - "Print Hello World" - ], - { - "title_aux": "Guide To Making Custom Nodes in ComfyUI" - } - ], - "https://github.com/Wanghanying/ComfyUI_RAGDemo": [ - [ - "testRAG" - ], - { - "title_aux": "ComfyUI_RAGDemo" - } - ], - "https://github.com/azure-dragon-ai/ComfyUI-HPSv2-Nodes": [ - [ - "GetImageSize", - "HaojihuiHPSv2ImageProcessor", - "HaojihuiHPSv2ImageScore", - "HaojihuiHPSv2ImageScores", - "HaojihuiHPSv2Loader", - "HaojihuiHPSv2SaveAnimatedWEBP", - "HaojihuiHPSv2SaveImage", - "HaojihuiHPSv2SaveWEBP", - "HaojihuiHPSv2SaveWebpImage", - "HaojihuiHPSv2TextProcessor", - "SaveImageWebp", - "ScaleShort" - ], - { - "title_aux": "ComfyUI-HPSv2-Nodes" - } - ], - "https://github.com/bamboodia/BAM_Nodes": [ - [ - "BAM Crop To Ratio", - "BAM Empty Latent By Ratio", - "BAM Get Shortest Side", - "BAM OnOff INT", - "BAM Random Float", - "BAM Random Image From Folder" - ], - { - "title_aux": "BAM Nodes" - } - ], - "https://github.com/boricuapab/ComfyUI_BoricuapabWFNodePack": [ - [ - "BoricuapabWF Concatenate Hello World", - "BoricuapabWF Integer", - "BoricuapabWF Print Hello Puerto Rican World", - "BoricuapabWF Print Puerto Rican" - ], - { - "title_aux": "ComfyUI_BoricuapabWFNodePack" - } - ], - "https://github.com/comfyanonymous/ComfyUI": [ - [ - "AddNoise", - "AlignYourStepsScheduler", - "BasicGuider", - "BasicScheduler", - "BetaSamplingScheduler", - "CFGGuider", - "CLIPAttentionMultiply", - "CLIPLoader", - "CLIPMergeAdd", - "CLIPMergeSimple", - "CLIPMergeSubtract", - "CLIPSave", - "CLIPSetLastLayer", - "CLIPTextEncode", - "CLIPTextEncodeControlnet", - "CLIPTextEncodeFlux", - "CLIPTextEncodeHunyuanDiT", - "CLIPTextEncodePixArtAlpha", - "CLIPTextEncodeSD3", - "CLIPTextEncodeSDXL", - "CLIPTextEncodeSDXLRefiner", - "CLIPVisionEncode", - "CLIPVisionLoader", - "Canny", - "CheckpointLoader", - "CheckpointLoaderSimple", - "CheckpointSave", - "ConditioningAverage", - "ConditioningCombine", - "ConditioningConcat", - "ConditioningSetArea", - "ConditioningSetAreaPercentage", - "ConditioningSetAreaStrength", - "ConditioningSetMask", - "ConditioningSetTimestepRange", - "ConditioningStableAudio", - "ConditioningZeroOut", - "ControlNetApply", - "ControlNetApplyAdvanced", - "ControlNetApplySD3", - "ControlNetInpaintingAliMamaApply", - "ControlNetLoader", - "CropMask", - "DiffControlNetLoader", - "DifferentialDiffusion", - "DiffusersLoader", - "DisableNoise", - "DualCFGGuider", - "DualCLIPLoader", - "EmptyHunyuanLatentVideo", - "EmptyImage", - "EmptyLTXVLatentVideo", - "EmptyLatentAudio", - "EmptyLatentImage", - "EmptyMochiLatentVideo", - "EmptySD3LatentImage", - "ExponentialScheduler", - "FeatherMask", - "FlipSigmas", - "FluxGuidance", - "FreeU", - "FreeU_V2", - "GITSScheduler", - "GLIGENLoader", - "GLIGENTextBoxApply", - "GrowMask", - "HyperTile", - "HypernetworkLoader", - "ImageBatch", - "ImageBlend", - "ImageBlur", - "ImageColorToMask", - "ImageCompositeMasked", - "ImageCrop", - "ImageFromBatch", - "ImageInvert", - "ImageOnlyCheckpointLoader", - "ImageOnlyCheckpointSave", - "ImagePadForOutpaint", - "ImageQuantize", - "ImageScale", - "ImageScaleBy", - "ImageScaleToTotalPixels", - "ImageSharpen", - "ImageToMask", - "ImageUpscaleWithModel", - "InpaintModelConditioning", - "InstructPixToPixConditioning", - "InvertMask", - "JoinImageWithAlpha", - "KSampler", - "KSamplerAdvanced", - "KSamplerSelect", - "KarrasScheduler", - "LTXVConditioning", - "LTXVImgToVideo", - "LTXVScheduler", - "LaplaceScheduler", - "LatentAdd", - "LatentApplyOperation", - "LatentApplyOperationCFG", - "LatentBatch", - "LatentBatchSeedBehavior", - "LatentBlend", - "LatentComposite", - "LatentCompositeMasked", - "LatentCrop", - "LatentFlip", - "LatentFromBatch", - "LatentInterpolate", - "LatentMultiply", - "LatentOperationSharpen", - "LatentOperationTonemapReinhard", - "LatentRotate", - "LatentSubtract", - "LatentUpscale", - "LatentUpscaleBy", - "Load3D", - "Load3DAnimation", - "LoadAudio", - "LoadImage", - "LoadImageMask", - "LoadLatent", - "LoraLoader", - "LoraLoaderModelOnly", - "LoraSave", - "Mahiro", - "MaskComposite", - "MaskToImage", - "ModelMergeAdd", - "ModelMergeAuraflow", - "ModelMergeBlocks", - "ModelMergeFlux1", - "ModelMergeLTXV", - "ModelMergeMochiPreview", - "ModelMergeSD1", - "ModelMergeSD2", - "ModelMergeSD35_Large", - "ModelMergeSD3_2B", - "ModelMergeSDXL", - "ModelMergeSimple", - "ModelMergeSubtract", - "ModelSamplingAuraFlow", - "ModelSamplingContinuousEDM", - "ModelSamplingContinuousV", - "ModelSamplingDiscrete", - "ModelSamplingFlux", - "ModelSamplingLTXV", - "ModelSamplingSD3", - "ModelSamplingStableCascade", - "ModelSave", - "Morphology", - "PatchModelAddDownscale", - "PerpNeg", - "PerpNegGuider", - "PerturbedAttentionGuidance", - "PhotoMakerEncode", - "PhotoMakerLoader", - "PolyexponentialScheduler", - "PorterDuffImageComposite", - "Preview3D", - "PreviewAudio", - "PreviewImage", - "RandomNoise", - "RebatchImages", - "RebatchLatents", - "RepeatImageBatch", - "RepeatLatentBatch", - "RescaleCFG", - "SDTurboScheduler", - "SD_4XUpscale_Conditioning", - "SV3D_Conditioning", - "SVD_img2vid_Conditioning", - "SamplerCustom", - "SamplerCustomAdvanced", - "SamplerDPMAdaptative", - "SamplerDPMPP_2M_SDE", - "SamplerDPMPP_2S_Ancestral", - "SamplerDPMPP_3M_SDE", - "SamplerDPMPP_SDE", - "SamplerEulerAncestral", - "SamplerEulerAncestralCFGPP", - "SamplerEulerCFGpp", - "SamplerLCMUpscale", - "SamplerLMS", - "SaveAnimatedPNG", - "SaveAnimatedWEBP", - "SaveAudio", - "SaveImage", - "SaveImageWebsocket", - "SaveLatent", - "SelfAttentionGuidance", - "SetLatentNoiseMask", - "SetUnionControlNetType", - "SkipLayerGuidanceDiT", - "SkipLayerGuidanceSD3", - "SolidMask", - "SplitImageWithAlpha", - "SplitSigmas", - "SplitSigmasDenoise", - "StableCascade_EmptyLatentImage", - "StableCascade_StageB_Conditioning", - "StableCascade_StageC_VAEEncode", - "StableCascade_SuperResolutionControlnet", - "StableZero123_Conditioning", - "StableZero123_Conditioning_Batched", - "StubConstantImage", - "StubFloat", - "StubImage", - "StubInt", - "StubMask", - "StyleModelApply", - "StyleModelLoader", - "TestAccumulateNode", - "TestAccumulationGetItemNode", - "TestAccumulationGetLengthNode", - "TestAccumulationHeadNode", - "TestAccumulationSetItemNode", - "TestAccumulationTailNode", - "TestAccumulationToListNode", - "TestBoolOperationNode", - "TestCustomIsChanged", - "TestCustomValidation1", - "TestCustomValidation2", - "TestCustomValidation3", - "TestCustomValidation4", - "TestCustomValidation5", - "TestDynamicDependencyCycle", - "TestExecutionBlocker", - "TestFloatConditions", - "TestForLoopClose", - "TestForLoopOpen", - "TestIntConditions", - "TestIntMathOperation", - "TestIsChangedWithConstants", - "TestLazyMixImages", - "TestListToAccumulationNode", - "TestMakeListNode", - "TestMixedExpansionReturns", - "TestStringConditions", - "TestToBoolNode", - "TestVariadicAverage", - "TestWhileLoopClose", - "TestWhileLoopOpen", - "ThresholdMask", - "TomePatchModel", - "TorchCompileModel", - "TripleCLIPLoader", - "UNETLoader", - "UNetCrossAttentionMultiply", - "UNetSelfAttentionMultiply", - "UNetTemporalAttentionMultiply", - "UpscaleModelLoader", - "VAEDecode", - "VAEDecodeAudio", - "VAEDecodeTiled", - "VAEEncode", - "VAEEncodeAudio", - "VAEEncodeForInpaint", - "VAEEncodeTiled", - "VAELoader", - "VAESave", - "VPScheduler", - "VideoLinearCFGGuidance", - "VideoTriangleCFGGuidance", - "WebcamCapture", - "unCLIPCheckpointLoader", - "unCLIPConditioning" - ], - { - "title_aux": "ComfyUI" - } - ], - "https://github.com/dynamixar/Atluris": [ - [ - "RandomLine" - ], - { - "title_aux": "Atluris" - } - ], - "https://github.com/ecjojo/ecjojo-example-nodes": [ - [ - "BiggerNote_Example", - "DisplayTextNode_Example", - "EmptyNode_Example", - "ExampleNode_Example", - "FilePrefixNode_Example", - "HelloWorldNode_Example", - "RandomSizeNode_Example", - "StringNode_Example", - "TextOverlayNode_Example" - ], - { - "title_aux": "ecjojo_example_nodes" - } - ], - "https://github.com/et118/ComfyUI-ElGogh-Nodes": [ - [ - "ElGoghCLIPSetLastLayer", - "ElGoghCheckpointLoaderSimple", - "ElGoghEmptyLatentImage", - "ElGoghKSamplerAdvanced", - "ElGoghNegativePrompt", - "ElGoghPositivePrompt", - "ElGoghPrimaryLoraLoader", - "ElGoghSecondaryLoraLoader", - "ElGoghSendWebsocketNSFWBool", - "ElGoghTertiaryLoraLoader", - "ElGoghVAELoader" - ], - { - "title_aux": "ComfyUI-ElGogh-Nodes" - } - ], - "https://github.com/foxtrot-roger/comfyui-custom-nodes": [ - [ - "RF_Tutorial" - ], - { - "title_aux": "comfyui-custom-nodes" - } - ], - "https://github.com/jhj0517/ComfyUI-CustomNodes-Template": [ - [ - "(Down)Load My Model", - "Calculate Minus", - "Calculate Plus", - "Example Output Node" - ], - { - "title_aux": "ComfyUI-CustomNodes-Template" - } - ], - "https://github.com/jtong/comfyui-jtong-workflow": [ - [ - "Example", - "high_workflow_caller", - "jtong.Highend", - "jtong.Highway" - ], - { - "author": "Trung0246", - "description": "Random nodes for ComfyUI I made to solve my struggle with ComfyUI (ex: pipe, process). Have varying quality.", - "nickname": "ComfyUI-0246", - "title": "ComfyUI-0246", - "title_aux": "comfyui-jtong-workflow" - } - ], - "https://github.com/kappa54m/ComfyUI_Usability": [ - [ - "KLoadImageByPath", - "KLoadImageByPathAdvanced", - "KLoadImageDedup" - ], - { - "title_aux": "ComfyUI-HPSv2-Nodes" - } - ], - "https://github.com/mira-6/mira-wildcard-node": [ - [ - "MiraWildcard" - ], - { - "author": "mira-6", - "description": "Single-node wildcard implementation.", - "nickname": "mira-wildcard-node", - "title": "mira-wildcard-node", - "title_aux": "mira-wildcard-node" - } - ], - "https://github.com/sonyeon-sj/ComfyUI-easy_ImageSize_Selecter": [ - [ - "ImageSizer", - "promptSelecter" - ], - { - "title_aux": "ComfyUI-easy_ImageSize_Selecter" - } - ], - "https://github.com/thinkthinking/ComfyUI-Ye": [ - [ - "CheckpointLoader|Ye", - "OllamaVision|Ye", - "PrintHelloWorld|Ye", - "Signature|Ye" - ], - { - "title_aux": "ComfyUI-Ye" - } - ], - "https://github.com/wailovet/ComfyUI-WW": [ - [ - "WW_AccumulationPreviewImages", - "WW_AppendString", - "WW_CurrentPreviewImages", - "WW_ImageResize", - "WW_PreviewTextNode", - "WW_RandString" - ], - { - "title_aux": "ComfyUI-WW" - } - ], - "https://github.com/yowipr/ComfyUI-Manual": [ - [ - "EXAMPLE", - "M_Layer", - "M_Output", - "M_RenderArea" - ], - { - "title_aux": "ComfyUI-Manual" - } - ] -} \ No newline at end of file diff --git a/node_db/tutorial/model-list.json b/node_db/tutorial/model-list.json deleted file mode 100644 index 8e3e1dc4..00000000 --- a/node_db/tutorial/model-list.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "models": [] -} diff --git a/node_db/tutorial/scan.sh b/node_db/tutorial/scan.sh deleted file mode 100755 index 6e3e6daf..00000000 --- a/node_db/tutorial/scan.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -rm ~/.tmp/dev/*.py > /dev/null 2>&1 -python ../../scanner.py ~/.tmp/tutorial diff --git a/notebooks/comfyui_colab_with_manager.ipynb b/notebooks/comfyui_colab_with_manager.ipynb deleted file mode 100644 index 3cfa484b..00000000 --- a/notebooks/comfyui_colab_with_manager.ipynb +++ /dev/null @@ -1,373 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "aaaaaaaaaa" - }, - "source": [ - "Git clone the repo and install the requirements. (ignore the pip errors about protobuf)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "bbbbbbbbbb" - }, - "outputs": [], - "source": [ - "# #@title Environment Setup\n", - "\n", - "from pathlib import Path\n", - "\n", - "OPTIONS = {}\n", - "\n", - "USE_GOOGLE_DRIVE = True #@param {type:\"boolean\"}\n", - "UPDATE_COMFY_UI = True #@param {type:\"boolean\"}\n", - "USE_COMFYUI_MANAGER = True #@param {type:\"boolean\"}\n", - "INSTALL_CUSTOM_NODES_DEPENDENCIES = True #@param {type:\"boolean\"}\n", - "OPTIONS['USE_GOOGLE_DRIVE'] = USE_GOOGLE_DRIVE\n", - "OPTIONS['UPDATE_COMFY_UI'] = UPDATE_COMFY_UI\n", - "OPTIONS['USE_COMFYUI_MANAGER'] = USE_COMFYUI_MANAGER\n", - "OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES'] = INSTALL_CUSTOM_NODES_DEPENDENCIES\n", - "\n", - "current_dir = !pwd\n", - "WORKSPACE = f\"{current_dir[0]}/ComfyUI\"\n", - "\n", - "if OPTIONS['USE_GOOGLE_DRIVE']:\n", - " !echo \"Mounting Google Drive...\"\n", - " %cd /\n", - "\n", - " from google.colab import drive\n", - " drive.mount('/content/drive')\n", - "\n", - " WORKSPACE = \"/content/drive/MyDrive/ComfyUI\"\n", - " %cd /content/drive/MyDrive\n", - "\n", - "![ ! -d $WORKSPACE ] && echo -= Initial setup ComfyUI =- && git clone https://github.com/comfyanonymous/ComfyUI\n", - "%cd $WORKSPACE\n", - "\n", - "if OPTIONS['UPDATE_COMFY_UI']:\n", - " !echo -= Updating ComfyUI =-\n", - "\n", - " # Correction of the issue of permissions being deleted on Google Drive.\n", - " ![ -f \".ci/nightly/update_windows/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/nightly/update_windows/update_comfyui_and_python_dependencies.bat\n", - " ![ -f \".ci/nightly/windows_base_files/run_nvidia_gpu.bat\" ] && chmod 755 .ci/nightly/windows_base_files/run_nvidia_gpu.bat\n", - " ![ -f \".ci/update_windows/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/update_windows/update_comfyui_and_python_dependencies.bat\n", - " ![ -f \".ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat\n", - " ![ -f \".ci/update_windows/update.py\" ] && chmod 755 .ci/update_windows/update.py\n", - " ![ -f \".ci/update_windows/update_comfyui.bat\" ] && chmod 755 .ci/update_windows/update_comfyui.bat\n", - " ![ -f \".ci/update_windows/README_VERY_IMPORTANT.txt\" ] && chmod 755 .ci/update_windows/README_VERY_IMPORTANT.txt\n", - " ![ -f \".ci/update_windows/run_cpu.bat\" ] && chmod 755 .ci/update_windows/run_cpu.bat\n", - " ![ -f \".ci/update_windows/run_nvidia_gpu.bat\" ] && chmod 755 .ci/update_windows/run_nvidia_gpu.bat\n", - "\n", - " !git pull\n", - "\n", - "!echo -= Install dependencies =-\n", - "!pip3 install accelerate\n", - "!pip3 install einops transformers>=4.28.1 safetensors>=0.4.2 aiohttp pyyaml Pillow scipy tqdm psutil tokenizers>=0.13.3\n", - "!pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121\n", - "!pip3 install torchsde\n", - "!pip3 install kornia>=0.7.1 spandrel soundfile sentencepiece\n", - "\n", - "if OPTIONS['USE_COMFYUI_MANAGER']:\n", - " %cd custom_nodes\n", - "\n", - " # Correction of the issue of permissions being deleted on Google Drive.\n", - " ![ -f \"ComfyUI-Manager/check.sh\" ] && chmod 755 ComfyUI-Manager/check.sh\n", - " ![ -f \"ComfyUI-Manager/scan.sh\" ] && chmod 755 ComfyUI-Manager/scan.sh\n", - " ![ -f \"ComfyUI-Manager/node_db/dev/scan.sh\" ] && chmod 755 ComfyUI-Manager/node_db/dev/scan.sh\n", - " ![ -f \"ComfyUI-Manager/node_db/tutorial/scan.sh\" ] && chmod 755 ComfyUI-Manager/node_db/tutorial/scan.sh\n", - " ![ -f \"ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh\" ] && chmod 755 ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh\n", - " ![ -f \"ComfyUI-Manager/scripts/install-comfyui-venv-win.bat\" ] && chmod 755 ComfyUI-Manager/scripts/install-comfyui-venv-win.bat\n", - "\n", - " ![ ! -d ComfyUI-Manager ] && echo -= Initial setup ComfyUI-Manager =- && git clone https://github.com/ltdrdata/ComfyUI-Manager\n", - " %cd ComfyUI-Manager\n", - " !git pull\n", - "\n", - "%cd $WORKSPACE\n", - "\n", - "if OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES']:\n", - " !echo -= Install custom nodes dependencies =-\n", - " !pip install GitPython\n", - " !python custom_nodes/ComfyUI-Manager/cm-cli.py restore-dependencies\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "cccccccccc" - }, - "source": [ - "Download some models/checkpoints/vae or custom comfyui nodes (uncomment the commands for the ones you want)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "dddddddddd" - }, - "outputs": [], - "source": [ - "# Checkpoints\n", - "\n", - "### SDXL\n", - "### I recommend these workflow examples: https://comfyanonymous.github.io/ComfyUI_examples/sdxl/\n", - "\n", - "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors -P ./models/checkpoints/\n", - "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0.safetensors -P ./models/checkpoints/\n", - "\n", - "# SDXL ReVision\n", - "#!wget -c https://huggingface.co/comfyanonymous/clip_vision_g/resolve/main/clip_vision_g.safetensors -P ./models/clip_vision/\n", - "\n", - "# SD1.5\n", - "!wget -c https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt -P ./models/checkpoints/\n", - "\n", - "# SD2\n", - "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.safetensors -P ./models/checkpoints/\n", - "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors -P ./models/checkpoints/\n", - "\n", - "# Some SD1.5 anime style\n", - "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_hard.safetensors -P ./models/checkpoints/\n", - "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A1_orangemixs.safetensors -P ./models/checkpoints/\n", - "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A3_orangemixs.safetensors -P ./models/checkpoints/\n", - "#!wget -c https://huggingface.co/Linaqruf/anything-v3.0/resolve/main/anything-v3-fp16-pruned.safetensors -P ./models/checkpoints/\n", - "\n", - "# Waifu Diffusion 1.5 (anime style SD2.x 768-v)\n", - "#!wget -c https://huggingface.co/waifu-diffusion/wd-1-5-beta3/resolve/main/wd-illusion-fp16.safetensors -P ./models/checkpoints/\n", - "\n", - "\n", - "# unCLIP models\n", - "#!wget -c https://huggingface.co/comfyanonymous/illuminatiDiffusionV1_v11_unCLIP/resolve/main/illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors -P ./models/checkpoints/\n", - "#!wget -c https://huggingface.co/comfyanonymous/wd-1.5-beta2_unCLIP/resolve/main/wd-1-5-beta2-aesthetic-unclip-h-fp16.safetensors -P ./models/checkpoints/\n", - "\n", - "\n", - "# VAE\n", - "!wget -c https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors -P ./models/vae/\n", - "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/VAEs/orangemix.vae.pt -P ./models/vae/\n", - "#!wget -c https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt -P ./models/vae/\n", - "\n", - "\n", - "# Loras\n", - "#!wget -c https://civitai.com/api/download/models/10350 -O ./models/loras/theovercomer8sContrastFix_sd21768.safetensors #theovercomer8sContrastFix SD2.x 768-v\n", - "#!wget -c https://civitai.com/api/download/models/10638 -O ./models/loras/theovercomer8sContrastFix_sd15.safetensors #theovercomer8sContrastFix SD1.x\n", - "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors -P ./models/loras/ #SDXL offset noise lora\n", - "\n", - "\n", - "# T2I-Adapter\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_seg_sd14v1.pth -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_sketch_sd14v1.pth -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_keypose_sd14v1.pth -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_openpose_sd14v1.pth -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_color_sd14v1.pth -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_canny_sd14v1.pth -P ./models/controlnet/\n", - "\n", - "# T2I Styles Model\n", - "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_style_sd14v1.pth -P ./models/style_models/\n", - "\n", - "# CLIPVision model (needed for styles model)\n", - "#!wget -c https://huggingface.co/openai/clip-vit-large-patch14/resolve/main/pytorch_model.bin -O ./models/clip_vision/clip_vit14.bin\n", - "\n", - "\n", - "# ControlNet\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_ip2p_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_shuffle_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_canny_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11f1p_sd15_depth_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_inpaint_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_lineart_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_mlsd_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_normalbae_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_openpose_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_scribble_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_seg_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_softedge_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15s2_lineart_anime_fp16.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11u_sd15_tile_fp16.safetensors -P ./models/controlnet/\n", - "\n", - "# ControlNet SDXL\n", - "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-canny-rank256.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-depth-rank256.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-recolor-rank256.safetensors -P ./models/controlnet/\n", - "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors -P ./models/controlnet/\n", - "\n", - "# Controlnet Preprocessor nodes by Fannovel16\n", - "#!cd custom_nodes && git clone https://github.com/Fannovel16/comfy_controlnet_preprocessors; cd comfy_controlnet_preprocessors && python install.py\n", - "\n", - "\n", - "# GLIGEN\n", - "#!wget -c https://huggingface.co/comfyanonymous/GLIGEN_pruned_safetensors/resolve/main/gligen_sd14_textbox_pruned_fp16.safetensors -P ./models/gligen/\n", - "\n", - "\n", - "# ESRGAN upscale model\n", - "#!wget -c https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P ./models/upscale_models/\n", - "#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x2.pth -P ./models/upscale_models/\n", - "#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x4.pth -P ./models/upscale_models/\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kkkkkkkkkkkkkkk" - }, - "source": [ - "### Run ComfyUI with cloudflared (Recommended Way)\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "jjjjjjjjjjjjjj" - }, - "outputs": [], - "source": [ - "!wget -P ~ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb\n", - "!dpkg -i ~/cloudflared-linux-amd64.deb\n", - "\n", - "import subprocess\n", - "import threading\n", - "import time\n", - "import socket\n", - "import urllib.request\n", - "\n", - "def iframe_thread(port):\n", - " while True:\n", - " time.sleep(0.5)\n", - " sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", - " result = sock.connect_ex(('127.0.0.1', port))\n", - " if result == 0:\n", - " break\n", - " sock.close()\n", - " print(\"\\nComfyUI finished loading, trying to launch cloudflared (if it gets stuck here cloudflared is having issues)\\n\")\n", - "\n", - " p = subprocess.Popen([\"cloudflared\", \"tunnel\", \"--url\", \"http://127.0.0.1:{}\".format(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", - " for line in p.stderr:\n", - " l = line.decode()\n", - " if \"trycloudflare.com \" in l:\n", - " print(\"This is the URL to access ComfyUI:\", l[l.find(\"http\"):], end='')\n", - " #print(l, end='')\n", - "\n", - "\n", - "threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n", - "\n", - "!python main.py --dont-print-server" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kkkkkkkkkkkkkk" - }, - "source": [ - "### Run ComfyUI with localtunnel\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "jjjjjjjjjjjjj" - }, - "outputs": [], - "source": [ - "!npm install -g localtunnel\n", - "\n", - "import subprocess\n", - "import threading\n", - "import time\n", - "import socket\n", - "import urllib.request\n", - "\n", - "def iframe_thread(port):\n", - " while True:\n", - " time.sleep(0.5)\n", - " sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", - " result = sock.connect_ex(('127.0.0.1', port))\n", - " if result == 0:\n", - " break\n", - " sock.close()\n", - " print(\"\\nComfyUI finished loading, trying to launch localtunnel (if it gets stuck here localtunnel is having issues)\\n\")\n", - "\n", - " print(\"The password/enpoint ip for localtunnel is:\", urllib.request.urlopen('https://ipv4.icanhazip.com').read().decode('utf8').strip(\"\\n\"))\n", - " p = subprocess.Popen([\"lt\", \"--port\", \"{}\".format(port)], stdout=subprocess.PIPE)\n", - " for line in p.stdout:\n", - " print(line.decode(), end='')\n", - "\n", - "\n", - "threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n", - "\n", - "!python main.py --dont-print-server" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gggggggggg" - }, - "source": [ - "### Run ComfyUI with colab iframe (use only in case the previous way with localtunnel doesn't work)\n", - "\n", - "You should see the ui appear in an iframe. If you get a 403 error, it's your firefox settings or an extension that's messing things up.\n", - "\n", - "If you want to open it in another window use the link.\n", - "\n", - "Note that some UI features like live image previews won't work because the colab iframe blocks websockets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "hhhhhhhhhh" - }, - "outputs": [], - "source": [ - "import threading\n", - "import time\n", - "import socket\n", - "def iframe_thread(port):\n", - " while True:\n", - " time.sleep(0.5)\n", - " sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", - " result = sock.connect_ex(('127.0.0.1', port))\n", - " if result == 0:\n", - " break\n", - " sock.close()\n", - " from google.colab import output\n", - " output.serve_kernel_port_as_iframe(port, height=1024)\n", - " print(\"to open it in a window you can open this link here:\")\n", - " output.serve_kernel_port_as_window(port)\n", - "\n", - "threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n", - "\n", - "!python main.py --dont-print-server" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "provenance": [] - }, - "gpuClass": "standard", - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/openapi.yaml b/openapi.yaml deleted file mode 100644 index 0446259e..00000000 --- a/openapi.yaml +++ /dev/null @@ -1,903 +0,0 @@ -openapi: 3.1.0 -info: - title: ComfyUI-Manager API - description: | - API for ComfyUI-Manager, a comprehensive management tool for ComfyUI custom nodes, models, and components. - This API enables programmatic access to node management, model downloading, snapshot operations, - and overall system configuration. - version: "3.32.3" - contact: - name: ComfyUI-Manager Maintainers -servers: - - url: '/' - description: Default ComfyUI server - -# Common API components -components: - schemas: - Error: - type: object - properties: - error: - type: string - description: Error message - - NodePackageMetadata: - type: object - properties: - title: - type: string - description: Display name of the node package - name: - type: string - description: Repository/package name - files: - type: array - items: - type: string - description: Source URLs for the package - description: - type: string - description: Description of the node package functionality - install_type: - type: string - enum: [git, copy, pip] - description: Installation method - version: - type: string - description: Version identifier - id: - type: string - description: Unique identifier for the node package - ui_id: - type: string - description: ID for UI reference - channel: - type: string - description: Source channel - mode: - type: string - description: Source mode - - ModelMetadata: - type: object - properties: - name: - type: string - description: Name of the model - type: - type: string - description: Type of model - base: - type: string - description: Base model type - save_path: - type: string - description: Path for saving the model - url: - type: string - description: Download URL - filename: - type: string - description: Target filename - ui_id: - type: string - description: ID for UI reference - - SnapshotItem: - type: string - description: Name of the snapshot - - QueueStatus: - type: object - properties: - total_count: - type: integer - description: Total number of tasks - done_count: - type: integer - description: Number of completed tasks - in_progress_count: - type: integer - description: Number of tasks in progress - is_processing: - type: boolean - description: Whether the queue is currently processing - - 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 - in: header - name: Security-Level - description: Security level for sensitive operations - - parameters: - modeParam: - name: mode - in: query - description: Source mode (e.g., "local", "remote") - schema: - type: string - enum: [local, remote, default] - - targetParam: - name: target - in: query - description: Target identifier - required: true - schema: - type: string - - valueParam: - name: value - in: query - description: New value to set - required: true - schema: - type: string - -# API Paths -paths: - # Custom Nodes Endpoints - /customnode/getmappings: - get: - summary: Get node-to-package mappings - description: Provides unified mapping between nodes and node packages - parameters: - - $ref: '#/components/parameters/modeParam' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - additionalProperties: - type: array - items: - type: array - description: Mapping of node packages to node classes - - /customnode/fetch_updates: - get: - summary: Check for updates - description: Fetches updates for custom nodes - parameters: - - $ref: '#/components/parameters/modeParam' - responses: - '200': - description: No updates available - '201': - description: Updates found - '400': - description: Error occurred - - /customnode/installed: - get: - summary: Get installed custom nodes - description: Returns a list of installed node packages - parameters: - - name: mode - in: query - description: Lists mode, default or imported - schema: - type: string - enum: [default, imported] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - additionalProperties: - $ref: '#/components/schemas/NodePackageMetadata' - - /customnode/getlist: - get: - summary: Get custom node list - description: Provides a list of available custom nodes - parameters: - - $ref: '#/components/parameters/modeParam' - - name: skip_update - in: query - description: Skip update check - schema: - type: boolean - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - properties: - channel: - type: string - node_packs: - type: object - additionalProperties: - $ref: '#/components/schemas/NodePackageMetadata' - - /customnode/alternatives: - get: - summary: Get alternative node options - description: Provides alternatives for nodes - parameters: - - $ref: '#/components/parameters/modeParam' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - additionalProperties: - type: object - - /customnode/versions/{node_name}: - get: - summary: Get available versions for a node - description: Lists all available versions for a specific node - parameters: - - name: node_name - in: path - required: true - schema: - type: string - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - type: object - properties: - version: - type: string - '400': - description: Node not found - - /customnode/disabled_versions/{node_name}: - get: - summary: Get disabled versions for a node - description: Lists all disabled versions for a specific node - parameters: - - name: node_name - in: path - required: true - schema: - type: string - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - type: object - properties: - version: - type: string - '400': - description: Node not found - - /customnode/import_fail_info: - post: - summary: Get import failure information - description: Returns information about why a node failed to import - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - cnr_id: - type: string - url: - type: string - responses: - '200': - description: Successful operation - '400': - description: No information available - - /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. - - /customnode/install/git_url: - post: - summary: Install custom node via Git URL - description: Installs a custom node from a Git repository URL - security: - - securityLevel: [] - requestBody: - required: true - content: - text/plain: - schema: - type: string - responses: - '200': - description: Installation successful or already installed - '400': - description: Installation failed - '403': - description: Security policy violation - - /customnode/install/pip: - post: - summary: Install custom node dependencies via pip - description: Installs Python package dependencies for custom nodes - security: - - securityLevel: [] - requestBody: - required: true - content: - text/plain: - schema: - type: string - responses: - '200': - description: Installation successful - '403': - description: Security policy violation - - # Model Management Endpoints - /externalmodel/getlist: - get: - summary: Get external model list - description: Provides a list of available external models - parameters: - - $ref: '#/components/parameters/modeParam' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - properties: - models: - type: array - items: - $ref: '#/components/schemas/ModelMetadata' - - # Queue Management Endpoints - /manager/queue/update_all: - get: - summary: Update all custom nodes - description: Queues update operations for all installed custom nodes - security: - - securityLevel: [] - parameters: - - $ref: '#/components/parameters/modeParam' - responses: - '200': - description: Update queued successfully - '401': - description: Processing already in progress - '403': - description: Security policy violation - - /manager/queue/reset: - get: - summary: Reset queue - description: Resets the operation queue - responses: - '200': - description: Queue reset successfully - - /manager/queue/status: - get: - summary: Get queue status - description: Returns the current status of the operation queue - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/QueueStatus' - - /manager/queue/install: - post: - summary: Install custom node - description: Queues installation of a custom node - security: - - securityLevel: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NodePackageMetadata' - responses: - '200': - description: Installation queued successfully - '403': - description: Security policy violation - '404': - description: Target node not found or security issue - - /manager/queue/start: - get: - summary: Start queue processing - description: Starts processing the operation queue - responses: - '200': - description: Processing started - '201': - description: Processing already in progress - - /manager/queue/fix: - post: - summary: Fix custom node - description: Attempts to fix a broken custom node installation - security: - - securityLevel: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NodePackageMetadata' - responses: - '200': - description: Fix operation queued successfully - '403': - description: Security policy violation - - /manager/queue/reinstall: - post: - summary: Reinstall custom node - description: Uninstalls and then reinstalls a custom node - security: - - securityLevel: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NodePackageMetadata' - responses: - '200': - description: Reinstall operation queued successfully - '403': - description: Security policy violation - - /manager/queue/uninstall: - post: - summary: Uninstall custom node - description: Queues uninstallation of a custom node - security: - - securityLevel: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NodePackageMetadata' - responses: - '200': - description: Uninstallation queued successfully - '403': - description: Security policy violation - - /manager/queue/update: - post: - summary: Update custom node - description: Queues update of a custom node - security: - - securityLevel: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NodePackageMetadata' - responses: - '200': - description: Update queued successfully - '403': - description: Security policy violation - - /manager/queue/disable: - post: - summary: Disable custom node - description: Disables a custom node without uninstalling it - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NodePackageMetadata' - responses: - '200': - description: Disable operation queued successfully - - /manager/queue/update_comfyui: - get: - summary: Update ComfyUI - description: Queues an update operation for ComfyUI itself - responses: - '200': - description: Update queued successfully - - /manager/queue/install_model: - post: - summary: Install model - description: Queues installation of a model - security: - - securityLevel: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ModelMetadata' - responses: - '200': - description: Installation queued successfully - '400': - description: Invalid model request - '403': - description: Security policy violation - - # Snapshot Management Endpoints - /snapshot/getlist: - get: - summary: Get snapshot list - description: Returns a list of available snapshots - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - properties: - items: - type: array - items: - $ref: '#/components/schemas/SnapshotItem' - - /snapshot/remove: - get: - summary: Remove snapshot - description: Removes a specified snapshot - security: - - securityLevel: [] - parameters: - - $ref: '#/components/parameters/targetParam' - responses: - '200': - description: Snapshot removed successfully - '400': - description: Error removing snapshot - '403': - description: Security policy violation - - /snapshot/restore: - get: - summary: Restore snapshot - description: Restores a specified snapshot - security: - - securityLevel: [] - parameters: - - $ref: '#/components/parameters/targetParam' - responses: - '200': - description: Snapshot restoration scheduled - '400': - description: Error restoring snapshot - '403': - description: Security policy violation - - /snapshot/get_current: - get: - summary: Get current snapshot - description: Returns the current system state as a snapshot - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - '400': - description: Error creating snapshot - - /snapshot/save: - get: - summary: Save snapshot - description: Saves the current system state as a new snapshot - responses: - '200': - description: Snapshot saved successfully - '400': - description: Error saving snapshot - - # ComfyUI Management Endpoints - /comfyui_manager/comfyui_versions: - get: - summary: Get ComfyUI versions - description: Returns available and current ComfyUI versions - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - properties: - versions: - type: array - items: - type: string - current: - type: string - '400': - description: Error retrieving versions - - /comfyui_manager/comfyui_switch_version: - get: - summary: Switch ComfyUI version - description: Switches to a specified ComfyUI version - parameters: - - name: ver - in: query - description: Target version - schema: - type: string - responses: - '200': - description: Version switch successful - '400': - description: Error switching version - - /manager/reboot: - get: - summary: Reboot ComfyUI - description: Restarts the ComfyUI server - security: - - securityLevel: [] - responses: - '200': - description: Reboot initiated - '403': - description: Security policy violation - - # Configuration Endpoints - /manager/preview_method: - get: - summary: Get or set preview method - description: Gets or sets the latent preview method - parameters: - - name: value - in: query - required: false - description: New preview method - schema: - type: string - enum: [auto, latent2rgb, taesd, none] - responses: - '200': - description: Setting updated or current value returned - content: - text/plain: - schema: - type: string - - /manager/db_mode: - get: - summary: Get or set database mode - description: Gets or sets the database mode - parameters: - - name: value - in: query - required: false - description: New database mode - schema: - type: string - enum: [channel, local, remote] - responses: - '200': - description: Setting updated or current value returned - content: - text/plain: - schema: - type: string - - /manager/policy/component: - get: - summary: Get or set component policy - description: Gets or sets the component policy - parameters: - - name: value - in: query - required: false - description: New component policy - schema: - type: string - responses: - '200': - description: Setting updated or current value returned - content: - text/plain: - schema: - type: string - - /manager/policy/update: - get: - summary: Get or set update policy - description: Gets or sets the update policy - parameters: - - name: value - in: query - required: false - description: New update policy - schema: - type: string - enum: [stable, nightly, nightly-comfyui] - responses: - '200': - description: Setting updated or current value returned - content: - text/plain: - schema: - type: string - - /manager/channel_url_list: - get: - summary: Get or set channel URL - description: Gets or sets the channel URL for custom node sources - parameters: - - name: value - in: query - required: false - description: New channel name - schema: - type: string - responses: - '200': - description: Setting updated or channel list returned - content: - application/json: - schema: - type: object - properties: - selected: - type: string - list: - type: array - items: - type: object - properties: - name: - type: string - url: - type: string - - # Component Management Endpoints - /manager/component/save: - post: - summary: Save component - description: Saves a reusable workflow component - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - workflow: - type: object - responses: - '200': - description: Component saved successfully - content: - text/plain: - schema: - type: string - '400': - description: Error saving component - - /manager/component/loads: - post: - summary: Load components - description: Loads all available workflow components - responses: - '200': - description: Components loaded successfully - content: - application/json: - schema: - type: object - '400': - description: Error loading components - - # Miscellaneous Endpoints - /manager/version: - get: - summary: Get manager version - description: Returns the current version of ComfyUI-Manager - responses: - '200': - description: Successful operation - content: - text/plain: - schema: - type: string - - /manager/notice: - get: - summary: Get manager notice - description: Returns HTML content with notices and version information - responses: - '200': - description: Successful operation - content: - text/html: - schema: - type: string \ No newline at end of file diff --git a/pip_overrides.json.template b/pip_overrides.json.template deleted file mode 100644 index 3884edb9..00000000 --- a/pip_overrides.json.template +++ /dev/null @@ -1,21 +0,0 @@ -{ - "imageio-ffmpeg": "imageio", - "imageio[ffmpeg]": "imageio", - "imageio_ffmpeg": "imageio", - "diffusers~=0.21.4": "diffusers", - "huggingface_hub": "huggingface-hub", - "numpy<1.24>=1.18": "numpy==1.26.4", - "numpy>=1.18.5, <1.25.0": "numpy==1.26.4", - "opencv-contrib-python": "opencv-contrib-python-headless", - "opencv-python": "opencv-contrib-python-headless", - "opencv-python-headless": "opencv-contrib-python-headless", - "opencv-python-headless[ffmpeg]<=4.7.0.72": "opencv-contrib-python-headless", - "opencv-python>=4.7.0.72": "opencv-contrib-python-headless", - "pandas<=1.5.1": "pandas", - "scikit-image==0.20.0": "scikit-image", - "scipy>=1.11.4": "scipy", - "segment_anything": "segment-anything", - "timm==0.6.5": "timm", - "timm>=0.4.12": "timm", - "transformers==4.26.1": "transformers" -} \ No newline at end of file diff --git a/pip_overrides.osx.template b/pip_overrides.osx.template deleted file mode 100644 index 3884edb9..00000000 --- a/pip_overrides.osx.template +++ /dev/null @@ -1,21 +0,0 @@ -{ - "imageio-ffmpeg": "imageio", - "imageio[ffmpeg]": "imageio", - "imageio_ffmpeg": "imageio", - "diffusers~=0.21.4": "diffusers", - "huggingface_hub": "huggingface-hub", - "numpy<1.24>=1.18": "numpy==1.26.4", - "numpy>=1.18.5, <1.25.0": "numpy==1.26.4", - "opencv-contrib-python": "opencv-contrib-python-headless", - "opencv-python": "opencv-contrib-python-headless", - "opencv-python-headless": "opencv-contrib-python-headless", - "opencv-python-headless[ffmpeg]<=4.7.0.72": "opencv-contrib-python-headless", - "opencv-python>=4.7.0.72": "opencv-contrib-python-headless", - "pandas<=1.5.1": "pandas", - "scikit-image==0.20.0": "scikit-image", - "scipy>=1.11.4": "scipy", - "segment_anything": "segment-anything", - "timm==0.6.5": "timm", - "timm>=0.4.12": "timm", - "transformers==4.26.1": "transformers" -} \ No newline at end of file diff --git a/prestartup_script.py b/prestartup_script.py deleted file mode 100644 index dabd39da..00000000 --- a/prestartup_script.py +++ /dev/null @@ -1,869 +0,0 @@ -import os -import shutil -import subprocess -import sys -import atexit -import threading -import re -import locale -import platform -import json -import ast -import logging -import traceback - -glob_path = os.path.join(os.path.dirname(__file__), "glob") -sys.path.append(glob_path) - -import security_check -import manager_util -import cm_global -import manager_downloader -import folder_paths - -manager_util.add_python_path_to_env() - -import datetime as dt - -if hasattr(dt, 'datetime'): - from datetime import datetime as dt_datetime - - def current_timestamp(): - return dt_datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] -else: - # NOTE: Occurs in some Mac environments. - import time - logging.error(f"[ComfyUI-Manager] fallback timestamp mode\n datetime module is invalid: '{dt.__file__}'") - - def current_timestamp(): - return str(time.time()).split('.')[0] - - -cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'} -cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia'] - - -def skip_pip_spam(x): - return ('Requirement already satisfied:' in x) or ("DEPRECATION: Loading egg at" in x) - - -message_collapses = [skip_pip_spam] -import_failed_extensions = set() -cm_global.variables['cm.on_revision_detected_handler'] = [] -enable_file_logging = True - - -def register_message_collapse(f): - global message_collapses - message_collapses.append(f) - - -def is_import_failed_extension(name): - global import_failed_extensions - return name in import_failed_extensions - - -comfy_path = os.environ.get('COMFYUI_PATH') -comfy_base_path = os.environ.get('COMFYUI_FOLDERS_BASE_PATH') - -if comfy_path is None: - # legacy env var - comfy_path = os.environ.get('COMFYUI_PATH') - -if comfy_path is None: - comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__)) - -if comfy_base_path is None: - comfy_base_path = comfy_path - -sys.__comfyui_manager_register_message_collapse = register_message_collapse -sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension -cm_global.register_api('cm.register_message_collapse', register_message_collapse) -cm_global.register_api('cm.is_import_failed_extension', is_import_failed_extension) - - -comfyui_manager_path = os.path.abspath(os.path.dirname(__file__)) - -custom_nodes_base_path = folder_paths.get_folder_paths('custom_nodes')[0] - -# Check for System User API availability (PR #10966) -_has_system_user_api = hasattr(folder_paths, 'get_system_user_directory') - -if _has_system_user_api: - manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), '__manager')) -else: - manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-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") -manager_config_path = os.path.join(manager_files_path, 'config.ini') - -cm_cli_path = os.path.join(comfyui_manager_path, "cm-cli.py") - - -default_conf = {} - -def read_config(): - global default_conf - try: - import configparser - config = configparser.ConfigParser(strict=False) - config.read(manager_config_path) - default_conf = config['default'] - except Exception: - pass - -def read_uv_mode(): - if 'use_uv' in default_conf: - manager_util.use_uv = default_conf['use_uv'].lower() == 'true' - -def check_file_logging(): - global enable_file_logging - if 'file_logging' in default_conf and default_conf['file_logging'].lower() == 'false': - enable_file_logging = False - - -read_config() -read_uv_mode() -security_check.security_check() -check_file_logging() - -cm_global.pip_overrides = {} - -if os.path.exists(manager_pip_overrides_path): - with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file: - cm_global.pip_overrides = json.load(json_file) - - -if os.path.exists(manager_pip_blacklist_path): - with open(manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f: - for x in f.readlines(): - y = x.strip() - if y != '': - cm_global.pip_blacklist.add(y) - - -def remap_pip_package(pkg): - if pkg in cm_global.pip_overrides: - res = cm_global.pip_overrides[pkg] - print(f"[ComfyUI-Manager] '{pkg}' is remapped to '{res}'") - return res - else: - return pkg - - -std_log_lock = threading.Lock() - - -def handle_stream(stream, prefix): - stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace') - for msg in stream: - if prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg): - if msg.startswith('100%'): - print('\r' + msg, end="", file=sys.stderr), - else: - print('\r' + msg[:-1], end="", file=sys.stderr), - else: - if prefix == '[!]': - print(prefix, msg, end="", file=sys.stderr) - else: - print(prefix, msg, end="") - - -def process_wrap(cmd_str, cwd_path, handler=None, env=None): - process = subprocess.Popen(cmd_str, cwd=cwd_path, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1) - - if handler is None: - handler = handle_stream - - stdout_thread = threading.Thread(target=handler, args=(process.stdout, "")) - stderr_thread = threading.Thread(target=handler, args=(process.stderr, "[!]")) - - stdout_thread.start() - stderr_thread.start() - - stdout_thread.join() - stderr_thread.join() - - return process.wait() - - -original_stdout = sys.stdout - - -def try_get_custom_nodes(x): - for custom_nodes_dir in folder_paths.get_folder_paths('custom_nodes'): - if x.startswith(custom_nodes_dir): - relative_path = os.path.relpath(x, custom_nodes_dir) - next_segment = relative_path.split(os.sep)[0] - if next_segment.lower() != 'comfyui-manager': - return next_segment, os.path.join(custom_nodes_dir, next_segment) - return None - - -def extract_origin_module(): - stack = traceback.extract_stack()[:-2] - for frame in reversed(stack): - info = try_get_custom_nodes(frame.filename) - if info is None: - continue - else: - return info - return None - -def extract_origin_module_from_strings(file_paths): - for filepath in file_paths: - info = try_get_custom_nodes(filepath) - if info is None: - continue - else: - return info - return None - - -def finalize_startup(): - res = {} - for k, v in cm_global.error_dict.items(): - if v['path'] in import_failed_extensions: - res[k] = v - - cm_global.error_dict = res - - -try: - if '--port' in sys.argv: - port_index = sys.argv.index('--port') - if port_index + 1 < len(sys.argv): - port = int(sys.argv[port_index + 1]) - postfix = f"_{port}" - else: - postfix = "" - else: - postfix = "" - - # Logger setup - log_path_base = None - if enable_file_logging: - log_path_base = os.path.join(folder_paths.user_directory, 'comfyui') - - if not os.path.exists(folder_paths.user_directory): - os.makedirs(folder_paths.user_directory) - - if os.path.exists(f"{log_path_base}{postfix}.log"): - if os.path.exists(f"{log_path_base}{postfix}.prev.log"): - if os.path.exists(f"{log_path_base}{postfix}.prev2.log"): - os.remove(f"{log_path_base}{postfix}.prev2.log") - os.rename(f"{log_path_base}{postfix}.prev.log", f"{log_path_base}{postfix}.prev2.log") - os.rename(f"{log_path_base}{postfix}.log", f"{log_path_base}{postfix}.prev.log") - - log_file = open(f"{log_path_base}{postfix}.log", "w", encoding="utf-8", errors="ignore") - - log_lock = threading.Lock() - - original_stdout = sys.stdout - original_stderr = sys.stderr - - if original_stdout.encoding.lower() == 'utf-8': - write_stdout = original_stdout.write - write_stderr = original_stderr.write - else: - def wrapper_stdout(msg): - original_stdout.write(msg.encode('utf-8').decode(original_stdout.encoding, errors="ignore")) - - def wrapper_stderr(msg): - original_stderr.write(msg.encode('utf-8').decode(original_stderr.encoding, errors="ignore")) - - write_stdout = wrapper_stdout - write_stderr = wrapper_stderr - - pat_tqdm = r'\d+%.*\[(.*?)\]' - pat_import_fail = r'seconds \(IMPORT FAILED\):(.*)$' - - is_start_mode = True - - - class ComfyUIManagerLogger: - def __init__(self, is_stdout): - self.is_stdout = is_stdout - self.encoding = "utf-8" - self.last_char = '' - - def fileno(self): - try: - if self.is_stdout: - return original_stdout.fileno() - else: - return original_stderr.fileno() - except AttributeError: - # Handle error - raise ValueError("The object does not have a fileno method") - - def isatty(self): - return False - - def write(self, message): - global is_start_mode - - if any(f(message) for f in message_collapses): - return - - if is_start_mode: - match = re.search(pat_import_fail, message) - if match: - import_failed_extensions.add(match.group(1).strip()) - - if not self.is_stdout: - origin_info = extract_origin_module() - if origin_info is not None: - name, origin_path = origin_info - - if name != 'comfyui-manager': - if name not in cm_global.error_dict: - cm_global.error_dict[name] = {'name': name, 'path': origin_path, 'msg': ''} - - cm_global.error_dict[name]['msg'] += message - - if not self.is_stdout: - match = re.search(pat_tqdm, message) - if match: - message = re.sub(r'([#|])\d', r'\1▌', message) - message = re.sub('#', '█', message) - if '100%' in message: - self.sync_write(message) - else: - write_stderr(message) - original_stderr.flush() - else: - self.sync_write(message) - else: - self.sync_write(message) - - def sync_write(self, message, file_only=False): - with log_lock: - timestamp = current_timestamp() - if self.last_char != '\n': - log_file.write(message) - else: - log_file.write(f"[{timestamp}] {message}") - - try: - log_file.flush() - except Exception: - pass - - self.last_char = message if message == '' else message[-1] - - if not file_only: - with std_log_lock: - if self.is_stdout: - write_stdout(message) - original_stdout.flush() - else: - write_stderr(message) - original_stderr.flush() - - def flush(self): - try: - log_file.flush() - except Exception: - pass - - with std_log_lock: - try: - if self.is_stdout: - original_stdout.flush() - else: - original_stderr.flush() - except (OSError, ValueError): - pass - - def close(self): - self.flush() - - def reconfigure(self, *args, **kwargs): - pass - - # You can close through sys.stderr.close_log() - def close_log(self): - sys.stderr = original_stderr - sys.stdout = original_stdout - log_file.close() - - def close_log(): - sys.stderr = original_stderr - sys.stdout = original_stdout - log_file.close() - - - if enable_file_logging: - sys.stdout = ComfyUIManagerLogger(True) - stderr_wrapper = ComfyUIManagerLogger(False) - sys.stderr = stderr_wrapper - - atexit.register(close_log) - else: - sys.stdout.close_log = lambda: None - stderr_wrapper = None - - - class LoggingHandler(logging.Handler): - def emit(self, record): - global is_start_mode - - message = record.getMessage() - - if is_start_mode: - match = re.search(pat_import_fail, message) - if match: - import_failed_extensions.add(match.group(1).strip()) - - if 'Traceback' in message: - file_lists = self._extract_file_paths(message) - origin_info = extract_origin_module_from_strings(file_lists) - if origin_info is not None: - name, origin_path = origin_info - - if name != 'comfyui-manager': - if name not in cm_global.error_dict: - cm_global.error_dict[name] = {'name': name, 'path': origin_path, 'msg': ''} - - cm_global.error_dict[name]['msg'] += message - - if 'Starting server' in message: - is_start_mode = False - finalize_startup() - - if stderr_wrapper: - stderr_wrapper.sync_write(message+'\n', file_only=True) - - def _extract_file_paths(self, msg): - file_paths = [] - for line in msg.split('\n'): - match = re.findall(r'File \"(.*?)\", line \d+', line) - for x in match: - if not x.startswith('<'): - file_paths.extend(match) - return file_paths - - - logging.getLogger().addHandler(LoggingHandler()) - - -except Exception as e: - print(f"[ComfyUI-Manager] Logging failed: {e}") - - -def ensure_dependencies(): - try: - import git # noqa: F401 - import toml # noqa: F401 - import rich # noqa: F401 - import chardet # noqa: F401 - except ModuleNotFoundError: - my_path = os.path.dirname(__file__) - requirements_path = os.path.join(my_path, "requirements.txt") - - print("## ComfyUI-Manager: installing dependencies. (GitPython)") - try: - subprocess.check_output(manager_util.make_pip_cmd(['install', '-r', requirements_path])) - except subprocess.CalledProcessError: - print("## [ERROR] ComfyUI-Manager: Attempting to reinstall dependencies using an alternative method.") - try: - subprocess.check_output(manager_util.make_pip_cmd(['install', '--user', '-r', requirements_path])) - except subprocess.CalledProcessError: - print("## [ERROR] ComfyUI-Manager: Failed to install the GitPython package in the correct Python environment. Please install it manually in the appropriate environment. (You can seek help at https://app.element.io/#/room/%23comfyui_space%3Amatrix.org)") - - try: - print("## ComfyUI-Manager: installing dependencies done.") - except: - # maybe we should sys.exit() here? there is at least two screens worth of error messages still being pumped after our error messages - print("## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed") - -ensure_dependencies() - - -print("** ComfyUI startup time:", current_timestamp()) -print("** Platform:", platform.system()) -print("** Python version:", sys.version) -print("** Python executable:", sys.executable) -print("** ComfyUI Path:", comfy_path) -print("** ComfyUI Base Folder Path:", comfy_base_path) -print("** User directory:", folder_paths.user_directory) -print("** ComfyUI-Manager config path:", manager_config_path) - - -if log_path_base is not None: - print("** Log path:", os.path.abspath(f'{log_path_base}.log')) -else: - print("** Log path: file logging is disabled") - - -def read_downgrade_blacklist(): - try: - if 'downgrade_blacklist' in default_conf: - items = default_conf['downgrade_blacklist'].split(',') - items = [x.strip() for x in items if x != ''] - cm_global.pip_downgrade_blacklist += items - cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist)) - except: - pass - - -read_downgrade_blacklist() - - -def check_bypass_ssl(): - try: - import ssl - if 'bypass_ssl' in default_conf and default_conf['bypass_ssl'].lower() == 'true': - print(f"[ComfyUI-Manager] WARN: Unsafe - SSL verification bypass option is Enabled. (see {manager_config_path})") - ssl._create_default_https_context = ssl._create_unverified_context # SSL certificate error fix. - except Exception: - pass - -check_bypass_ssl() - - -# Perform install -processed_install = set() -# Use manager_files_path for consistency (fixes path inconsistency bug) -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) - - -def is_installed(name): - name = name.strip() - - if name.startswith('#'): - return True - - pattern = r'([^<>!~=]+)([<>!~=]=?)([0-9.a-zA-Z]*)' - match = re.search(pattern, name) - - if match: - name = match.group(1) - - if name in cm_global.pip_blacklist: - return True - - if name in cm_global.pip_downgrade_blacklist: - pips = manager_util.get_installed_packages() - - if match is None: - if name in pips: - return True - elif match.group(2) in ['<=', '==', '<', '~=']: - if name in pips: - if manager_util.StrictVersion(pips[name]) >= manager_util.StrictVersion(match.group(3)): - print(f"[ComfyUI-Manager] skip black listed pip installation: '{name}'") - return True - - pkg = manager_util.get_installed_packages().get(name.lower()) - if pkg is None: - return False # update if not installed - - if match is None: - return True # don't update if version is not specified - - if match.group(2) in ['>', '>=']: - if manager_util.StrictVersion(pkg) < manager_util.StrictVersion(match.group(3)): - return False - elif manager_util.StrictVersion(pkg) > manager_util.StrictVersion(match.group(3)): - print(f"[SKIP] Downgrading pip package isn't allowed: {name.lower()} (cur={pkg})") - - if match.group(2) == '==': - if manager_util.StrictVersion(pkg) < manager_util.StrictVersion(match.group(3)): - return False - - if match.group(2) == '~=': - if manager_util.StrictVersion(pkg) == manager_util.StrictVersion(match.group(3)): - return False - - return True # prevent downgrade - - -if os.path.exists(restore_snapshot_path): - try: - cloned_repos = [] - - def msg_capture(stream, prefix): - stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace') - for msg in stream: - if msg.startswith("CLONE: "): - cloned_repos.append(msg[7:]) - if prefix == '[!]': - print(prefix, msg, end="", file=sys.stderr) - else: - print(prefix, msg, end="") - - elif prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg): - if msg.startswith('100%'): - print('\r' + msg, end="", file=sys.stderr), - else: - print('\r'+msg[:-1], end="", file=sys.stderr), - else: - if prefix == '[!]': - print(prefix, msg, end="", file=sys.stderr) - else: - print(prefix, msg, end="") - - print("[ComfyUI-Manager] Restore snapshot.") - new_env = os.environ.copy() - if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env: - new_env["COMFYUI_FOLDERS_BASE_PATH"] = comfy_path - - cmd_str = [sys.executable, cm_cli_path, 'restore-snapshot', restore_snapshot_path] - exit_code = process_wrap(cmd_str, custom_nodes_base_path, handler=msg_capture, env=new_env) - - if exit_code != 0: - print("[ComfyUI-Manager] Restore snapshot failed.") - else: - print("[ComfyUI-Manager] Restore snapshot done.") - - except Exception as e: - print(e) - print("[ComfyUI-Manager] Restore snapshot failed.") - - os.remove(restore_snapshot_path) - - -def execute_lazy_install_script(repo_path, executable): - global processed_install - - install_script_path = os.path.join(repo_path, "install.py") - requirements_path = os.path.join(repo_path, "requirements.txt") - - if os.path.exists(requirements_path): - print(f"Install: pip packages for '{repo_path}'") - - lines = manager_util.robust_readlines(requirements_path) - for line in lines: - package_name = remap_pip_package(line.strip()) - package_name = package_name.split('#')[0].strip() - if package_name and not is_installed(package_name): - if '--index-url' in package_name: - s = package_name.split('--index-url') - install_cmd = manager_util.make_pip_cmd(["install", s[0].strip(), '--index-url', s[1].strip()]) - else: - install_cmd = manager_util.make_pip_cmd(["install", package_name]) - - process_wrap(install_cmd, repo_path) - - if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install: - processed_install.add(f'{repo_path}/install.py') - print(f"Install: install script for '{repo_path}'") - install_cmd = [executable, "install.py"] - - new_env = os.environ.copy() - if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env: - new_env["COMFYUI_FOLDERS_BASE_PATH"] = comfy_path - process_wrap(install_cmd, repo_path, env=new_env) - - -def execute_lazy_cnr_switch(target, zip_url, from_path, to_path, no_deps, custom_nodes_path): - import uuid - import shutil - - # 1. download - archive_name = f"CNR_temp_{str(uuid.uuid4())}.zip" # should be unpredictable name - security precaution - download_path = os.path.join(custom_nodes_path, archive_name) - manager_downloader.download_url(zip_url, custom_nodes_path, archive_name) - - # 2. extract files into @ - extracted = manager_util.extract_package_as_zip(download_path, from_path) - os.remove(download_path) - - if extracted is None: - if len(os.listdir(from_path)) == 0: - shutil.rmtree(from_path) - - print(f'Empty archive file: {target}') - return False - - - # 3. calculate garbage files (.tracking - extracted) - tracking_info_file = os.path.join(from_path, '.tracking') - prev_files = set() - with open(tracking_info_file, 'r') as f: - for line in f: - prev_files.add(line.strip()) - garbage = prev_files.difference(extracted) - garbage = [os.path.join(custom_nodes_path, x) for x in garbage] - - # 4-1. remove garbage files - for x in garbage: - if os.path.isfile(x): - os.remove(x) - - # 4-2. remove garbage dir if empty - for x in garbage: - if os.path.isdir(x): - if not os.listdir(x): - os.rmdir(x) - - # 5. rename dir name @ ==> @ - print(f"'{from_path}' is moved to '{to_path}'") - shutil.move(from_path, to_path) - - # 6. create .tracking file - tracking_info_file = os.path.join(to_path, '.tracking') - with open(tracking_info_file, "w", encoding='utf-8') as file: - file.write('\n'.join(list(extracted))) - - -script_executed = False - -def execute_startup_script(): - global script_executed - print("\n#######################################################################") - print("[ComfyUI-Manager] Starting dependency installation/(de)activation for the extension\n") - - custom_nodelist_cache = None - - def get_custom_node_paths(): - nonlocal custom_nodelist_cache - if custom_nodelist_cache is None: - custom_nodelist_cache = set() - for base in folder_paths.get_folder_paths('custom_nodes'): - for x in os.listdir(base): - fullpath = os.path.join(base, x) - if os.path.isdir(fullpath): - custom_nodelist_cache.add(fullpath) - - return custom_nodelist_cache - - def execute_lazy_delete(path): - # Validate to prevent arbitrary paths from being deleted - if path not in get_custom_node_paths(): - logging.error(f"## ComfyUI-Manager: The scheduled '{path}' is not a custom node path, so the deletion has been canceled.") - return - - if not os.path.exists(path): - logging.info(f"## ComfyUI-Manager: SKIP-DELETE => '{path}' (already deleted)") - return - - try: - shutil.rmtree(path) - logging.info(f"## ComfyUI-Manager: DELETE => '{path}'") - except Exception as e: - logging.error(f"## ComfyUI-Manager: Failed to delete '{path}' ({e})") - - executed = set() - # Read each line from the file and convert it to a list using eval - with open(script_list_path, 'r', encoding="UTF-8", errors="ignore") as file: - for line in file: - if line in executed: - continue - - executed.add(line) - - try: - script = ast.literal_eval(line) - - if script[1].startswith('#') and script[1] != '#FORCE': - if script[1] == "#LAZY-INSTALL-SCRIPT": - execute_lazy_install_script(script[0], script[2]) - - elif script[1] == "#LAZY-CNR-SWITCH-SCRIPT": - execute_lazy_cnr_switch(script[0], script[2], script[3], script[4], script[5], script[6]) - execute_lazy_install_script(script[3], script[7]) - - elif script[1] == "#LAZY-DELETE-NODEPACK": - execute_lazy_delete(script[2]) - - elif os.path.exists(script[0]): - if script[1] == "#FORCE": - del script[1] - else: - if 'pip' in script[1:] and 'install' in script[1:] and is_installed(script[-1]): - continue - - print(f"\n## ComfyUI-Manager: EXECUTE => {script[1:]}") - print(f"\n## Execute management script for '{script[0]}'") - - new_env = os.environ.copy() - if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env: - new_env["COMFYUI_FOLDERS_BASE_PATH"] = comfy_path - exit_code = process_wrap(script[1:], script[0], env=new_env) - - if exit_code != 0: - print(f"management script failed: {script[0]}") - else: - print(f"\n## ComfyUI-Manager: CANCELED => {script[1:]}") - - except Exception as e: - print(f"[ERROR] Failed to execute management script: {line} / {e}") - - # Remove the script_list_path file - if os.path.exists(script_list_path): - script_executed = True - os.remove(script_list_path) - - print("\n[ComfyUI-Manager] Startup script completed.") - print("#######################################################################\n") - - -# Check if script_list_path exists -# Block startup-scripts on old ComfyUI (security measure) -if not _has_system_user_api: - if os.path.exists(script_list_path): - print("[ComfyUI-Manager] Startup scripts blocked on old ComfyUI version.") -elif os.path.exists(script_list_path): - execute_startup_script() - - -pip_fixer.fix_broken() - -del processed_install -del pip_fixer -manager_util.clear_pip_cache() - -if script_executed: - # Restart - print("[ComfyUI-Manager] Restarting to reapply dependency installation.") - - if '__COMFY_CLI_SESSION__' in os.environ: - with open(os.path.join(os.environ['__COMFY_CLI_SESSION__'] + '.reboot'), 'w'): - pass - - print("--------------------------------------------------------------------------\n") - exit(0) - else: - sys_argv = sys.argv.copy() - - if sys_argv[0].endswith("__main__.py"): # this is a python module - module_name = os.path.basename(os.path.dirname(sys_argv[0])) - cmds = [sys.executable, '-m', module_name] + sys_argv[1:] - elif sys.platform.startswith('win32'): - cmds = ['"' + sys.executable + '"', '"' + sys_argv[0] + '"'] + sys_argv[1:] - else: - cmds = [sys.executable] + sys_argv - - print(f"Command: {cmds}", flush=True) - print("--------------------------------------------------------------------------\n") - - os.execv(sys.executable, cmds) - - -def check_windows_event_loop_policy(): - try: - import configparser - config = configparser.ConfigParser(strict=False) - config.read(manager_config_path) - default_conf = config['default'] - - if 'windows_selector_event_loop_policy' in default_conf and default_conf['windows_selector_event_loop_policy'].lower() == 'true': - try: - import asyncio - import asyncio.windows_events - asyncio.set_event_loop_policy(asyncio.windows_events.WindowsSelectorEventLoopPolicy()) - print("[ComfyUI-Manager] Windows event loop policy mode enabled") - except Exception as e: - print(f"[ComfyUI-Manager] WARN: Windows initialization fail: {e}") - except Exception: - pass - - -if platform.system() == 'Windows': - check_windows_event_loop_policy() diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index b4c1d244..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name = "comfyui-manager" -description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI." -version = "3.39.3" -license = { file = "LICENSE.txt" } -dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"] - -[project.urls] -Repository = "https://github.com/ltdrdata/ComfyUI-Manager" -# Used by Comfy Registry https://registry.comfy.org - -[tool.comfy] -PublisherId = "drltdata" -DisplayName = "ComfyUI-Manager" -Icon = "" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b179f07f..00000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -GitPython -PyGithub -matrix-nio -transformers -huggingface-hub -typer -rich -typing-extensions -toml -uv -chardet diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index 26ec1d89..00000000 --- a/ruff.toml +++ /dev/null @@ -1,12 +0,0 @@ -# Disable all rules by default -lint.ignore = ["ALL"] - -# Enable specific rules -lint.select = [ - "S307", # suspicious-eval-usage - # The "F" series in Ruff stands for "Pyflakes" rules, which catch various Python syntax errors and undefined names. - # See all rules here: https://docs.astral.sh/ruff/rules/#pyflakes-f - "F", -] - -exclude = ["*.ipynb"] diff --git a/scan.sh b/scan.sh deleted file mode 100755 index c35e90a5..00000000 --- a/scan.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -rm ~/.tmp/default/*.py > /dev/null 2>&1 -python scanner.py ~/.tmp/default $* -cp extension-node-map.json node_db/new/. - -echo "Integrity check" -if [ -f "check2.sh" ]; then - ./check2.sh -else - ./check.sh -fi \ No newline at end of file diff --git a/scanner.py b/scanner.py deleted file mode 100644 index d5307e74..00000000 --- a/scanner.py +++ /dev/null @@ -1,1649 +0,0 @@ -import ast -import re -import os -import json -import threading -from collections import defaultdict -from git import Repo -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, Auth -from pathlib import Path -from typing import Set, Dict, Optional - -# Scanner version for cache invalidation -SCANNER_VERSION = "2.0.13" # Add fallback for dynamic v3 node_id - -# Cache for extract_nodes and extract_nodes_enhanced results -_extract_nodes_cache: Dict[str, Set[str]] = {} -_extract_nodes_enhanced_cache: Dict[str, Set[str]] = {} -_file_mtime_cache: Dict[Path, float] = {} - - -def _get_repo_root(file_path: Path) -> Optional[Path]: - """Find the repository root directory containing .git""" - current = file_path if file_path.is_dir() else file_path.parent - while current != current.parent: - if (current / ".git").exists(): - return current - current = current.parent - return None - - -def _get_repo_hash(repo_path: Path) -> str: - """Get git commit hash or fallback identifier""" - git_dir = repo_path / ".git" - if not git_dir.exists(): - return "" - - try: - # Read HEAD to get current commit - head_file = git_dir / "HEAD" - if head_file.exists(): - head_content = head_file.read_text().strip() - if head_content.startswith("ref:"): - # HEAD points to a ref - ref_path = git_dir / head_content[5:].strip() - if ref_path.exists(): - commit_hash = ref_path.read_text().strip() - return commit_hash[:16] # First 16 chars - else: - # Detached HEAD - return head_content[:16] - except: - pass - - return "" - - -def _load_per_repo_cache(repo_path: Path) -> Optional[tuple]: - """Load nodes and metadata from per-repo cache - - Returns: - tuple: (nodes_set, metadata_dict) or None if cache invalid - """ - cache_file = repo_path / ".git" / "nodecache.json" - - if not cache_file.exists(): - return None - - try: - with open(cache_file, 'r') as f: - cache_data = json.load(f) - - # Verify scanner version - if cache_data.get('scanner_version') != SCANNER_VERSION: - return None - - # Verify git hash - current_hash = _get_repo_hash(repo_path) - if cache_data.get('git_hash') != current_hash: - return None - - # Return nodes and metadata - nodes = cache_data.get('nodes', []) - metadata = cache_data.get('metadata', {}) - return (set(nodes) if nodes else set(), metadata) - - except: - return None - - -def _save_per_repo_cache(repo_path: Path, all_nodes: Set[str], metadata: dict = None): - """Save nodes and metadata to per-repo cache""" - cache_file = repo_path / ".git" / "nodecache.json" - - if not cache_file.parent.exists(): - return - - git_hash = _get_repo_hash(repo_path) - cache_data = { - "scanner_version": SCANNER_VERSION, - "git_hash": git_hash, - "scanned_at": datetime.datetime.now().isoformat(), - "nodes": sorted(list(all_nodes)), - "metadata": metadata if metadata else {} - } - - try: - with open(cache_file, 'w') as f: - json.dump(cache_data, f, indent=2) - except: - pass # Silently fail - cache is optional - - -def download_url(url, dest_folder, filename=None): - # Ensure the destination folder exists - if not os.path.exists(dest_folder): - os.makedirs(dest_folder) - - # Extract filename from URL if not provided - if filename is None: - filename = os.path.basename(url) - - # Full path to save the file - dest_path = os.path.join(dest_folder, filename) - - # Download the file - response = requests.get(url, stream=True) - if response.status_code == 200: - with open(dest_path, 'wb') as file: - for chunk in response.iter_content(chunk_size=1024): - if chunk: - file.write(chunk) - else: - raise Exception(f"Failed to download file from {url}") - - -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 - python3 scanner.py --skip-all --force-rescan - - # 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 --force-rescan - ''' - ) - - 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') - parser.add_argument('--force-rescan', action='store_true', - help='Force rescan all nodes (ignore cache)') - - # 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 - - -# 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 - -# Thread-safe git error state -_git_error_lock = threading.Lock() -_git_errors: defaultdict = defaultdict(list) # category -> list[{'repo': str, 'op': str, 'msg': str}] - -# Ordered categories: (key, display label, compiled regex). First match wins. -# Single source of truth — add new categories here only. -_GIT_ERROR_CATEGORIES = [ - ('repository_not_found', 'Repository Not Found', re.compile( - r'repository\s+not\s+found|does\s+not\s+exist|\b404\b|remote:\s*repository\s+not\s+found', - re.IGNORECASE - )), - ('divergent_branch', 'Divergent Branch', re.compile( - r'divergent\s+branches|need\s+to\s+specify\s+how\s+to\s+reconcile\s+divergent\s+branches', - re.IGNORECASE - )), - ('auth_failed', 'Authentication Failed', re.compile( - r'authentication\s+failed|could\s+not\s+read\s+username|invalid\s+username|invalid\s+password|auth\s+failed', - re.IGNORECASE - )), - ('network_error', 'Network Error', re.compile( - r'could\s+not\s+resolve\s+host|connection\s+refused|timed?\s*out|failed\s+to\s+connect|' - r'network\s+is\s+unreachable|temporary\s+failure\s+in\s+name\s+resolution', - re.IGNORECASE - )), - ('merge_conflict', 'Merge Conflict', re.compile( - r'merge\s+conflict|\bCONFLICT\b|automatic\s+merge\s+failed', - re.IGNORECASE - )), - ('permission_denied', 'Permission Denied', re.compile( - r'permission\s+denied|access\s+denied|operation\s+not\s+permitted|publickey', - re.IGNORECASE - )), -] - - -def _categorize_git_error(error_str: str) -> str: - """Classify a git error string into a category. First match wins.""" - for category, _label, pattern in _GIT_ERROR_CATEGORIES: - if pattern.search(error_str): - return category - return 'other' - - -def _record_git_error(repo_name: str, op: str, error: Exception) -> None: - """Record a git error in the thread-safe collector.""" - category = _categorize_git_error(str(error)) - with _git_error_lock: - _git_errors[category].append({'repo': repo_name, 'op': op, 'msg': str(error)}) - - -def _report_git_errors() -> None: - """Print a grouped summary of git errors by category.""" - if not _git_errors: - return - - total = sum(len(v) for v in _git_errors.values()) - print(f"\n{'='*60}") - print(f"Git Operation Errors Summary: {total} failure(s)") - print(f"{'='*60}") - - for category, label, _pattern in _GIT_ERROR_CATEGORIES: - entries = _git_errors.get(category, []) - if not entries: - continue - print(f"\n[{label}] ({len(entries)} repo(s))") - for entry in entries: - print(f" • {entry['repo']} ({entry['op']}): {entry['msg']}") - - other_entries = _git_errors.get('other', []) - if other_entries: - print(f"\n[Other] ({len(other_entries)} repo(s))") - for entry in other_entries: - print(f" • {entry['repo']} ({entry['op']}): {entry['msg']}") - - print(f"{'='*60}\n") - - -def extract_nodes(code_text): - global parse_cnt - - # Check cache first - cache_key = hash(code_text) - if cache_key in _extract_nodes_cache: - return _extract_nodes_cache[cache_key].copy() - - try: - if parse_cnt % 100 == 0: - print(".", end="", flush=True) - parse_cnt += 1 - - code_text = re.sub(r'\\[^"\']', '', 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))) - - for assignment in assignments: - # 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: - node_class_mappings = None - - if node_class_mappings: - s = set() - - for key in node_class_mappings.keys: - if key is not None and isinstance(key.value, str): - s.add(key.value.strip()) - - # Cache the result - _extract_nodes_cache[cache_key] = s - return s - else: - # Cache empty result - _extract_nodes_cache[cache_key] = set() - return set() - except: - # Cache empty result on error - _extract_nodes_cache[cache_key] = set() - return set() - -def extract_nodes_from_repo(repo_path: Path, verbose: bool = False, force_rescan: bool = False) -> tuple: - """ - Extract all nodes and metadata from a repository with per-repo caching. - - Automatically caches results in .git/nodecache.json. - Cache is invalidated when: - - Git commit hash changes - - Scanner version changes - - force_rescan flag is True - - Args: - repo_path: Path to repository root - verbose: If True, print UI-only extension detection messages - force_rescan: If True, ignore cache and force fresh scan - - Returns: - tuple: (nodes_set, metadata_dict) - """ - # Ensure path is absolute - repo_path = repo_path.resolve() - - # Check per-repo cache first (unless force_rescan is True) - if not force_rescan: - cached_result = _load_per_repo_cache(repo_path) - if cached_result is not None: - return cached_result - - # Cache miss - scan all .py files - all_nodes = set() - all_metadata = {} - py_files = list(repo_path.rglob("*.py")) - - # Filter out __pycache__, .git, and other hidden directories - filtered_files = [] - for f in py_files: - try: - rel_path = f.relative_to(repo_path) - # Skip __pycache__, .git, and any directory starting with . - if '__pycache__' not in str(rel_path) and not any(part.startswith('.') for part in rel_path.parts): - filtered_files.append(f) - except: - continue - py_files = filtered_files - - for py_file in py_files: - try: - # Read file with proper encoding - with open(py_file, 'r', encoding='utf-8', errors='ignore') as f: - code = f.read() - - if code: - # Extract nodes using SAME logic as scan_in_file - # V1 nodes (enhanced with fallback patterns) - nodes = extract_nodes_enhanced(code, py_file, visited=set(), verbose=verbose) - all_nodes.update(nodes) - - # V3 nodes detection - v3_nodes = extract_v3_nodes(code) - all_nodes.update(v3_nodes) - - # Dict parsing - exclude commented NODE_CLASS_MAPPINGS lines - pattern = r"_CLASS_MAPPINGS\s*(?::\s*\w+\s*)?=\s*(?:\\\s*)?{([^}]*)}" - regex = re.compile(pattern, re.MULTILINE | re.DOTALL) - - for match_obj in regex.finditer(code): - # Get the line where NODE_CLASS_MAPPINGS is defined - match_start = match_obj.start() - line_start = code.rfind('\n', 0, match_start) + 1 - line_end = code.find('\n', match_start) - if line_end == -1: - line_end = len(code) - line = code[line_start:line_end] - - # Skip if line starts with # (commented) - if re.match(r'^\s*#', line): - continue - - match = match_obj.group(1) - - # Filter out commented lines from dict content - match_lines = match.split('\n') - match_filtered = '\n'.join( - line for line in match_lines - if not re.match(r'^\s*#', line) - ) - - # Extract key-value pairs with double quotes - key_value_pairs = re.findall(r"\"([^\"]*)\"\s*:\s*([^,\n]*)", match_filtered) - for key, value in key_value_pairs: - all_nodes.add(key.strip()) - - # Extract key-value pairs with single quotes - key_value_pairs = re.findall(r"'([^']*)'\s*:\s*([^,\n]*)", match_filtered) - for key, value in key_value_pairs: - all_nodes.add(key.strip()) - - # Handle .update() pattern (AFTER comment removal) - code_cleaned = re.sub(r'^#.*?$', '', code, flags=re.MULTILINE) - - update_pattern = r"_CLASS_MAPPINGS\.update\s*\(\s*{([^}]*)}\s*\)" - update_match = re.search(update_pattern, code_cleaned, re.DOTALL) - if update_match: - update_dict_text = update_match.group(1) - # Extract key-value pairs (double quotes) - update_pairs = re.findall(r'"([^"]*)"\s*:\s*([^,\n]*)', update_dict_text) - for key, value in update_pairs: - all_nodes.add(key.strip()) - # Extract key-value pairs (single quotes) - update_pairs_single = re.findall(r"'([^']*)'\s*:\s*([^,\n]*)", update_dict_text) - for key, value in update_pairs_single: - all_nodes.add(key.strip()) - - # Additional regex patterns (AFTER comment removal) - patterns = [ - r'^[^=]*_CLASS_MAPPINGS\["(.*?)"\]', - r'^[^=]*_CLASS_MAPPINGS\[\'(.*?)\'\]', - r'@register_node\("(.+)",\s*\".+"\)', - r'"(\w+)"\s*:\s*{"class":\s*\w+\s*' - ] - - for pattern in patterns: - keys = re.findall(pattern, code_cleaned) - all_nodes.update(key.strip() for key in keys) - - # Extract metadata from this file - metadata = extract_metadata_only(str(py_file)) - all_metadata.update(metadata) - except Exception: - # Silently skip files that can't be read - continue - - # Save to per-repo cache - _save_per_repo_cache(repo_path, all_nodes, all_metadata) - - return (all_nodes, all_metadata) - - -def _verify_class_exists(node_name: str, code_text: str, file_path: Optional[Path] = None) -> tuple[bool, Optional[str], Optional[int]]: - """ - Verify that a node class exists and has ComfyUI node structure. - - Returns: (exists: bool, file_path: str, line_number: int) - - A valid ComfyUI node must have: - - Class definition (not commented) - - At least one of: INPUT_TYPES, RETURN_TYPES, FUNCTION method/attribute - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=SyntaxWarning) - tree = ast.parse(code_text) - except: - return (False, None, None) - - for node in ast.walk(tree): - if isinstance(node, ast.ClassDef): - if node.name == node_name or node.name.replace('_', '') == node_name.replace('_', ''): - # Found class definition - check if it has ComfyUI interface - has_input_types = False - has_return_types = False - has_function = False - - for item in node.body: - # Check for INPUT_TYPES method - if isinstance(item, ast.FunctionDef) and item.name == 'INPUT_TYPES': - has_input_types = True - # Check for RETURN_TYPES attribute - elif isinstance(item, ast.Assign): - for target in item.targets: - if isinstance(target, ast.Name): - if target.id == 'RETURN_TYPES': - has_return_types = True - elif target.id == 'FUNCTION': - has_function = True - # Check for FUNCTION method - elif isinstance(item, ast.FunctionDef): - has_function = True - - # Valid if has any ComfyUI signature - if has_input_types or has_return_types or has_function: - file_str = str(file_path) if file_path else None - return (True, file_str, node.lineno) - - return (False, None, None) - - -def _extract_display_name_mappings(code_text: str) -> Set[str]: - """ - Extract node names from NODE_DISPLAY_NAME_MAPPINGS. - - Pattern: - NODE_DISPLAY_NAME_MAPPINGS = { - "node_key": "Display Name", - ... - } - - Returns: - Set of node keys from NODE_DISPLAY_NAME_MAPPINGS - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=SyntaxWarning) - tree = ast.parse(code_text) - except: - return set() - - nodes = set() - - for node in tree.body: - if isinstance(node, ast.Assign): - for target in node.targets: - if isinstance(target, ast.Name) and target.id == 'NODE_DISPLAY_NAME_MAPPINGS': - if isinstance(node.value, ast.Dict): - for key in node.value.keys: - if isinstance(key, ast.Constant) and isinstance(key.value, str): - nodes.add(key.value.strip()) - - return nodes - - -def extract_nodes_enhanced( - code_text: str, - file_path: Optional[Path] = None, - visited: Optional[Set[Path]] = None, - verbose: bool = False -) -> Set[str]: - """ - Enhanced node extraction with multi-layer detection system. - - Scanner 2.0.11 - Comprehensive detection strategy: - - Phase 1: NODE_CLASS_MAPPINGS dict literal - - Phase 2: Class.NAME attribute access (e.g., FreeChat.NAME) - - Phase 3: Item assignment (NODE_CLASS_MAPPINGS["key"] = value) - - Phase 4: Class existence verification (detects active classes even if registration commented) - - Phase 5: NODE_DISPLAY_NAME_MAPPINGS cross-reference - - Phase 6: Empty dict detection (UI-only extensions, logging only) - - Fixed Bugs: - - Scanner 2.0.9: Fallback cascade prevented Phase 3 execution - - Scanner 2.0.10: Missed active classes with commented registrations (15 false negatives) - - Args: - code_text: Python source code - file_path: Path to file (for logging and caching) - visited: Visited paths (for circular import prevention) - verbose: If True, print UI-only extension detection messages - - Returns: - Set of node names (union of all detected patterns) - """ - # Check file-based cache if file_path provided - if file_path is not None: - try: - file_path_obj = Path(file_path) if not isinstance(file_path, Path) else file_path - if file_path_obj.exists(): - current_mtime = file_path_obj.stat().st_mtime - - # Check if we have cached result with matching mtime and scanner version - if file_path_obj in _file_mtime_cache: - cached_mtime = _file_mtime_cache[file_path_obj] - cache_key = (str(file_path_obj), cached_mtime, SCANNER_VERSION) - - if current_mtime == cached_mtime and cache_key in _extract_nodes_enhanced_cache: - return _extract_nodes_enhanced_cache[cache_key].copy() - except: - pass # Ignore cache errors, proceed with normal execution - - # Suppress warnings from AST parsing - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=SyntaxWarning) - warnings.filterwarnings('ignore', category=DeprecationWarning) - - # Phase 1: Original extract_nodes() - dict literal - phase1_nodes = extract_nodes(code_text) - - # Phase 2: Class.NAME pattern - if visited is None: - visited = set() - phase2_nodes = _fallback_classname_resolver(code_text, file_path) - - # Phase 3: Item assignment pattern - phase3_nodes = _fallback_item_assignment(code_text) - - # Phase 4: NODE_DISPLAY_NAME_MAPPINGS cross-reference (NEW in 2.0.11) - # This catches nodes that are in display names but not in NODE_CLASS_MAPPINGS - phase4_nodes = _extract_display_name_mappings(code_text) - - # Phase 5: Class existence verification ONLY for display name candidates (NEW in 2.0.11) - # This phase is CONSERVATIVE - only verify classes that appear in display names - # This catches the specific Scanner 2.0.10 bug pattern: - # - NODE_CLASS_MAPPINGS registration is commented - # - NODE_DISPLAY_NAME_MAPPINGS still has the entry - # - Class implementation exists - # Example: Bjornulf_ollamaLoader in Bjornulf_custom_nodes - phase5_nodes = set() - for node_name in phase4_nodes: - # Only check classes that appear in display names but not in registrations - if node_name not in (phase1_nodes | phase2_nodes | phase3_nodes): - exists, _, _ = _verify_class_exists(node_name, code_text, file_path) - if exists: - phase5_nodes.add(node_name) - - # Phase 6: Dict comprehension pattern (NEW in 2.0.12) - # Detects: NODE_CLASS_MAPPINGS = {cls.__name__: cls for cls in to_export} - # Example: TobiasGlaubach/ComfyUI-TG_PyCode - phase6_nodes = _fallback_dict_comprehension(code_text, file_path) - - # Phase 7: Import-based class names for dict comprehension (NEW in 2.0.12) - # Detects imported classes that are added to export lists - phase7_nodes = _fallback_import_class_names(code_text, file_path) - - # Union all results (FIX: Scanner 2.0.9 bug + Scanner 2.0.10 bug + Scanner 2.0.12 dict comp) - # 2.0.9: Used early return which missed Phase 3 nodes - # 2.0.10: Only checked registrations, missed classes referenced in display names - # 2.0.12: Added dict comprehension and import-based class detection - all_nodes = phase1_nodes | phase2_nodes | phase3_nodes | phase4_nodes | phase5_nodes | phase6_nodes | phase7_nodes - - # Phase 8: Empty dict detector (logging only, doesn't add nodes) - if not all_nodes: - _fallback_empty_dict_detector(code_text, file_path, verbose) - - # Cache the result - if file_path is not None: - try: - file_path_obj = Path(file_path) if not isinstance(file_path, Path) else file_path - if file_path_obj.exists(): - current_mtime = file_path_obj.stat().st_mtime - cache_key = (str(file_path_obj), current_mtime, SCANNER_VERSION) - _extract_nodes_enhanced_cache[cache_key] = all_nodes - _file_mtime_cache[file_path_obj] = current_mtime - except: - pass - - return all_nodes - - -def _fallback_classname_resolver(code_text: str, file_path: Optional[Path]) -> Set[str]: - """ - Detect Class.NAME pattern in NODE_CLASS_MAPPINGS. - - Pattern: - NODE_CLASS_MAPPINGS = { - FreeChat.NAME: FreeChat, - PaidChat.NAME: PaidChat - } - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=SyntaxWarning) - parsed = ast.parse(code_text) - except: - return set() - - nodes = set() - - for node in parsed.body: - if isinstance(node, ast.Assign): - for target in node.targets: - if isinstance(target, ast.Name) and target.id == 'NODE_CLASS_MAPPINGS': - if isinstance(node.value, ast.Dict): - for key in node.value.keys: - # Detect Class.NAME pattern - if isinstance(key, ast.Attribute): - if isinstance(key.value, ast.Name): - # Use class name as node name - nodes.add(key.value.id) - # Also handle literal strings - elif isinstance(key, ast.Constant) and isinstance(key.value, str): - nodes.add(key.value.strip()) - - return nodes - - -def _fallback_item_assignment(code_text: str) -> Set[str]: - """ - Detect item assignment pattern. - - Pattern: - NODE_CLASS_MAPPINGS = {} - NODE_CLASS_MAPPINGS["MyNode"] = MyNode - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=SyntaxWarning) - parsed = ast.parse(code_text) - except: - return set() - - nodes = set() - - for node in ast.walk(parsed): - if isinstance(node, ast.Assign): - for target in node.targets: - if isinstance(target, ast.Subscript): - if (isinstance(target.value, ast.Name) and - target.value.id in ['NODE_CLASS_MAPPINGS', 'NODE_CONFIG']): - # Extract key - if isinstance(target.slice, ast.Constant): - if isinstance(target.slice.value, str): - nodes.add(target.slice.value) - - return nodes - - -def _fallback_dict_comprehension(code_text: str, file_path: Optional[Path] = None) -> Set[str]: - """ - Detect dict comprehension pattern with __name__ attribute access. - - Pattern: - NODE_CLASS_MAPPINGS = {cls.__name__: cls for cls in to_export} - NODE_CLASS_MAPPINGS = {c.__name__: c for c in [ClassA, ClassB]} - - This function detects dict comprehension assignments to NODE_CLASS_MAPPINGS - and extracts class names from the iterable (list literal or variable reference). - - Returns: - Set of class names extracted from the dict comprehension - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=SyntaxWarning) - parsed = ast.parse(code_text) - except: - return set() - - nodes = set() - export_lists = {} # Track list variables and their contents - - # First pass: collect list assignments (to_export = [...], exports = [...]) - for node in ast.walk(parsed): - if isinstance(node, ast.Assign): - for target in node.targets: - if isinstance(target, ast.Name): - var_name = target.id - # Check for list literal - if isinstance(node.value, ast.List): - class_names = set() - for elt in node.value.elts: - if isinstance(elt, ast.Name): - class_names.add(elt.id) - export_lists[var_name] = class_names - - # Handle augmented assignment: to_export += [...] - elif isinstance(node, ast.AugAssign): - if isinstance(node.target, ast.Name) and isinstance(node.op, ast.Add): - var_name = node.target.id - if isinstance(node.value, ast.List): - class_names = set() - for elt in node.value.elts: - if isinstance(elt, ast.Name): - class_names.add(elt.id) - if var_name in export_lists: - export_lists[var_name].update(class_names) - else: - export_lists[var_name] = class_names - - # Second pass: find NODE_CLASS_MAPPINGS dict comprehension - for node in ast.walk(parsed): - if isinstance(node, ast.Assign): - for target in node.targets: - if isinstance(target, ast.Name) and target.id in ['NODE_CLASS_MAPPINGS', 'NODE_CONFIG']: - # Check for dict comprehension - if isinstance(node.value, ast.DictComp): - dictcomp = node.value - - # Check if key is cls.__name__ pattern - key = dictcomp.key - if isinstance(key, ast.Attribute) and key.attr == '__name__': - # Get the iterable from the first generator - for generator in dictcomp.generators: - iter_node = generator.iter - - # Case 1: Inline list [ClassA, ClassB, ...] - if isinstance(iter_node, ast.List): - for elt in iter_node.elts: - if isinstance(elt, ast.Name): - nodes.add(elt.id) - - # Case 2: Variable reference (to_export, exports, etc.) - elif isinstance(iter_node, ast.Name): - var_name = iter_node.id - if var_name in export_lists: - nodes.update(export_lists[var_name]) - - return nodes - - -def _fallback_import_class_names(code_text: str, file_path: Optional[Path] = None) -> Set[str]: - """ - Extract class names from imports that are added to export lists. - - Pattern: - from .module import ClassA, ClassB - to_export = [ClassA, ClassB] - NODE_CLASS_MAPPINGS = {cls.__name__: cls for cls in to_export} - - This is a complementary fallback that works with _fallback_dict_comprehension - to resolve import-based node registrations. - - Returns: - Set of imported class names that appear in export-like contexts - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=SyntaxWarning) - parsed = ast.parse(code_text) - except: - return set() - - # Collect imported names - imported_names = set() - for node in ast.walk(parsed): - if isinstance(node, ast.ImportFrom): - for alias in node.names: - name = alias.asname if alias.asname else alias.name - imported_names.add(name) - - # Check if these names appear in list assignments that feed into NODE_CLASS_MAPPINGS - export_candidates = set() - has_dict_comp_mapping = False - - for node in ast.walk(parsed): - # Check for dict comprehension NODE_CLASS_MAPPINGS - if isinstance(node, ast.Assign): - for target in node.targets: - if isinstance(target, ast.Name) and target.id == 'NODE_CLASS_MAPPINGS': - if isinstance(node.value, ast.DictComp): - has_dict_comp_mapping = True - - # Collect list contents - if isinstance(node, ast.Assign): - if isinstance(node.value, ast.List): - for elt in node.value.elts: - if isinstance(elt, ast.Name) and elt.id in imported_names: - export_candidates.add(elt.id) - - # Handle augmented assignment - elif isinstance(node, ast.AugAssign): - if isinstance(node.value, ast.List): - for elt in node.value.elts: - if isinstance(elt, ast.Name) and elt.id in imported_names: - export_candidates.add(elt.id) - - # Only return if there's a dict comprehension mapping - if has_dict_comp_mapping: - return export_candidates - - return set() - - -def _extract_repo_name(file_path: Path) -> str: - """ - Extract repository name from file path. - - Path structure: /home/rho/.tmp/analysis/temp/{author}_{reponame}/{path/to/file.py} - Returns: {author}_{reponame} or filename if extraction fails - """ - try: - parts = file_path.parts - # Find 'temp' directory in path - if 'temp' in parts: - temp_idx = parts.index('temp') - if temp_idx + 1 < len(parts): - # Next part after 'temp' is the repo directory - return parts[temp_idx + 1] - except (ValueError, IndexError): - pass - - # Fallback to filename if extraction fails - return file_path.name if hasattr(file_path, 'name') else str(file_path) - - -def _fallback_empty_dict_detector(code_text: str, file_path: Optional[Path], verbose: bool = False) -> None: - """ - Detect empty NODE_CLASS_MAPPINGS (UI-only extensions). - Logs for documentation purposes only (when verbose=True). - - Args: - code_text: Python source code to analyze - file_path: Path to the file being analyzed - verbose: If True, print detection messages - """ - empty_patterns = [ - 'NODE_CLASS_MAPPINGS = {}', - 'NODE_CLASS_MAPPINGS={}', - ] - - code_normalized = code_text.replace(' ', '').replace('\n', '') - - for pattern in empty_patterns: - pattern_normalized = pattern.replace(' ', '') - if pattern_normalized in code_normalized: - if file_path and verbose: - repo_name = _extract_repo_name(file_path) - print(f"Info: UI-only extension (empty NODE_CLASS_MAPPINGS): {repo_name}") - return - -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) - else: - # Fallback: use class name when node_id is dynamic/empty - nodes.add(node.name) - - return nodes - - -# scan -def extract_metadata_only(filename): - """Extract only metadata (@author, @title, etc) without node scanning""" - try: - with open(filename, encoding='utf-8', errors='ignore') as file: - code = file.read() - - metadata = {} - lines = code.strip().split('\n') - for line in lines: - if line.startswith('@'): - if line.startswith("@author:") or line.startswith("@title:") or line.startswith("@nickname:") or line.startswith("@description:"): - key, value = line[1:].strip().split(':', 1) - metadata[key.strip()] = value.strip() - - return metadata - except: - return {} - - -def scan_in_file(filename, is_builtin=False): - global builtin_nodes - - with open(filename, encoding='utf-8', errors='ignore') as file: - code = file.read() - - # 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 (enhanced with fallback patterns) - nodes |= extract_nodes_enhanced(code, file_path=Path(filename), visited=set()) - - # V3 nodes detection - nodes |= extract_v3_nodes(code) - code = re.sub(r'^#.*?$', '', code, flags=re.MULTILINE) - - def extract_keys(pattern, code): - keys = re.findall(pattern, code) - return {key.strip() for key in keys} - - def update_nodes(nodes, new_keys): - nodes |= new_keys - - patterns = [ - r'^[^=]*_CLASS_MAPPINGS\["(.*?)"\]', - r'^[^=]*_CLASS_MAPPINGS\[\'(.*?)\'\]', - r'@register_node\("(.+)",\s*\".+"\)', - r'"(\w+)"\s*:\s*{"class":\s*\w+\s*' - ] - - with concurrent.futures.ThreadPoolExecutor() as executor: - futures = {executor.submit(extract_keys, pattern, code): pattern for pattern in patterns} - for future in concurrent.futures.as_completed(futures): - update_nodes(nodes, future.result()) - - matches = regex.findall(code) - for match in matches: - dict_text = match - - key_value_pairs = re.findall(r"\"([^\"]*)\"\s*:\s*([^,\n]*)", dict_text) - for key, value in key_value_pairs: - class_dict[key.strip()] = value.strip() - - key_value_pairs = re.findall(r"'([^']*)'\s*:\s*([^,\n]*)", dict_text) - for key, value in key_value_pairs: - class_dict[key.strip()] = value.strip() - - for key, value in class_dict.items(): - nodes.add(key.strip()) - - update_pattern = r"_CLASS_MAPPINGS.update\s*\({([^}]*)}\)" - update_match = re.search(update_pattern, code) - if update_match: - update_dict_text = update_match.group(1) - update_key_value_pairs = re.findall(r"\"([^\"]*)\"\s*:\s*([^,\n]*)", update_dict_text) - for key, value in update_key_value_pairs: - class_dict[key.strip()] = value.strip() - nodes.add(key.strip()) - - metadata = {} - lines = code.strip().split('\n') - for line in lines: - if line.startswith('@'): - if line.startswith("@author:") or line.startswith("@title:") or line.startswith("@nickname:") or line.startswith("@description:"): - key, value = line[1:].strip().split(':', 1) - metadata[key.strip()] = value.strip() - - if is_builtin: - builtin_nodes += set(nodes) - else: - for x in builtin_nodes: - if x in nodes: - nodes.remove(x) - - return nodes, metadata - - -def get_py_file_paths(dirname): - file_paths = [] - - for root, dirs, files in os.walk(dirname): - if ".git" in root or "__pycache__" in root: - continue - - for file in files: - if file.endswith(".py"): - file_path = os.path.join(root, file) - file_paths.append(file_path) - - return file_paths - - -def get_nodes(target_dir): - py_files = [] - directories = [] - - for item in os.listdir(target_dir): - if ".git" in item or "__pycache__" in item: - continue - - path = os.path.abspath(os.path.join(target_dir, item)) - - if os.path.isfile(path) and item.endswith(".py"): - py_files.append(path) - elif os.path.isdir(path): - directories.append(path) - - 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) - - custom_nodes = data.get('custom_nodes', []) - git_clone_files = [] - for node in custom_nodes: - if node.get('install_type') == 'git-clone': - files = node.get('files', []) - if files: - git_clone_files.append((files[0], node.get('title'), node.get('preemptions'), node.get('nodename_pattern'))) - - git_clone_files.append(("https://github.com/comfyanonymous/ComfyUI", "ComfyUI", None, None)) - - return git_clone_files - - -def get_py_urls_from_json(json_file): - with open(json_file, encoding='utf-8') as file: - data = json.load(file) - - custom_nodes = data.get('custom_nodes', []) - py_files = [] - for node in custom_nodes: - if node.get('install_type') == 'copy': - files = node.get('files', []) - if files: - py_files.append((files[0], node.get('title'), node.get('preemptions'), node.get('nodename_pattern'))) - - return py_files - - -def clone_or_pull_git_repository(git_url): - repo_name = git_url.split("/")[-1] - if repo_name.endswith(".git"): - repo_name = repo_name[:-4] - - repo_dir = os.path.join(temp_dir, repo_name) - - if os.path.exists(repo_dir): - try: - repo = Repo(repo_dir) - origin = repo.remote(name="origin") - origin.pull() - repo.git.submodule('update', '--init', '--recursive') - print(f"Pulling {repo_name}...") - except Exception as e: - print(f"Failed to pull '{repo_name}': {e}") - _record_git_error(repo_name, 'pull', e) - else: - try: - Repo.clone_from(git_url, repo_dir, recursive=True) - print(f"Cloning {repo_name}...") - except Exception as e: - print(f"Failed to clone '{repo_name}': {e}") - _record_git_error(repo_name, 'clone', e) - - -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 = {} - - # 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) - if name.endswith(".git"): - name = name[:-4] - - node_info[name] = (url, title, preemptions, node_pattern) - if not skip_update: - clone_or_pull_git_repository(url) - - def process_git_stats(git_url_titles_preemptions): - GITHUB_STATS_CACHE_FILENAME = 'github-stats-cache.json' - GITHUB_STATS_FILENAME = 'github-stats.json' - - github_stats = {} - try: - with open(GITHUB_STATS_CACHE_FILENAME, 'r', encoding='utf-8') as file: - github_stats = json.load(file) - except FileNotFoundError: - pass - - def is_rate_limit_exceeded(): - return g.rate_limiting[0] <= 20 - - if is_rate_limit_exceeded(): - print(f"GitHub API Rate Limit Exceeded: remained - {(g.rate_limiting_resettime - datetime.datetime.now().timestamp())/60:.2f} min") - else: - def renew_stat(url): - if is_rate_limit_exceeded(): - return - - if 'github.com' not in url: - return None - - print('.', end="") - sys.stdout.flush() - try: - # Parsing the URL - parsed_url = urlparse(url) - domain = parsed_url.netloc - path = parsed_url.path - path_parts = path.strip("/").split("/") - if len(path_parts) >= 2 and domain == "github.com": - owner_repo = "/".join(path_parts[-2:]) - repo = g.get_repo(owner_repo) - owner = repo.owner - now = datetime.datetime.now(datetime.timezone.utc) - author_time_diff = now - owner.created_at - - last_update = repo.pushed_at.strftime("%Y-%m-%d %H:%M:%S") if repo.pushed_at else 'N/A' - item = { - "stars": repo.stargazers_count, - "last_update": last_update, - "cached_time": now.timestamp(), - "author_account_age_days": author_time_diff.days, - } - return url, item - else: - print(f"\nInvalid URL format for GitHub repository: {url}\n") - except Exception as e: - print(f"\nERROR on {url}\n{e}") - - return None - - # resolve unresolved urls - with concurrent.futures.ThreadPoolExecutor(11) as executor: - futures = [] - for url, title, preemptions, node_pattern in git_url_titles_preemptions: - if url not in github_stats: - futures.append(executor.submit(renew_stat, url)) - - for future in concurrent.futures.as_completed(futures): - url_item = future.result() - if url_item is not None: - url, item = url_item - github_stats[url] = item - - # renew outdated cache - outdated_urls = [] - for k, v in github_stats.items(): - elapsed = (datetime.datetime.now().timestamp() - v['cached_time']) - if elapsed > 60*60*12: # 12 hours - outdated_urls.append(k) - - with concurrent.futures.ThreadPoolExecutor(11) as executor: - for url in outdated_urls: - futures.append(executor.submit(renew_stat, url)) - - for future in concurrent.futures.as_completed(futures): - url_item = future.result() - if url_item is not None: - url, item = url_item - github_stats[url] = item - - with open('github-stats-cache.json', 'w', encoding='utf-8') as file: - json.dump(github_stats, file, ensure_ascii=False, indent=4) - - with open(GITHUB_STATS_FILENAME, 'w', encoding='utf-8') as file: - for v in github_stats.values(): - if "cached_time" in v: - del v["cached_time"] - - github_stats = dict(sorted(github_stats.items())) - - json.dump(github_stats, file, ensure_ascii=False, indent=4) - - print(f"Successfully written to {GITHUB_STATS_FILENAME}.") - - if not skip_stat_update: - process_git_stats(git_url_titles_preemptions) - - # Reset error collector before this run - with _git_error_lock: - _git_errors.clear() - - # 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) - - # Report any git errors grouped by category (after all workers complete) - _report_git_errors() - - # .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) - - try: - download_url(url, temp_dir) - except: - print(f"[ERROR] Cannot download '{url}'") - - 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, scan_only_mode=False, force_rescan=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 - force_rescan (bool): If True, ignore cache and force rescan all nodes - """ - # scan from .py file - node_files, node_dirs = get_nodes(temp_dir) - - comfyui_path = os.path.abspath(os.path.join(temp_dir, "ComfyUI")) - # 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 = {} - - # Use per-repo cache for node AND metadata extraction - try: - nodes, metadata = extract_nodes_from_repo(Path(dirname), verbose=False, force_rescan=force_rescan) - except: - # Fallback to file-by-file scanning if extract_nodes_from_repo fails - nodes = set() - for py in py_files: - nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI") - nodes.update(nodes_in_file) - metadata.update(metadata_in_file) - - dirname = os.path.basename(dirname) - - if 'Jovimetrix' in dirname: - pass - - if len(nodes) > 0 or (dirname in node_info and node_info[dirname][3] is not None): - nodes = list(nodes) - nodes.sort() - - if dirname in node_info: - git_url, title, preemptions, node_pattern = node_info[dirname] - - # 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 node_pattern is not None: - metadata['nodename_pattern'] = node_pattern - # Scan-only mode: metadata remains empty - - data[git_url] = (nodes, metadata) - else: - # 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) - - if len(nodes) > 0 or (dirname in node_info and node_info[dirname][3] is not None): - nodes = list(nodes) - nodes.sort() - - file = os.path.basename(file) - - if file in node_info: - url, title, preemptions, node_pattern = node_info[file] - - # 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: - print(f"Missing info: {file}") - - # scan from node_list.json file - extensions = [name for name in os.listdir(temp_dir) if os.path.isdir(os.path.join(temp_dir, name))] - - 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: - try: - node_list_json = json.load(f) - except Exception as e: - print(f"\nERROR: Invalid json format '{node_list_json_path}'") - print("------------------------------------------------------") - print(e) - print("------------------------------------------------------") - node_list_json = {} - - metadata_in_url = {} - if git_url not in data: - nodes = set() - else: - nodes_in_url, metadata_in_url = data[git_url] - nodes = set(nodes_in_url) - - try: - for x, desc in node_list_json.items(): - nodes.add(x.strip()) - except Exception as e: - print(f"\nERROR: Invalid json format '{node_list_json_path}'") - print("------------------------------------------------------") - print(e) - print("------------------------------------------------------") - node_list_json = {} - - # Conditionally add metadata based on mode - if not scan_only_mode: - metadata_in_url['title_aux'] = title - - if preemptions is not None: - metadata_in_url['preemptions'] = preemptions - - 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) - - json_path = "extension-node-map.json" - with open(json_path, "w", encoding='utf-8') as file: - json.dump(data, file, indent=4, sort_keys=True) - - -if __name__ == "__main__": - # Parse arguments - args = parse_arguments() - - # Determine mode - scan_only_mode = args.scan_only is not None - url_list_file = args.scan_only if scan_only_mode else None - - # 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") - - 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 - force_rescan = args.force_rescan if hasattr(args, 'force_rescan') else False - if force_rescan: - print("⚠️ Force rescan enabled - ignoring all cached results\n") - gen_json(updated_node_info, scan_only_mode, force_rescan) - - 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)") diff --git a/scripts/colab-dependencies.py b/scripts/colab-dependencies.py deleted file mode 100644 index d5a70ed6..00000000 --- a/scripts/colab-dependencies.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import subprocess - - -def get_enabled_subdirectories_with_files(base_directory): - subdirs_with_files = [] - for subdir in os.listdir(base_directory): - try: - full_path = os.path.join(base_directory, subdir) - if os.path.isdir(full_path) and not subdir.endswith(".disabled") and not subdir.startswith('.') and subdir != '__pycache__': - print(f"## Install dependencies for '{subdir}'") - requirements_file = os.path.join(full_path, "requirements.txt") - install_script = os.path.join(full_path, "install.py") - - if os.path.exists(requirements_file) or os.path.exists(install_script): - subdirs_with_files.append((full_path, requirements_file, install_script)) - except Exception as e: - print(f"EXCEPTION During Dependencies INSTALL on '{subdir}':\n{e}") - - return subdirs_with_files - - -def install_requirements(requirements_file_path): - if os.path.exists(requirements_file_path): - subprocess.run(["pip", "install", "-r", requirements_file_path]) - - -def run_install_script(install_script_path): - if os.path.exists(install_script_path): - subprocess.run(["python", install_script_path]) - - -custom_nodes_directory = "custom_nodes" -subdirs_with_files = get_enabled_subdirectories_with_files(custom_nodes_directory) - - -for subdir, requirements_file, install_script in subdirs_with_files: - install_requirements(requirements_file) - run_install_script(install_script) diff --git a/scripts/install-comfyui-venv-linux.sh b/scripts/install-comfyui-venv-linux.sh deleted file mode 100755 index 6a621329..00000000 --- a/scripts/install-comfyui-venv-linux.sh +++ /dev/null @@ -1,21 +0,0 @@ -git clone https://github.com/comfyanonymous/ComfyUI -cd ComfyUI/custom_nodes -git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager -cd .. -python -m venv venv -source venv/bin/activate -python -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu130 -python -m pip install -r requirements.txt -python -m pip install -r custom_nodes/comfyui-manager/requirements.txt -cd .. -echo "#!/bin/bash" > run_gpu.sh -echo "cd ComfyUI" >> run_gpu.sh -echo "source venv/bin/activate" >> run_gpu.sh -echo "python main.py --preview-method auto" >> run_gpu.sh -chmod +x run_gpu.sh - -echo "#!/bin/bash" > run_cpu.sh -echo "cd ComfyUI" >> run_cpu.sh -echo "source venv/bin/activate" >> run_cpu.sh -echo "python main.py --preview-method auto --cpu" >> run_cpu.sh -chmod +x run_cpu.sh diff --git a/scripts/install-comfyui-venv-win.bat b/scripts/install-comfyui-venv-win.bat deleted file mode 100755 index f1fdb075..00000000 --- a/scripts/install-comfyui-venv-win.bat +++ /dev/null @@ -1,17 +0,0 @@ -git clone https://github.com/comfyanonymous/ComfyUI -cd ComfyUI/custom_nodes -git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager -cd .. -python -m venv venv -call venv/Scripts/activate -python -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu130 -python -m pip install -r requirements.txt -python -m pip install -r custom_nodes/comfyui-manager/requirements.txt -cd .. -echo "cd ComfyUI" >> run_gpu.bat -echo "call venv/Scripts/activate" >> run_gpu.bat -echo "python main.py" >> run_gpu.bat - -echo "cd ComfyUI" >> run_cpu.bat -echo "call venv/Scripts/activate" >> run_cpu.bat -echo "python main.py --cpu" >> run_cpu.bat diff --git a/scripts/install-manager-for-portable-version.bat b/scripts/install-manager-for-portable-version.bat deleted file mode 100644 index 6eb58b74..00000000 --- a/scripts/install-manager-for-portable-version.bat +++ /dev/null @@ -1,3 +0,0 @@ -.\python_embeded\python.exe -s -m pip install gitpython -.\python_embeded\python.exe -c "import git; git.Repo.clone_from('https://github.com/ltdrdata/ComfyUI-Manager', './ComfyUI/custom_nodes/comfyui-manager')" -.\python_embeded\python.exe -m pip install -r ./ComfyUI/custom_nodes/comfyui-manager/requirements.txt diff --git a/snapshots/the_snapshot_files_are_located_here b/snapshots/the_snapshot_files_are_located_here deleted file mode 100644 index e69de29b..00000000