mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-11 23:00:51 +08:00
Merge branch 'master' into add-model-cli
This commit is contained in:
commit
eabd0f7894
@ -18,5 +18,10 @@ You can download the stable diffusion 1.5 one from: https://huggingface.co/runwa
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
To update only the ComfyUI code: update\update_comfyui_only.bat
|
RECOMMENDED WAY TO UPDATE:
|
||||||
To update ComfyUI with the python dependencies: update\update_all.bat
|
To update the ComfyUI code: update\update_comfyui.bat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To update ComfyUI with the python dependencies:
|
||||||
|
update\update_comfyui_and_python_dependencies.bat
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
Invoke-WebRequest -URI https://www.python.org/ftp/python/3.10.9/python-3.10.9-embed-amd64.zip -O python_embeded.zip
|
|
||||||
Expand-Archive python_embeded.zip
|
|
||||||
cd python_embeded
|
|
||||||
Add-Content -Path .\python310._pth -Value 'import site'
|
|
||||||
Invoke-WebRequest -Uri https://bootstrap.pypa.io/get-pip.py -OutFile get-pip.py
|
|
||||||
.\python.exe get-pip.py
|
|
||||||
.\python.exe -s -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117 xformers -r ../ComfyUI/requirements.txt pygit2
|
|
||||||
"../ComfyUI`n" + (Get-Content .\python310._pth -Raw) | Set-Content .\python310._pth
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
|
|
||||||
mkdir ComfyUI_windows_portable
|
|
||||||
mv python_embeded ComfyUI_windows_portable
|
|
||||||
mv ComfyUI_copy ComfyUI_windows_portable/ComfyUI
|
|
||||||
|
|
||||||
cd ComfyUI_windows_portable
|
|
||||||
|
|
||||||
mkdir update
|
|
||||||
cp ComfyUI/.ci/update_windows/* ./update/
|
|
||||||
cp ComfyUI/.ci/windows_base_files/* ./
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
& "C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma -mx=8 -mfb=64 -md=32m -ms=on ComfyUI_windows_portable.7z ComfyUI_windows_portable
|
|
||||||
mv ComfyUI_windows_portable.7z ComfyUI/ComfyUI_windows_portable_nvidia_or_cpu.7z
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
Invoke-WebRequest -URI https://www.python.org/ftp/python/3.10.9/python-3.10.9-embed-amd64.zip -O python_embeded.zip
|
|
||||||
Expand-Archive python_embeded.zip
|
|
||||||
rm python_embeded.zip
|
|
||||||
cd python_embeded
|
|
||||||
Add-Content -Path .\python310._pth -Value 'import site'
|
|
||||||
Invoke-WebRequest -Uri https://bootstrap.pypa.io/get-pip.py -OutFile get-pip.py
|
|
||||||
.\python.exe get-pip.py
|
|
||||||
python -m pip wheel torch torchvision torchaudio --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu118 -r ../ComfyUI/requirements.txt pygit2 -w ../temp_wheel_dir
|
|
||||||
ls ../temp_wheel_dir
|
|
||||||
.\python.exe -s -m pip install --pre (get-item ..\temp_wheel_dir\*)
|
|
||||||
"../ComfyUI`n" + (Get-Content .\python310._pth -Raw) | Set-Content .\python310._pth
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
mkdir ComfyUI_windows_portable_nightly_pytorch
|
|
||||||
mv python_embeded ComfyUI_windows_portable_nightly_pytorch
|
|
||||||
mv ComfyUI_copy ComfyUI_windows_portable_nightly_pytorch/ComfyUI
|
|
||||||
|
|
||||||
cd ComfyUI_windows_portable_nightly_pytorch
|
|
||||||
|
|
||||||
mkdir update
|
|
||||||
cp ComfyUI/.ci/nightly/update_windows/* ./update/
|
|
||||||
cp ComfyUI/.ci/nightly/windows_base_files/* ./
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
& "C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma -mx=8 -mfb=64 -md=32m -ms=on ComfyUI_windows_portable_nightly_pytorch.7z ComfyUI_windows_portable_nightly_pytorch
|
|
||||||
mv ComfyUI_windows_portable_nightly_pytorch.7z ComfyUI/ComfyUI_windows_portable_nvidia_or_cpu_nightly_pytorch.7z
|
|
||||||
4
.ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat
Executable file
4
.ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
..\python_embeded\python.exe .\update.py ..\ComfyUI\
|
||||||
|
..\python_embeded\python.exe -s -m pip install --upgrade torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu118 xformers -r ../ComfyUI/requirements.txt pygit2
|
||||||
|
echo NOTE If you get an error with pip you can ignore it, it's pip being pip as usual, your ComfyUI should have updated anyways.
|
||||||
|
pause
|
||||||
@ -17,6 +17,11 @@ IF YOU GET A RED ERROR IN THE UI MAKE SURE YOU HAVE A MODEL/CHECKPOINT IN: Comfy
|
|||||||
You can download the stable diffusion 1.5 one from: https://huggingface.co/runwayml/stable-diffusion-v1-5/blob/main/v1-5-pruned-emaonly.ckpt
|
You can download the stable diffusion 1.5 one from: https://huggingface.co/runwayml/stable-diffusion-v1-5/blob/main/v1-5-pruned-emaonly.ckpt
|
||||||
|
|
||||||
|
|
||||||
|
RECOMMENDED WAY TO UPDATE:
|
||||||
|
To update the ComfyUI code: update\update_comfyui.bat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To update ComfyUI with the python dependencies, note that you should ONLY run this if you have issues with python dependencies.
|
||||||
|
update\update_comfyui_and_python_dependencies.bat
|
||||||
|
|
||||||
To update only the ComfyUI code: update\update_comfyui_only.bat
|
|
||||||
To update ComfyUI with the python dependencies: update\update_all.bat
|
|
||||||
|
|||||||
35
.github/workflows/windows_release.yml
vendored
35
.github/workflows/windows_release.yml
vendored
@ -18,13 +18,42 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- run: |
|
- shell: bash
|
||||||
|
run: |
|
||||||
cd ..
|
cd ..
|
||||||
cp ComfyUI/.ci/setup_windows_zip.ps1 ./
|
|
||||||
cp -r ComfyUI ComfyUI_copy
|
cp -r ComfyUI ComfyUI_copy
|
||||||
.\setup_windows_zip.ps1
|
curl https://www.python.org/ftp/python/3.10.9/python-3.10.9-embed-amd64.zip -o python_embeded.zip
|
||||||
|
unzip python_embeded.zip -d python_embeded
|
||||||
|
cd python_embeded
|
||||||
|
echo 'import site' >> ./python310._pth
|
||||||
|
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||||
|
./python.exe get-pip.py
|
||||||
|
./python.exe -s -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117 xformers -r ../ComfyUI/requirements.txt pygit2
|
||||||
|
sed -i '1i../ComfyUI' ./python310._pth
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
|
||||||
|
mkdir ComfyUI_windows_portable
|
||||||
|
mv python_embeded ComfyUI_windows_portable
|
||||||
|
mv ComfyUI_copy ComfyUI_windows_portable/ComfyUI
|
||||||
|
|
||||||
|
cd ComfyUI_windows_portable
|
||||||
|
|
||||||
|
mkdir update
|
||||||
|
cp -r ComfyUI/.ci/update_windows/* ./update/
|
||||||
|
cp -r ComfyUI/.ci/windows_base_files/* ./
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma -mx=8 -mfb=64 -md=32m -ms=on ComfyUI_windows_portable.7z ComfyUI_windows_portable
|
||||||
|
mv ComfyUI_windows_portable.7z ComfyUI/ComfyUI_windows_portable_nvidia_or_cpu.7z
|
||||||
|
|
||||||
|
cd ComfyUI_windows_portable
|
||||||
|
python_embeded/python.exe -s ComfyUI/main.py --quick-test-for-ci --cpu
|
||||||
|
|
||||||
ls
|
ls
|
||||||
|
|
||||||
|
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
141
.github/workflows/windows_release_cu118.yml
vendored
Normal file
141
.github/workflows/windows_release_cu118.yml
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
name: "Windows Release cu118"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# push:
|
||||||
|
# branches:
|
||||||
|
# - master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_dependencies:
|
||||||
|
env:
|
||||||
|
# you need at least cuda 5.0 for some of the stuff compiled here.
|
||||||
|
TORCH_CUDA_ARCH_LIST: "5.0+PTX 6.0 6.1 7.0 7.5 8.0 8.6 8.9"
|
||||||
|
FORCE_CUDA: 1
|
||||||
|
MAX_JOBS: 1 # will crash otherwise
|
||||||
|
DISTUTILS_USE_SDK: 1 # otherwise distutils will complain on windows about multiple versions of msvc
|
||||||
|
XFORMERS_BUILD_TYPE: "Release"
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Cache Built Dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: cache-cu118_python_stuff
|
||||||
|
with:
|
||||||
|
path: cu118_python_deps.tar
|
||||||
|
key: ${{ runner.os }}-build-cu118
|
||||||
|
|
||||||
|
- if: ${{ steps.cache-cu118_python_stuff.cache-hit != 'true' }}
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- if: ${{ steps.cache-cu118_python_stuff.cache-hit != 'true' }}
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10.9'
|
||||||
|
|
||||||
|
- if: ${{ steps.cache-cu118_python_stuff.cache-hit != 'true' }}
|
||||||
|
uses: comfyanonymous/cuda-toolkit@test
|
||||||
|
id: cuda-toolkit
|
||||||
|
with:
|
||||||
|
cuda: '11.8.0'
|
||||||
|
# copied from xformers github
|
||||||
|
- name: Setup MSVC
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
- name: Configure Pagefile
|
||||||
|
# windows runners will OOM with many CUDA architectures
|
||||||
|
# we cheat here with a page file
|
||||||
|
uses: al-cheb/configure-pagefile-action@v1.3
|
||||||
|
with:
|
||||||
|
minimum-size: 2GB
|
||||||
|
# really unfortunate: https://github.com/ilammy/msvc-dev-cmd#name-conflicts-with-shell-bash
|
||||||
|
- name: Remove link.exe
|
||||||
|
shell: bash
|
||||||
|
run: rm /usr/bin/link
|
||||||
|
|
||||||
|
- if: ${{ steps.cache-cu118_python_stuff.cache-hit != 'true' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python -m pip wheel --no-cache-dir torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu118 -r requirements.txt pygit2 -w ./temp_wheel_dir
|
||||||
|
python -m pip install --no-cache-dir ./temp_wheel_dir/*
|
||||||
|
echo installed basic
|
||||||
|
git clone --recurse-submodules https://github.com/facebookresearch/xformers.git
|
||||||
|
cd xformers
|
||||||
|
python -m pip install --no-cache-dir wheel setuptools twine
|
||||||
|
echo building xformers
|
||||||
|
python setup.py bdist_wheel -d ../temp_wheel_dir/
|
||||||
|
cd ..
|
||||||
|
rm -rf xformers
|
||||||
|
ls -lah temp_wheel_dir
|
||||||
|
mv temp_wheel_dir cu118_python_deps
|
||||||
|
tar cf cu118_python_deps.tar cu118_python_deps
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: cu118_python_deps
|
||||||
|
path: cu118_python_deps.tar
|
||||||
|
|
||||||
|
|
||||||
|
package_comfyui:
|
||||||
|
needs: build_dependencies
|
||||||
|
permissions:
|
||||||
|
contents: "write"
|
||||||
|
packages: "write"
|
||||||
|
pull-requests: "read"
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: cu118_python_deps
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
mv cu118_python_deps.tar ../
|
||||||
|
cd ..
|
||||||
|
tar xf cu118_python_deps.tar
|
||||||
|
pwd
|
||||||
|
ls
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cp -r ComfyUI ComfyUI_copy
|
||||||
|
curl https://www.python.org/ftp/python/3.10.9/python-3.10.9-embed-amd64.zip -o python_embeded.zip
|
||||||
|
unzip python_embeded.zip -d python_embeded
|
||||||
|
cd python_embeded
|
||||||
|
echo 'import site' >> ./python310._pth
|
||||||
|
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||||
|
./python.exe get-pip.py
|
||||||
|
./python.exe -s -m pip install ../cu118_python_deps/*
|
||||||
|
sed -i '1i../ComfyUI' ./python310._pth
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
|
||||||
|
mkdir ComfyUI_windows_portable
|
||||||
|
mv python_embeded ComfyUI_windows_portable
|
||||||
|
mv ComfyUI_copy ComfyUI_windows_portable/ComfyUI
|
||||||
|
|
||||||
|
cd ComfyUI_windows_portable
|
||||||
|
|
||||||
|
mkdir update
|
||||||
|
cp -r ComfyUI/.ci/update_windows/* ./update/
|
||||||
|
cp -r ComfyUI/.ci/update_windows_cu118/* ./update/
|
||||||
|
cp -r ComfyUI/.ci/windows_base_files/* ./
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma -mx=8 -mfb=64 -md=32m -ms=on ComfyUI_windows_portable.7z ComfyUI_windows_portable
|
||||||
|
mv ComfyUI_windows_portable.7z ComfyUI/ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z
|
||||||
|
|
||||||
|
cd ComfyUI_windows_portable
|
||||||
|
python_embeded/python.exe -s ComfyUI/main.py --quick-test-for-ci --cpu
|
||||||
|
|
||||||
|
ls
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z
|
||||||
|
tag: "latest"
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
@ -20,11 +20,41 @@ jobs:
|
|||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.10.9'
|
python-version: '3.10.9'
|
||||||
- run: |
|
- shell: bash
|
||||||
|
run: |
|
||||||
cd ..
|
cd ..
|
||||||
cp ComfyUI/.ci/setup_windows_zip_nightly_pytorch.ps1 ./
|
|
||||||
cp -r ComfyUI ComfyUI_copy
|
cp -r ComfyUI ComfyUI_copy
|
||||||
.\setup_windows_zip_nightly_pytorch.ps1
|
curl https://www.python.org/ftp/python/3.10.9/python-3.10.9-embed-amd64.zip -o python_embeded.zip
|
||||||
|
unzip python_embeded.zip -d python_embeded
|
||||||
|
cd python_embeded
|
||||||
|
echo 'import site' >> ./python310._pth
|
||||||
|
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||||
|
./python.exe get-pip.py
|
||||||
|
python -m pip wheel torch torchvision torchaudio --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu118 -r ../ComfyUI/requirements.txt pygit2 -w ../temp_wheel_dir
|
||||||
|
ls ../temp_wheel_dir
|
||||||
|
./python.exe -s -m pip install --pre ../temp_wheel_dir/*
|
||||||
|
sed -i '1i../ComfyUI' ./python310._pth
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
|
||||||
|
mkdir ComfyUI_windows_portable_nightly_pytorch
|
||||||
|
mv python_embeded ComfyUI_windows_portable_nightly_pytorch
|
||||||
|
mv ComfyUI_copy ComfyUI_windows_portable_nightly_pytorch/ComfyUI
|
||||||
|
|
||||||
|
cd ComfyUI_windows_portable_nightly_pytorch
|
||||||
|
|
||||||
|
mkdir update
|
||||||
|
cp -r ComfyUI/.ci/update_windows/* ./update/
|
||||||
|
cp -r ComfyUI/.ci/windows_base_files/* ./
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma -mx=8 -mfb=64 -md=32m -ms=on ComfyUI_windows_portable_nightly_pytorch.7z ComfyUI_windows_portable_nightly_pytorch
|
||||||
|
mv ComfyUI_windows_portable_nightly_pytorch.7z ComfyUI/ComfyUI_windows_portable_nvidia_or_cpu_nightly_pytorch.7z
|
||||||
|
|
||||||
|
cd ComfyUI_windows_portable_nightly_pytorch
|
||||||
|
python_embeded/python.exe -s ComfyUI/main.py --quick-test-for-ci --cpu
|
||||||
|
|
||||||
ls
|
ls
|
||||||
|
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
|
|||||||
15
README.md
15
README.md
@ -56,23 +56,14 @@ At the time of writing this pytorch has issues with python versions higher than
|
|||||||
### AMD (Linux only)
|
### AMD (Linux only)
|
||||||
AMD users can install rocm and pytorch with pip if you don't have it already installed, this is the command to install the stable version:
|
AMD users can install rocm and pytorch with pip if you don't have it already installed, this is the command to install the stable version:
|
||||||
|
|
||||||
```pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/rocm5.2```
|
```pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/rocm5.4.2```
|
||||||
|
|
||||||
|
|
||||||
I highly recommend you use the nightly/unstable pytorch builds though because they work a lot better for me (run this in the ComfyUI folder so it picks up the requirements.txt):
|
|
||||||
|
|
||||||
```pip install --upgrade --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/nightly/rocm5.4.2 -r requirements.txt```
|
|
||||||
|
|
||||||
|
|
||||||
### NVIDIA
|
### NVIDIA
|
||||||
|
|
||||||
Nvidia users should install torch using this command:
|
Nvidia users should install torch and xformers using this command:
|
||||||
|
|
||||||
```pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117```
|
```pip install torch==1.13.1 torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117 xformers```
|
||||||
|
|
||||||
Nvidia users should also install Xformers for a speed boost but can still run the software without it.
|
|
||||||
|
|
||||||
```pip install xformers```
|
|
||||||
|
|
||||||
#### Troubleshooting
|
#### Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@ -50,8 +50,6 @@ if "--use-pytorch-cross-attention" in sys.argv:
|
|||||||
XFORMERS_IS_AVAILBLE = False
|
XFORMERS_IS_AVAILBLE = False
|
||||||
|
|
||||||
|
|
||||||
if "--cpu" in sys.argv:
|
|
||||||
vram_state = CPU
|
|
||||||
if "--lowvram" in sys.argv:
|
if "--lowvram" in sys.argv:
|
||||||
set_vram_to = LOW_VRAM
|
set_vram_to = LOW_VRAM
|
||||||
if "--novram" in sys.argv:
|
if "--novram" in sys.argv:
|
||||||
@ -73,6 +71,8 @@ if set_vram_to == LOW_VRAM or set_vram_to == NO_VRAM:
|
|||||||
total_vram_available_mb = (total_vram - 1024) // 2
|
total_vram_available_mb = (total_vram - 1024) // 2
|
||||||
total_vram_available_mb = int(max(256, total_vram_available_mb))
|
total_vram_available_mb = int(max(256, total_vram_available_mb))
|
||||||
|
|
||||||
|
if "--cpu" in sys.argv:
|
||||||
|
vram_state = CPU
|
||||||
|
|
||||||
print("Set vram state to:", ["CPU", "NO VRAM", "LOW VRAM", "NORMAL VRAM", "HIGH VRAM"][vram_state])
|
print("Set vram state to:", ["CPU", "NO VRAM", "LOW VRAM", "NORMAL VRAM", "HIGH VRAM"][vram_state])
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
from comfy_extras.chainner_models import model_loading
|
from comfy_extras.chainner_models import model_loading
|
||||||
from comfy.sd import load_torch_file
|
from comfy.sd import load_torch_file
|
||||||
import comfy.model_management
|
import model_management
|
||||||
from nodes import filter_files_extensions, recursive_search, supported_ckpt_extensions, extract_arg_values
|
from nodes import filter_files_extensions, recursive_search, supported_ckpt_extensions, extract_arg_values
|
||||||
import torch
|
import torch
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
@ -38,7 +38,7 @@ class ImageUpscaleWithModel:
|
|||||||
CATEGORY = "image/upscaling"
|
CATEGORY = "image/upscaling"
|
||||||
|
|
||||||
def upscale(self, upscale_model, image):
|
def upscale(self, upscale_model, image):
|
||||||
device = comfy.model_management.get_torch_device()
|
device = model_management.get_torch_device()
|
||||||
upscale_model.to(device)
|
upscale_model.to(device)
|
||||||
in_img = image.movedim(-1,-3).to(device)
|
in_img = image.movedim(-1,-3).to(device)
|
||||||
s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=128 + 64, tile_y=128 + 64, overlap = 8, upscale_amount=upscale_model.scale)
|
s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=128 + 64, tile_y=128 + 64, overlap = 8, upscale_amount=upscale_model.scale)
|
||||||
|
|||||||
12
main.py
12
main.py
@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -66,7 +67,14 @@ def hijack_progress(server):
|
|||||||
return v
|
return v
|
||||||
setattr(tqdm, "update", wrapped_func)
|
setattr(tqdm, "update", wrapped_func)
|
||||||
|
|
||||||
|
def cleanup_temp():
|
||||||
|
temp_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "temp")
|
||||||
|
if os.path.exists(temp_dir):
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
cleanup_temp()
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
server = server.PromptServer(loop)
|
server = server.PromptServer(loop)
|
||||||
@ -91,6 +99,9 @@ if __name__ == "__main__":
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if '--quick-test-for-ci' in sys.argv:
|
||||||
|
exit(0)
|
||||||
|
|
||||||
call_on_start = None
|
call_on_start = None
|
||||||
if "--windows-standalone-build" in sys.argv:
|
if "--windows-standalone-build" in sys.argv:
|
||||||
def startup_server(address, port):
|
def startup_server(address, port):
|
||||||
@ -106,3 +117,4 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
loop.run_until_complete(run(server, address=address, port=port, verbose=not dont_print, call_on_start=call_on_start))
|
loop.run_until_complete(run(server, address=address, port=port, verbose=not dont_print, call_on_start=call_on_start))
|
||||||
|
|
||||||
|
cleanup_temp()
|
||||||
|
|||||||
23
nodes.py
23
nodes.py
@ -700,8 +700,8 @@ def common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
|||||||
if t.shape[0] < noise.shape[0]:
|
if t.shape[0] < noise.shape[0]:
|
||||||
t = torch.cat([t] * noise.shape[0])
|
t = torch.cat([t] * noise.shape[0])
|
||||||
t = t.to(device)
|
t = t.to(device)
|
||||||
if 'control' in p[1]:
|
if 'control' in n[1]:
|
||||||
control_nets += [p[1]['control']]
|
control_nets += [n[1]['control']]
|
||||||
negative_copy += [[t] + n[1:]]
|
negative_copy += [[t] + n[1:]]
|
||||||
|
|
||||||
control_net_models = []
|
control_net_models = []
|
||||||
@ -784,6 +784,7 @@ class KSamplerAdvanced:
|
|||||||
class SaveImage:
|
class SaveImage:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output")
|
self.output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output")
|
||||||
|
self.url_suffix = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@ -817,6 +818,9 @@ class SaveImage:
|
|||||||
os.mkdir(self.output_dir)
|
os.mkdir(self.output_dir)
|
||||||
counter = 1
|
counter = 1
|
||||||
|
|
||||||
|
if not os.path.exists(self.output_dir):
|
||||||
|
os.makedirs(self.output_dir)
|
||||||
|
|
||||||
paths = list()
|
paths = list()
|
||||||
for image in images:
|
for image in images:
|
||||||
i = 255. * image.cpu().numpy()
|
i = 255. * image.cpu().numpy()
|
||||||
@ -829,10 +833,22 @@ class SaveImage:
|
|||||||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||||||
file = f"{filename_prefix}_{counter:05}_.png"
|
file = f"{filename_prefix}_{counter:05}_.png"
|
||||||
img.save(os.path.join(self.output_dir, file), pnginfo=metadata, optimize=True)
|
img.save(os.path.join(self.output_dir, file), pnginfo=metadata, optimize=True)
|
||||||
paths.append(file)
|
paths.append(file + self.url_suffix)
|
||||||
counter += 1
|
counter += 1
|
||||||
return { "ui": { "images": paths } }
|
return { "ui": { "images": paths } }
|
||||||
|
|
||||||
|
class PreviewImage(SaveImage):
|
||||||
|
def __init__(self):
|
||||||
|
self.output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "temp")
|
||||||
|
self.url_suffix = "?type=temp"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"images": ("IMAGE", ), },
|
||||||
|
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||||
|
}
|
||||||
|
|
||||||
class LoadImage:
|
class LoadImage:
|
||||||
input_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "input")
|
input_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "input")
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -953,6 +969,7 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"EmptyLatentImage": EmptyLatentImage,
|
"EmptyLatentImage": EmptyLatentImage,
|
||||||
"LatentUpscale": LatentUpscale,
|
"LatentUpscale": LatentUpscale,
|
||||||
"SaveImage": SaveImage,
|
"SaveImage": SaveImage,
|
||||||
|
"PreviewImage": PreviewImage,
|
||||||
"LoadImage": LoadImage,
|
"LoadImage": LoadImage,
|
||||||
"LoadImageMask": LoadImageMask,
|
"LoadImageMask": LoadImageMask,
|
||||||
"ImageScale": ImageScale,
|
"ImageScale": ImageScale,
|
||||||
|
|||||||
@ -113,7 +113,7 @@ class PromptServer():
|
|||||||
async def view_image(request):
|
async def view_image(request):
|
||||||
if "file" in request.match_info:
|
if "file" in request.match_info:
|
||||||
type = request.rel_url.query.get("type", "output")
|
type = request.rel_url.query.get("type", "output")
|
||||||
if type != "output" and type != "input":
|
if type not in ["output", "input", "temp"]:
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), type)
|
output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), type)
|
||||||
|
|||||||
@ -142,7 +142,14 @@ class ComfyApp {
|
|||||||
if (numImages === 1 && !imageIndex) {
|
if (numImages === 1 && !imageIndex) {
|
||||||
this.imageIndex = imageIndex = 0;
|
this.imageIndex = imageIndex = 0;
|
||||||
}
|
}
|
||||||
let shiftY = this.type === "SaveImage" ? 55 : this.imageOffset || 0;
|
|
||||||
|
let shiftY;
|
||||||
|
if (this.imageOffset != null) {
|
||||||
|
shiftY = this.imageOffset;
|
||||||
|
} else {
|
||||||
|
shiftY = this.computeSize()[1];
|
||||||
|
}
|
||||||
|
|
||||||
let dw = this.size[0];
|
let dw = this.size[0];
|
||||||
let dh = this.size[1];
|
let dh = this.size[1];
|
||||||
dh -= shiftY;
|
dh -= shiftY;
|
||||||
@ -284,9 +291,47 @@ class ComfyApp {
|
|||||||
document.addEventListener("drop", async (event) => {
|
document.addEventListener("drop", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
await this.handleFile(file);
|
const n = this.dragOverNode;
|
||||||
|
this.dragOverNode = null;
|
||||||
|
// Node handles file drop, we dont use the built in onDropFile handler as its buggy
|
||||||
|
// If you drag multiple files it will call it multiple times with the same file
|
||||||
|
if (n && n.onDragDrop && (await n.onDragDrop(event))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.handleFile(event.dataTransfer.files[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Always clear over node on drag leave
|
||||||
|
this.canvasEl.addEventListener("dragleave", async () => {
|
||||||
|
if (this.dragOverNode) {
|
||||||
|
this.dragOverNode = null;
|
||||||
|
this.graph.setDirtyCanvas(false, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add handler for dropping onto a specific node
|
||||||
|
this.canvasEl.addEventListener(
|
||||||
|
"dragover",
|
||||||
|
(e) => {
|
||||||
|
this.canvas.adjustMouseEvent(e);
|
||||||
|
const node = this.graph.getNodeOnPos(e.canvasX, e.canvasY);
|
||||||
|
if (node) {
|
||||||
|
if (node.onDragOver && node.onDragOver(e)) {
|
||||||
|
this.dragOverNode = node;
|
||||||
|
|
||||||
|
// dragover event is fired very frequently, run this on an animation frame
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.graph.setDirtyCanvas(false, true);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.dragOverNode = null;
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -314,15 +359,22 @@ class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws currently executing node highlight and progress bar
|
* Draws node highlights (executing, drag drop) and progress bar
|
||||||
*/
|
*/
|
||||||
#addDrawNodeProgressHandler() {
|
#addDrawNodeHandler() {
|
||||||
const orig = LGraphCanvas.prototype.drawNodeShape;
|
const orig = LGraphCanvas.prototype.drawNodeShape;
|
||||||
const self = this;
|
const self = this;
|
||||||
LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
|
LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
|
||||||
const res = orig.apply(this, arguments);
|
const res = orig.apply(this, arguments);
|
||||||
|
|
||||||
if (node.id + "" === self.runningNodeId) {
|
let color = null;
|
||||||
|
if (node.id === +self.runningNodeId) {
|
||||||
|
color = "#0f0";
|
||||||
|
} else if (self.dragOverNode && node.id === self.dragOverNode.id) {
|
||||||
|
color = "dodgerblue";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color) {
|
||||||
const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
|
const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.globalAlpha = 0.8;
|
ctx.globalAlpha = 0.8;
|
||||||
@ -348,7 +400,7 @@ class ComfyApp {
|
|||||||
);
|
);
|
||||||
else if (shape == LiteGraph.CIRCLE_SHAPE)
|
else if (shape == LiteGraph.CIRCLE_SHAPE)
|
||||||
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
|
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
|
||||||
ctx.strokeStyle = "#0f0";
|
ctx.strokeStyle = color;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.strokeStyle = fgcolor;
|
ctx.strokeStyle = fgcolor;
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
@ -398,6 +450,15 @@ class ComfyApp {
|
|||||||
api.init();
|
api.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#addKeyboardHandler() {
|
||||||
|
window.addEventListener("keydown", (e) => {
|
||||||
|
// Queue prompt using ctrl or command + enter
|
||||||
|
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) {
|
||||||
|
this.queuePrompt(e.shiftKey ? -1 : 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all extensions from the API into the window
|
* Loads all extensions from the API into the window
|
||||||
*/
|
*/
|
||||||
@ -419,7 +480,7 @@ class ComfyApp {
|
|||||||
await this.#loadExtensions();
|
await this.#loadExtensions();
|
||||||
|
|
||||||
// Create and mount the LiteGraph in the DOM
|
// Create and mount the LiteGraph in the DOM
|
||||||
const canvasEl = Object.assign(document.createElement("canvas"), { id: "graph-canvas" });
|
const canvasEl = (this.canvasEl = Object.assign(document.createElement("canvas"), { id: "graph-canvas" }));
|
||||||
document.body.prepend(canvasEl);
|
document.body.prepend(canvasEl);
|
||||||
|
|
||||||
this.graph = new LGraph();
|
this.graph = new LGraph();
|
||||||
@ -460,10 +521,11 @@ class ComfyApp {
|
|||||||
// Save current workflow automatically
|
// Save current workflow automatically
|
||||||
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000);
|
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000);
|
||||||
|
|
||||||
this.#addDrawNodeProgressHandler();
|
this.#addDrawNodeHandler();
|
||||||
this.#addApiUpdateHandlers();
|
this.#addApiUpdateHandlers();
|
||||||
this.#addDropHandler();
|
this.#addDropHandler();
|
||||||
this.#addPasteHandler();
|
this.#addPasteHandler();
|
||||||
|
this.#addKeyboardHandler();
|
||||||
|
|
||||||
await this.#invokeExtensionsAsync("setup");
|
await this.#invokeExtensionsAsync("setup");
|
||||||
}
|
}
|
||||||
@ -497,7 +559,11 @@ class ComfyApp {
|
|||||||
|
|
||||||
if (Array.isArray(type)) {
|
if (Array.isArray(type)) {
|
||||||
// Enums e.g. latent rotation
|
// Enums e.g. latent rotation
|
||||||
this.addWidget("combo", inputName, type[0], () => {}, { values: type });
|
let defaultValue = type[0];
|
||||||
|
if (inputData[1] && inputData[1].default) {
|
||||||
|
defaultValue = inputData[1].default;
|
||||||
|
}
|
||||||
|
this.addWidget("combo", inputName, defaultValue, () => {}, { values: type });
|
||||||
} else if (`${type}:${inputName}` in widgets) {
|
} else if (`${type}:${inputName}` in widgets) {
|
||||||
// Support custom widgets by Type:Name
|
// Support custom widgets by Type:Name
|
||||||
Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {});
|
Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {});
|
||||||
@ -641,31 +707,33 @@ class ComfyApp {
|
|||||||
return { workflow, output };
|
return { workflow, output };
|
||||||
}
|
}
|
||||||
|
|
||||||
async queuePrompt(number) {
|
async queuePrompt(number, batchCount = 1) {
|
||||||
const p = await this.graphToPrompt();
|
for (let i = 0; i < batchCount; i++) {
|
||||||
|
const p = await this.graphToPrompt();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.queuePrompt(number, p);
|
await api.queuePrompt(number, p);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.ui.dialog.show(error.response || error.toString());
|
this.ui.dialog.show(error.response || error.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const n of p.workflow.nodes) {
|
for (const n of p.workflow.nodes) {
|
||||||
const node = graph.getNodeById(n.id);
|
const node = graph.getNodeById(n.id);
|
||||||
if (node.widgets) {
|
if (node.widgets) {
|
||||||
for (const widget of node.widgets) {
|
for (const widget of node.widgets) {
|
||||||
// Allow widgets to run callbacks after a prompt has been queued
|
// Allow widgets to run callbacks after a prompt has been queued
|
||||||
// e.g. random seed after every gen
|
// e.g. random seed after every gen
|
||||||
if (widget.afterQueued) {
|
if (widget.afterQueued) {
|
||||||
widget.afterQueued();
|
widget.afterQueued();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.canvas.draw(true, true);
|
this.canvas.draw(true, true);
|
||||||
await this.ui.queue.update();
|
await this.ui.queue.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -231,6 +231,7 @@ export class ComfyUI {
|
|||||||
this.dialog = new ComfyDialog();
|
this.dialog = new ComfyDialog();
|
||||||
this.settings = new ComfySettingsDialog();
|
this.settings = new ComfySettingsDialog();
|
||||||
|
|
||||||
|
this.batchCount = 1;
|
||||||
this.queue = new ComfyList("Queue");
|
this.queue = new ComfyList("Queue");
|
||||||
this.history = new ComfyList("History");
|
this.history = new ComfyList("History");
|
||||||
|
|
||||||
@ -254,9 +255,35 @@ export class ComfyUI {
|
|||||||
$el("span", { $: (q) => (this.queueSize = q) }),
|
$el("span", { $: (q) => (this.queueSize = q) }),
|
||||||
$el("button.comfy-settings-btn", { textContent: "⚙️", onclick: () => this.settings.show() }),
|
$el("button.comfy-settings-btn", { textContent: "⚙️", onclick: () => this.settings.show() }),
|
||||||
]),
|
]),
|
||||||
$el("button.comfy-queue-btn", { textContent: "Queue Prompt", onclick: () => app.queuePrompt(0) }),
|
$el("button.comfy-queue-btn", { textContent: "Queue Prompt", onclick: () => app.queuePrompt(0, this.batchCount) }),
|
||||||
|
$el("div", {}, [
|
||||||
|
$el("label", { innerHTML: "Extra options"}, [
|
||||||
|
$el("input", { type: "checkbox",
|
||||||
|
onchange: (i) => {
|
||||||
|
document.getElementById('extraOptions').style.display = i.srcElement.checked ? "block" : "none";
|
||||||
|
this.batchCount = i.srcElement.checked ? document.getElementById('batchCountInputRange').value : 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
$el("div", { id: "extraOptions", style: { width: "100%", display: "none" }}, [
|
||||||
|
$el("label", { innerHTML: "Batch count" }, [
|
||||||
|
$el("input", { id: "batchCountInputNumber", type: "number", value: this.batchCount, min: "1", style: { width: "35%", "margin-left": "0.4em" },
|
||||||
|
oninput: (i) => {
|
||||||
|
this.batchCount = i.target.value;
|
||||||
|
document.getElementById('batchCountInputRange').value = this.batchCount;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
$el("input", { id: "batchCountInputRange", type: "range", min: "1", max: "100", value: this.batchCount,
|
||||||
|
oninput: (i) => {
|
||||||
|
this.batchCount = i.srcElement.value;
|
||||||
|
document.getElementById('batchCountInputNumber').value = i.srcElement.value;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
$el("div.comfy-menu-btns", [
|
$el("div.comfy-menu-btns", [
|
||||||
$el("button", { textContent: "Queue Front", onclick: () => app.queuePrompt(-1) }),
|
$el("button", { textContent: "Queue Front", onclick: () => app.queuePrompt(-1, this.batchCount) }),
|
||||||
$el("button", {
|
$el("button", {
|
||||||
$: (b) => (this.queue.button = b),
|
$: (b) => (this.queue.button = b),
|
||||||
textContent: "View Queue",
|
textContent: "View Queue",
|
||||||
|
|||||||
@ -132,7 +132,7 @@ export const ComfyWidgets = {
|
|||||||
|
|
||||||
function showImage(name) {
|
function showImage(name) {
|
||||||
// Position the image somewhere sensible
|
// Position the image somewhere sensible
|
||||||
if(!node.imageOffset) {
|
if (!node.imageOffset) {
|
||||||
node.imageOffset = uploadWidget.last_y ? uploadWidget.last_y + 25 : 75;
|
node.imageOffset = uploadWidget.last_y ? uploadWidget.last_y + 25 : 75;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +162,36 @@ export const ComfyWidgets = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function uploadFile(file, updateNode) {
|
||||||
|
try {
|
||||||
|
// Wrap file in formdata so it includes filename
|
||||||
|
const body = new FormData();
|
||||||
|
body.append("image", file);
|
||||||
|
const resp = await fetch("/upload/image", {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const data = await resp.json();
|
||||||
|
// Add the file as an option and update the widget value
|
||||||
|
if (!imageWidget.options.values.includes(data.name)) {
|
||||||
|
imageWidget.options.values.push(data.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateNode) {
|
||||||
|
showImage(data.name);
|
||||||
|
|
||||||
|
imageWidget.value = data.name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(resp.status + " - " + resp.statusText);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fileInput = document.createElement("input");
|
const fileInput = document.createElement("input");
|
||||||
Object.assign(fileInput, {
|
Object.assign(fileInput, {
|
||||||
type: "file",
|
type: "file",
|
||||||
@ -169,30 +199,7 @@ export const ComfyWidgets = {
|
|||||||
style: "display: none",
|
style: "display: none",
|
||||||
onchange: async () => {
|
onchange: async () => {
|
||||||
if (fileInput.files.length) {
|
if (fileInput.files.length) {
|
||||||
try {
|
await uploadFile(fileInput.files[0], true);
|
||||||
// Wrap file in formdata so it includes filename
|
|
||||||
const body = new FormData();
|
|
||||||
body.append("image", fileInput.files[0]);
|
|
||||||
const resp = await fetch("/upload/image", {
|
|
||||||
method: "POST",
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.status === 200) {
|
|
||||||
const data = await resp.json();
|
|
||||||
showImage(data.name);
|
|
||||||
|
|
||||||
// Add the file as an option and update the widget value
|
|
||||||
if (!imageWidget.options.values.includes(data.name)) {
|
|
||||||
imageWidget.options.values.push(data.name);
|
|
||||||
}
|
|
||||||
imageWidget.value = data.name;
|
|
||||||
} else {
|
|
||||||
alert(resp.status + " - " + resp.statusText);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
alert(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -204,6 +211,30 @@ export const ComfyWidgets = {
|
|||||||
});
|
});
|
||||||
uploadWidget.serialize = false;
|
uploadWidget.serialize = false;
|
||||||
|
|
||||||
|
// Add handler to check if an image is being dragged over our node
|
||||||
|
node.onDragOver = function (e) {
|
||||||
|
if (e.dataTransfer && e.dataTransfer.items) {
|
||||||
|
const image = [...e.dataTransfer.items].find((f) => f.kind === "file" && f.type.startsWith("image/"));
|
||||||
|
return !!image;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// On drop upload files
|
||||||
|
node.onDragDrop = function (e) {
|
||||||
|
console.log("onDragDrop called");
|
||||||
|
let handled = false;
|
||||||
|
for (const file of e.dataTransfer.files) {
|
||||||
|
if (file.type.startsWith("image/")) {
|
||||||
|
uploadFile(file, !handled); // Dont await these, any order is fine, only update on first one
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handled;
|
||||||
|
};
|
||||||
|
|
||||||
return { widget: uploadWidget };
|
return { widget: uploadWidget };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user