diff --git a/.github/workflows/test-invoke-pip-skip.yml b/.github/workflows/test-invoke-pip-skip.yml index d4c9d9fc00..004b46d5a8 100644 --- a/.github/workflows/test-invoke-pip-skip.yml +++ b/.github/workflows/test-invoke-pip-skip.yml @@ -1,10 +1,16 @@ name: Test invoke.py pip + +# This is a dummy stand-in for the actual tests +# we don't need to run python tests on non-Python changes +# But PRs require passing tests to be mergeable + on: pull_request: paths: - '**' - '!pyproject.toml' - '!invokeai/**' + - '!tests/**' - 'invokeai/frontend/web/**' merge_group: workflow_dispatch: @@ -19,48 +25,26 @@ jobs: strategy: matrix: python-version: - # - '3.9' - '3.10' pytorch: - # - linux-cuda-11_6 - linux-cuda-11_7 - linux-rocm-5_2 - linux-cpu - macos-default - windows-cpu - # - windows-cuda-11_6 - # - windows-cuda-11_7 include: - # - pytorch: linux-cuda-11_6 - # os: ubuntu-22.04 - # extra-index-url: 'https://download.pytorch.org/whl/cu116' - # github-env: $GITHUB_ENV - pytorch: linux-cuda-11_7 os: ubuntu-22.04 - github-env: $GITHUB_ENV - pytorch: linux-rocm-5_2 os: ubuntu-22.04 - extra-index-url: 'https://download.pytorch.org/whl/rocm5.2' - github-env: $GITHUB_ENV - pytorch: linux-cpu os: ubuntu-22.04 - extra-index-url: 'https://download.pytorch.org/whl/cpu' - github-env: $GITHUB_ENV - pytorch: macos-default os: macOS-12 - github-env: $GITHUB_ENV - pytorch: windows-cpu os: windows-2022 - github-env: $env:GITHUB_ENV - # - pytorch: windows-cuda-11_6 - # os: windows-2022 - # extra-index-url: 'https://download.pytorch.org/whl/cu116' - # github-env: $env:GITHUB_ENV - # - pytorch: windows-cuda-11_7 - # os: windows-2022 - # extra-index-url: 'https://download.pytorch.org/whl/cu117' - # github-env: $env:GITHUB_ENV name: ${{ matrix.pytorch }} on ${{ matrix.python-version }} runs-on: ${{ matrix.os }} steps: - - run: 'echo "No build required"' + - name: skip + run: echo "no build required" diff --git a/.github/workflows/test-invoke-pip.yml b/.github/workflows/test-invoke-pip.yml index 071232e06e..40be0a529e 100644 --- a/.github/workflows/test-invoke-pip.yml +++ b/.github/workflows/test-invoke-pip.yml @@ -11,6 +11,7 @@ on: paths: - 'pyproject.toml' - 'invokeai/**' + - 'tests/**' - '!invokeai/frontend/web/**' types: - 'ready_for_review' @@ -32,19 +33,12 @@ jobs: # - '3.9' - '3.10' pytorch: - # - linux-cuda-11_6 - linux-cuda-11_7 - linux-rocm-5_2 - linux-cpu - macos-default - windows-cpu - # - windows-cuda-11_6 - # - windows-cuda-11_7 include: - # - pytorch: linux-cuda-11_6 - # os: ubuntu-22.04 - # extra-index-url: 'https://download.pytorch.org/whl/cu116' - # github-env: $GITHUB_ENV - pytorch: linux-cuda-11_7 os: ubuntu-22.04 github-env: $GITHUB_ENV @@ -62,14 +56,6 @@ jobs: - pytorch: windows-cpu os: windows-2022 github-env: $env:GITHUB_ENV - # - pytorch: windows-cuda-11_6 - # os: windows-2022 - # extra-index-url: 'https://download.pytorch.org/whl/cu116' - # github-env: $env:GITHUB_ENV - # - pytorch: windows-cuda-11_7 - # os: windows-2022 - # extra-index-url: 'https://download.pytorch.org/whl/cu117' - # github-env: $env:GITHUB_ENV name: ${{ matrix.pytorch }} on ${{ matrix.python-version }} runs-on: ${{ matrix.os }} env: @@ -100,40 +86,38 @@ jobs: id: run-pytest run: pytest - - name: run invokeai-configure - id: run-preload-models - env: - HUGGING_FACE_HUB_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }} - run: > - invokeai-configure - --yes - --default_only - --full-precision - # can't use fp16 weights without a GPU + # - name: run invokeai-configure + # env: + # HUGGING_FACE_HUB_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }} + # run: > + # invokeai-configure + # --yes + # --default_only + # --full-precision + # # can't use fp16 weights without a GPU - - name: run invokeai - id: run-invokeai - env: - # Set offline mode to make sure configure preloaded successfully. - HF_HUB_OFFLINE: 1 - HF_DATASETS_OFFLINE: 1 - TRANSFORMERS_OFFLINE: 1 - INVOKEAI_OUTDIR: ${{ github.workspace }}/results - run: > - invokeai - --no-patchmatch - --no-nsfw_checker - --precision=float32 - --always_use_cpu - --use_memory_db - --outdir ${{ env.INVOKEAI_OUTDIR }}/${{ matrix.python-version }}/${{ matrix.pytorch }} - --from_file ${{ env.TEST_PROMPTS }} + # - name: run invokeai + # id: run-invokeai + # env: + # # Set offline mode to make sure configure preloaded successfully. + # HF_HUB_OFFLINE: 1 + # HF_DATASETS_OFFLINE: 1 + # TRANSFORMERS_OFFLINE: 1 + # INVOKEAI_OUTDIR: ${{ github.workspace }}/results + # run: > + # invokeai + # --no-patchmatch + # --no-nsfw_checker + # --precision=float32 + # --always_use_cpu + # --use_memory_db + # --outdir ${{ env.INVOKEAI_OUTDIR }}/${{ matrix.python-version }}/${{ matrix.pytorch }} + # --from_file ${{ env.TEST_PROMPTS }} - - name: Archive results - id: archive-results - env: - INVOKEAI_OUTDIR: ${{ github.workspace }}/results - uses: actions/upload-artifact@v3 - with: - name: results - path: ${{ env.INVOKEAI_OUTDIR }} + # - name: Archive results + # env: + # INVOKEAI_OUTDIR: ${{ github.workspace }}/results + # uses: actions/upload-artifact@v3 + # with: + # name: results + # path: ${{ env.INVOKEAI_OUTDIR }} diff --git a/README.md b/README.md index 745baf12be..4fd2ca9616 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,23 @@ _Note: InvokeAI is rapidly evolving. Please use the [Issues](https://github.com/invoke-ai/InvokeAI/issues) tab to report bugs and make feature requests. Be sure to use the provided templates. They will help us diagnose issues faster._ +## FOR DEVELOPERS - MIGRATING TO THE 3.0.0 MODELS FORMAT + +The models directory and models.yaml have changed. To migrate to the +new layout, please follow this recipe: + +1. Run `python scripts/migrate_models_to_3.0.py + +2. This will create a new models directory named `models-3.0` and a + new config directory named `models.yaml-3.0`, both in the current + working directory. If you prefer to name them something else, pass + the `--dest-directory` and/or `--dest-yaml` arguments. + +3. Check that the new models directory and yaml file look ok. + +4. Replace the existing directory and file, keeping backup copies just in +case. +
![canvas preview](https://github.com/invoke-ai/InvokeAI/raw/main/docs/assets/canvas_preview.png) diff --git a/binary_installer/WinLongPathsEnabled.reg b/binary_installer/WinLongPathsEnabled.reg deleted file mode 100644 index 778782b272..0000000000 Binary files a/binary_installer/WinLongPathsEnabled.reg and /dev/null differ diff --git a/binary_installer/install.bat.in b/binary_installer/install.bat.in deleted file mode 100644 index 1370b98cae..0000000000 --- a/binary_installer/install.bat.in +++ /dev/null @@ -1,164 +0,0 @@ -@echo off - -@rem This script will install git (if not found on the PATH variable) -@rem using micromamba (an 8mb static-linked single-file binary, conda replacement). -@rem For users who already have git, this step will be skipped. - -@rem Next, it'll download the project's source code. -@rem Then it will download a self-contained, standalone Python and unpack it. -@rem Finally, it'll create the Python virtual environment and preload the models. - -@rem This enables a user to install this project without manually installing git or Python - -@rem change to the script's directory -PUSHD "%~dp0" - -set "no_cache_dir=--no-cache-dir" -if "%1" == "use-cache" ( - set "no_cache_dir=" -) - -echo ***** Installing InvokeAI.. ***** -@rem Config -set INSTALL_ENV_DIR=%cd%\installer_files\env -@rem https://mamba.readthedocs.io/en/latest/installation.html -set MICROMAMBA_DOWNLOAD_URL=https://github.com/cmdr2/stable-diffusion-ui/releases/download/v1.1/micromamba.exe -set RELEASE_URL=https://github.com/invoke-ai/InvokeAI -set RELEASE_SOURCEBALL=/archive/refs/heads/main.tar.gz -set PYTHON_BUILD_STANDALONE_URL=https://github.com/indygreg/python-build-standalone/releases/download -set PYTHON_BUILD_STANDALONE=20221002/cpython-3.10.7+20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz - -set PACKAGES_TO_INSTALL= - -call git --version >.tmp1 2>.tmp2 -if "%ERRORLEVEL%" NEQ "0" set PACKAGES_TO_INSTALL=%PACKAGES_TO_INSTALL% git - -@rem Cleanup -del /q .tmp1 .tmp2 - -@rem (if necessary) install git into a contained environment -if "%PACKAGES_TO_INSTALL%" NEQ "" ( - @rem download micromamba - echo ***** Downloading micromamba from %MICROMAMBA_DOWNLOAD_URL% to micromamba.exe ***** - - call curl -L "%MICROMAMBA_DOWNLOAD_URL%" > micromamba.exe - - @rem test the mamba binary - echo ***** Micromamba version: ***** - call micromamba.exe --version - - @rem create the installer env - if not exist "%INSTALL_ENV_DIR%" ( - call micromamba.exe create -y --prefix "%INSTALL_ENV_DIR%" - ) - - echo ***** Packages to install:%PACKAGES_TO_INSTALL% ***** - - call micromamba.exe install -y --prefix "%INSTALL_ENV_DIR%" -c conda-forge %PACKAGES_TO_INSTALL% - - if not exist "%INSTALL_ENV_DIR%" ( - echo ----- There was a problem while installing "%PACKAGES_TO_INSTALL%" using micromamba. Cannot continue. ----- - pause - exit /b - ) -) - -del /q micromamba.exe - -@rem For 'git' only -set PATH=%INSTALL_ENV_DIR%\Library\bin;%PATH% - -@rem Download/unpack/clean up InvokeAI release sourceball -set err_msg=----- InvokeAI source download failed ----- -echo Trying to download "%RELEASE_URL%%RELEASE_SOURCEBALL%" -curl -L %RELEASE_URL%%RELEASE_SOURCEBALL% --output InvokeAI.tgz -if %errorlevel% neq 0 goto err_exit - -set err_msg=----- InvokeAI source unpack failed ----- -tar -zxf InvokeAI.tgz -if %errorlevel% neq 0 goto err_exit - -del /q InvokeAI.tgz - -set err_msg=----- InvokeAI source copy failed ----- -cd InvokeAI-* -xcopy . .. /e /h -if %errorlevel% neq 0 goto err_exit -cd .. - -@rem cleanup -for /f %%i in ('dir /b InvokeAI-*') do rd /s /q %%i -rd /s /q .dev_scripts .github docker-build tests -del /q requirements.in requirements-mkdocs.txt shell.nix - -echo ***** Unpacked InvokeAI source ***** - -@rem Download/unpack/clean up python-build-standalone -set err_msg=----- Python download failed ----- -curl -L %PYTHON_BUILD_STANDALONE_URL%/%PYTHON_BUILD_STANDALONE% --output python.tgz -if %errorlevel% neq 0 goto err_exit - -set err_msg=----- Python unpack failed ----- -tar -zxf python.tgz -if %errorlevel% neq 0 goto err_exit - -del /q python.tgz - -echo ***** Unpacked python-build-standalone ***** - -@rem create venv -set err_msg=----- problem creating venv ----- -.\python\python -E -s -m venv .venv -if %errorlevel% neq 0 goto err_exit -call .venv\Scripts\activate.bat - -echo ***** Created Python virtual environment ***** - -@rem Print venv's Python version -set err_msg=----- problem calling venv's python ----- -echo We're running under -.venv\Scripts\python --version -if %errorlevel% neq 0 goto err_exit - -set err_msg=----- pip update failed ----- -.venv\Scripts\python -m pip install %no_cache_dir% --no-warn-script-location --upgrade pip wheel -if %errorlevel% neq 0 goto err_exit - -echo ***** Updated pip and wheel ***** - -set err_msg=----- requirements file copy failed ----- -copy binary_installer\py3.10-windows-x86_64-cuda-reqs.txt requirements.txt -if %errorlevel% neq 0 goto err_exit - -set err_msg=----- main pip install failed ----- -.venv\Scripts\python -m pip install %no_cache_dir% --no-warn-script-location -r requirements.txt -if %errorlevel% neq 0 goto err_exit - -echo ***** Installed Python dependencies ***** - -set err_msg=----- InvokeAI setup failed ----- -.venv\Scripts\python -m pip install %no_cache_dir% --no-warn-script-location -e . -if %errorlevel% neq 0 goto err_exit - -copy binary_installer\invoke.bat.in .\invoke.bat -echo ***** Installed invoke launcher script ****** - -@rem more cleanup -rd /s /q binary_installer installer_files - -@rem preload the models -call .venv\Scripts\python ldm\invoke\config\invokeai_configure.py -set err_msg=----- model download clone failed ----- -if %errorlevel% neq 0 goto err_exit -deactivate - -echo ***** Finished downloading models ***** - -echo All done! Execute the file invoke.bat in this directory to start InvokeAI -pause -exit - -:err_exit - echo %err_msg% - pause - exit diff --git a/binary_installer/install.sh.in b/binary_installer/install.sh.in deleted file mode 100644 index 888dfc40cf..0000000000 --- a/binary_installer/install.sh.in +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env bash - -# ensure we're in the correct folder in case user's CWD is somewhere else -scriptdir=$(dirname "$0") -cd "$scriptdir" - -set -euo pipefail -IFS=$'\n\t' - -function _err_exit { - if test "$1" -ne 0 - then - echo -e "Error code $1; Error caught was '$2'" - read -p "Press any key to exit..." - exit - fi -} - -# This script will install git (if not found on the PATH variable) -# using micromamba (an 8mb static-linked single-file binary, conda replacement). -# For users who already have git, this step will be skipped. - -# Next, it'll download the project's source code. -# Then it will download a self-contained, standalone Python and unpack it. -# Finally, it'll create the Python virtual environment and preload the models. - -# This enables a user to install this project without manually installing git or Python - -echo -e "\n***** Installing InvokeAI into $(pwd)... *****\n" - -export no_cache_dir="--no-cache-dir" -if [ $# -ge 1 ]; then - if [ "$1" = "use-cache" ]; then - export no_cache_dir="" - fi -fi - - -OS_NAME=$(uname -s) -case "${OS_NAME}" in - Linux*) OS_NAME="linux";; - Darwin*) OS_NAME="darwin";; - *) echo -e "\n----- Unknown OS: $OS_NAME! This script runs only on Linux or macOS -----\n" && exit -esac - -OS_ARCH=$(uname -m) -case "${OS_ARCH}" in - x86_64*) ;; - arm64*) ;; - *) echo -e "\n----- Unknown system architecture: $OS_ARCH! This script runs only on x86_64 or arm64 -----\n" && exit -esac - -# https://mamba.readthedocs.io/en/latest/installation.html -MAMBA_OS_NAME=$OS_NAME -MAMBA_ARCH=$OS_ARCH -if [ "$OS_NAME" == "darwin" ]; then - MAMBA_OS_NAME="osx" -fi - -if [ "$OS_ARCH" == "linux" ]; then - MAMBA_ARCH="aarch64" -fi - -if [ "$OS_ARCH" == "x86_64" ]; then - MAMBA_ARCH="64" -fi - -PY_ARCH=$OS_ARCH -if [ "$OS_ARCH" == "arm64" ]; then - PY_ARCH="aarch64" -fi - -# Compute device ('cd' segment of reqs files) detect goes here -# This needs a ton of work -# Suggestions: -# - lspci -# - check $PATH for nvidia-smi, gtt CUDA/GPU version from output -# - Surely there's a similar utility for AMD? -CD="cuda" -if [ "$OS_NAME" == "darwin" ] && [ "$OS_ARCH" == "arm64" ]; then - CD="mps" -fi - -# config -INSTALL_ENV_DIR="$(pwd)/installer_files/env" -MICROMAMBA_DOWNLOAD_URL="https://micro.mamba.pm/api/micromamba/${MAMBA_OS_NAME}-${MAMBA_ARCH}/latest" -RELEASE_URL=https://github.com/invoke-ai/InvokeAI -RELEASE_SOURCEBALL=/archive/refs/heads/main.tar.gz -PYTHON_BUILD_STANDALONE_URL=https://github.com/indygreg/python-build-standalone/releases/download -if [ "$OS_NAME" == "darwin" ]; then - PYTHON_BUILD_STANDALONE=20221002/cpython-3.10.7+20221002-${PY_ARCH}-apple-darwin-install_only.tar.gz -elif [ "$OS_NAME" == "linux" ]; then - PYTHON_BUILD_STANDALONE=20221002/cpython-3.10.7+20221002-${PY_ARCH}-unknown-linux-gnu-install_only.tar.gz -fi -echo "INSTALLING $RELEASE_SOURCEBALL FROM $RELEASE_URL" - -PACKAGES_TO_INSTALL="" - -if ! hash "git" &>/dev/null; then PACKAGES_TO_INSTALL="$PACKAGES_TO_INSTALL git"; fi - -# (if necessary) install git and conda into a contained environment -if [ "$PACKAGES_TO_INSTALL" != "" ]; then - # download micromamba - echo -e "\n***** Downloading micromamba from $MICROMAMBA_DOWNLOAD_URL to micromamba *****\n" - - curl -L "$MICROMAMBA_DOWNLOAD_URL" | tar -xvjO bin/micromamba > micromamba - - chmod u+x ./micromamba - - # test the mamba binary - echo -e "\n***** Micromamba version: *****\n" - ./micromamba --version - - # create the installer env - if [ ! -e "$INSTALL_ENV_DIR" ]; then - ./micromamba create -y --prefix "$INSTALL_ENV_DIR" - fi - - echo -e "\n***** Packages to install:$PACKAGES_TO_INSTALL *****\n" - - ./micromamba install -y --prefix "$INSTALL_ENV_DIR" -c conda-forge "$PACKAGES_TO_INSTALL" - - if [ ! -e "$INSTALL_ENV_DIR" ]; then - echo -e "\n----- There was a problem while initializing micromamba. Cannot continue. -----\n" - exit - fi -fi - -rm -f micromamba.exe - -export PATH="$INSTALL_ENV_DIR/bin:$PATH" - -# Download/unpack/clean up InvokeAI release sourceball -_err_msg="\n----- InvokeAI source download failed -----\n" -curl -L $RELEASE_URL/$RELEASE_SOURCEBALL --output InvokeAI.tgz -_err_exit $? _err_msg -_err_msg="\n----- InvokeAI source unpack failed -----\n" -tar -zxf InvokeAI.tgz -_err_exit $? _err_msg - -rm -f InvokeAI.tgz - -_err_msg="\n----- InvokeAI source copy failed -----\n" -cd InvokeAI-* -cp -r . .. -_err_exit $? _err_msg -cd .. - -# cleanup -rm -rf InvokeAI-*/ -rm -rf .dev_scripts/ .github/ docker-build/ tests/ requirements.in requirements-mkdocs.txt shell.nix - -echo -e "\n***** Unpacked InvokeAI source *****\n" - -# Download/unpack/clean up python-build-standalone -_err_msg="\n----- Python download failed -----\n" -curl -L $PYTHON_BUILD_STANDALONE_URL/$PYTHON_BUILD_STANDALONE --output python.tgz -_err_exit $? _err_msg -_err_msg="\n----- Python unpack failed -----\n" -tar -zxf python.tgz -_err_exit $? _err_msg - -rm -f python.tgz - -echo -e "\n***** Unpacked python-build-standalone *****\n" - -# create venv -_err_msg="\n----- problem creating venv -----\n" - -if [ "$OS_NAME" == "darwin" ]; then - # patch sysconfig so that extensions can build properly - # adapted from https://github.com/cashapp/hermit-packages/commit/fcba384663892f4d9cfb35e8639ff7a28166ee43 - PYTHON_INSTALL_DIR="$(pwd)/python" - SYSCONFIG="$(echo python/lib/python*/_sysconfigdata_*.py)" - TMPFILE="$(mktemp)" - chmod +w "${SYSCONFIG}" - cp "${SYSCONFIG}" "${TMPFILE}" - sed "s,'/install,'${PYTHON_INSTALL_DIR},g" "${TMPFILE}" > "${SYSCONFIG}" - rm -f "${TMPFILE}" -fi - -./python/bin/python3 -E -s -m venv .venv -_err_exit $? _err_msg -source .venv/bin/activate - -echo -e "\n***** Created Python virtual environment *****\n" - -# Print venv's Python version -_err_msg="\n----- problem calling venv's python -----\n" -echo -e "We're running under" -.venv/bin/python3 --version -_err_exit $? _err_msg - -_err_msg="\n----- pip update failed -----\n" -.venv/bin/python3 -m pip install $no_cache_dir --no-warn-script-location --upgrade pip -_err_exit $? _err_msg - -echo -e "\n***** Updated pip *****\n" - -_err_msg="\n----- requirements file copy failed -----\n" -cp binary_installer/py3.10-${OS_NAME}-"${OS_ARCH}"-${CD}-reqs.txt requirements.txt -_err_exit $? _err_msg - -_err_msg="\n----- main pip install failed -----\n" -.venv/bin/python3 -m pip install $no_cache_dir --no-warn-script-location -r requirements.txt -_err_exit $? _err_msg - -echo -e "\n***** Installed Python dependencies *****\n" - -_err_msg="\n----- InvokeAI setup failed -----\n" -.venv/bin/python3 -m pip install $no_cache_dir --no-warn-script-location -e . -_err_exit $? _err_msg - -echo -e "\n***** Installed InvokeAI *****\n" - -cp binary_installer/invoke.sh.in ./invoke.sh -chmod a+rx ./invoke.sh -echo -e "\n***** Installed invoke launcher script ******\n" - -# more cleanup -rm -rf binary_installer/ installer_files/ - -# preload the models -.venv/bin/python3 scripts/configure_invokeai.py -_err_msg="\n----- model download clone failed -----\n" -_err_exit $? _err_msg -deactivate - -echo -e "\n***** Finished downloading models *****\n" - -echo "All done! Run the command" -echo " $scriptdir/invoke.sh" -echo "to start InvokeAI." -read -p "Press any key to exit..." -exit diff --git a/binary_installer/invoke.bat.in b/binary_installer/invoke.bat.in deleted file mode 100644 index a120ed75a5..0000000000 --- a/binary_installer/invoke.bat.in +++ /dev/null @@ -1,36 +0,0 @@ -@echo off - -PUSHD "%~dp0" -call .venv\Scripts\activate.bat - -echo Do you want to generate images using the -echo 1. command-line -echo 2. browser-based UI -echo OR -echo 3. open the developer console -set /p choice="Please enter 1, 2 or 3: " -if /i "%choice%" == "1" ( - echo Starting the InvokeAI command-line. - .venv\Scripts\python scripts\invoke.py %* -) else if /i "%choice%" == "2" ( - echo Starting the InvokeAI browser-based UI. - .venv\Scripts\python scripts\invoke.py --web %* -) else if /i "%choice%" == "3" ( - echo Developer Console - echo Python command is: - where python - echo Python version is: - python --version - echo ************************* - echo You are now in the system shell, with the local InvokeAI Python virtual environment activated, - echo so that you can troubleshoot this InvokeAI installation as necessary. - echo ************************* - echo *** Type `exit` to quit this shell and deactivate the Python virtual environment *** - call cmd /k -) else ( - echo Invalid selection - pause - exit /b -) - -deactivate diff --git a/binary_installer/invoke.sh.in b/binary_installer/invoke.sh.in deleted file mode 100644 index b131b4ae2f..0000000000 --- a/binary_installer/invoke.sh.in +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env sh - -set -eu - -. .venv/bin/activate - -# set required env var for torch on mac MPS - if [ "$(uname -s)" == "Darwin" ]; then - export PYTORCH_ENABLE_MPS_FALLBACK=1 - fi - -echo "Do you want to generate images using the" -echo "1. command-line" -echo "2. browser-based UI" -echo "OR" -echo "3. open the developer console" -echo "Please enter 1, 2, or 3:" -read choice - -case $choice in - 1) - printf "\nStarting the InvokeAI command-line..\n"; - .venv/bin/python scripts/invoke.py $*; - ;; - 2) - printf "\nStarting the InvokeAI browser-based UI..\n"; - .venv/bin/python scripts/invoke.py --web $*; - ;; - 3) - printf "\nDeveloper Console:\n"; - printf "Python command is:\n\t"; - which python; - printf "Python version is:\n\t"; - python --version; - echo "*************************" - echo "You are now in your user shell ($SHELL) with the local InvokeAI Python virtual environment activated,"; - echo "so that you can troubleshoot this InvokeAI installation as necessary."; - printf "*************************\n" - echo "*** Type \`exit\` to quit this shell and deactivate the Python virtual environment *** "; - /usr/bin/env "$SHELL"; - ;; - *) - echo "Invalid selection"; - exit - ;; -esac diff --git a/binary_installer/py3.10-darwin-arm64-mps-reqs.txt b/binary_installer/py3.10-darwin-arm64-mps-reqs.txt deleted file mode 100644 index 2ec36a431c..0000000000 --- a/binary_installer/py3.10-darwin-arm64-mps-reqs.txt +++ /dev/null @@ -1,2097 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: -# -# pip-compile --allow-unsafe --generate-hashes --output-file=installer/py3.10-darwin-arm64-mps-reqs.txt installer/requirements.in -# ---extra-index-url https://download.pytorch.org/whl/torch_stable.html ---extra-index-url https://download.pytorch.org/whl/cu116 ---trusted-host https - -absl-py==1.3.0 \ - --hash=sha256:34995df9bd7a09b3b8749e230408f5a2a2dd7a68a0d33c12a3d0cb15a041a507 \ - --hash=sha256:463c38a08d2e4cef6c498b76ba5bd4858e4c6ef51da1a5a1f27139a022e20248 - # via - # tb-nightly - # tensorboard -accelerate==0.14.0 \ - --hash=sha256:31c5bcc40564ef849b5bc1c4424a43ccaf9e26413b7df89c2e36bf81f070fd44 \ - --hash=sha256:b15d562c0889d0cf441b01faa025dfc29b163d061b6cc7d489c2c83b0a55ffab - # via - # -r installer/requirements.in - # k-diffusion -addict==2.4.0 \ - --hash=sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc \ - --hash=sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494 - # via basicsr -aiohttp==3.8.3 \ - --hash=sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8 \ - --hash=sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142 \ - --hash=sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18 \ - --hash=sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34 \ - --hash=sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a \ - --hash=sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033 \ - --hash=sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06 \ - --hash=sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4 \ - --hash=sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d \ - --hash=sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b \ - --hash=sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc \ - --hash=sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091 \ - --hash=sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d \ - --hash=sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85 \ - --hash=sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb \ - --hash=sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937 \ - --hash=sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf \ - --hash=sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1 \ - --hash=sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b \ - --hash=sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d \ - --hash=sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269 \ - --hash=sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da \ - --hash=sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346 \ - --hash=sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494 \ - --hash=sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697 \ - --hash=sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4 \ - --hash=sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585 \ - --hash=sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c \ - --hash=sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da \ - --hash=sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad \ - --hash=sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2 \ - --hash=sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6 \ - --hash=sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c \ - --hash=sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849 \ - --hash=sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa \ - --hash=sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b \ - --hash=sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb \ - --hash=sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7 \ - --hash=sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715 \ - --hash=sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76 \ - --hash=sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d \ - --hash=sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276 \ - --hash=sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6 \ - --hash=sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37 \ - --hash=sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb \ - --hash=sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d \ - --hash=sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c \ - --hash=sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446 \ - --hash=sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008 \ - --hash=sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342 \ - --hash=sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d \ - --hash=sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7 \ - --hash=sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061 \ - --hash=sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba \ - --hash=sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7 \ - --hash=sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290 \ - --hash=sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0 \ - --hash=sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d \ - --hash=sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8 \ - --hash=sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f \ - --hash=sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48 \ - --hash=sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502 \ - --hash=sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62 \ - --hash=sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9 \ - --hash=sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403 \ - --hash=sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77 \ - --hash=sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476 \ - --hash=sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e \ - --hash=sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96 \ - --hash=sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5 \ - --hash=sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784 \ - --hash=sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091 \ - --hash=sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b \ - --hash=sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97 \ - --hash=sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a \ - --hash=sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2 \ - --hash=sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9 \ - --hash=sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d \ - --hash=sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73 \ - --hash=sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017 \ - --hash=sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363 \ - --hash=sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c \ - --hash=sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d \ - --hash=sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618 \ - --hash=sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491 \ - --hash=sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b \ - --hash=sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca - # via fsspec -aiosignal==1.2.0 \ - --hash=sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a \ - --hash=sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2 - # via aiohttp -albumentations==1.3.0 \ - --hash=sha256:294165d87d03bc8323e484927f0a5c1a3c64b0e7b9c32a979582a6c93c363bdf \ - --hash=sha256:be1af36832c8893314f2a5550e8ac19801e04770734c1b70fa3c996b41f37bed - # via -r installer/requirements.in -altair==4.2.0 \ - --hash=sha256:0c724848ae53410c13fa28be2b3b9a9dcb7b5caa1a70f7f217bd663bb419935a \ - --hash=sha256:d87d9372e63b48cd96b2a6415f0cf9457f50162ab79dc7a31cd7e024dd840026 - # via streamlit -antlr4-python3-runtime==4.9.3 \ - --hash=sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b - # via omegaconf -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c - # via aiohttp -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c - # via - # aiohttp - # jsonschema -basicsr==1.4.2 \ - --hash=sha256:b89b595a87ef964cda9913b4d99380ddb6554c965577c0c10cb7b78e31301e87 - # via - # gfpgan - # realesrgan -bidict==0.22.0 \ - --hash=sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0 \ - --hash=sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8 - # via python-socketio -blinker==1.5 \ - --hash=sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36 \ - --hash=sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462 - # via streamlit -boltons==21.0.0 \ - --hash=sha256:65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13 \ - --hash=sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b - # via torchsde -cachetools==5.2.0 \ - --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ - --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db - # via - # google-auth - # streamlit -certifi==2022.9.24 \ - --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ - --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 - # via - # requests - # sentry-sdk -chardet==4.0.0 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 - # via requests -charset-normalizer==2.1.1 \ - --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ - --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f - # via aiohttp -clean-fid==0.1.34 \ - --hash=sha256:2997f85a67a28c95adaae7899a33fc10537164fef4cdd424e3257bffad79a901 - # via k-diffusion -click==8.1.3 \ - --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ - --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 - # via - # flask - # streamlit - # wandb -clip @ https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip \ - --hash=sha256:b5842c25da441d6c581b53a5c60e0c2127ebafe0f746f8e15561a006c6c3be6a - # via - # -r installer/requirements.in - # clipseg -clipseg @ https://github.com/invoke-ai/clipseg/archive/1f754751c85d7d4255fa681f4491ff5711c1c288.zip \ - --hash=sha256:14f43ed42f90be3fe57f06de483cb8be0f67f87a6f62a011339d45a39f4b4189 - # via -r installer/requirements.in -commonmark==0.9.1 \ - --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ - --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 - # via rich -contourpy==1.0.6 \ - --hash=sha256:0236875c5a0784215b49d00ebbe80c5b6b5d5244b3655a36dda88105334dea17 \ - --hash=sha256:03d1b9c6b44a9e30d554654c72be89af94fab7510b4b9f62356c64c81cec8b7d \ - --hash=sha256:0537cc1195245bbe24f2913d1f9211b8f04eb203de9044630abd3664c6cc339c \ - --hash=sha256:06ca79e1efbbe2df795822df2fa173d1a2b38b6e0f047a0ec7903fbca1d1847e \ - --hash=sha256:08e8d09d96219ace6cb596506fb9b64ea5f270b2fb9121158b976d88871fcfd1 \ - --hash=sha256:0b1e66346acfb17694d46175a0cea7d9036f12ed0c31dfe86f0f405eedde2bdd \ - --hash=sha256:0b97454ed5b1368b66ed414c754cba15b9750ce69938fc6153679787402e4cdf \ - --hash=sha256:0e4854cc02006ad6684ce092bdadab6f0912d131f91c2450ce6dbdea78ee3c0b \ - --hash=sha256:12a7dc8439544ed05c6553bf026d5e8fa7fad48d63958a95d61698df0e00092b \ - --hash=sha256:1b1ee48a130da4dd0eb8055bbab34abf3f6262957832fd575e0cab4979a15a41 \ - --hash=sha256:1c0e1308307a75e07d1f1b5f0f56b5af84538a5e9027109a7bcf6cb47c434e72 \ - --hash=sha256:1dedf4c64185a216c35eb488e6f433297c660321275734401760dafaeb0ad5c2 \ - --hash=sha256:208bc904889c910d95aafcf7be9e677726df9ef71e216780170dbb7e37d118fa \ - --hash=sha256:211dfe2bd43bf5791d23afbe23a7952e8ac8b67591d24be3638cabb648b3a6eb \ - --hash=sha256:341330ed19074f956cb20877ad8d2ae50e458884bfa6a6df3ae28487cc76c768 \ - --hash=sha256:344cb3badf6fc7316ad51835f56ac387bdf86c8e1b670904f18f437d70da4183 \ - --hash=sha256:358f6364e4873f4d73360b35da30066f40387dd3c427a3e5432c6b28dd24a8fa \ - --hash=sha256:371f6570a81dfdddbb837ba432293a63b4babb942a9eb7aaa699997adfb53278 \ - --hash=sha256:375d81366afd547b8558c4720337218345148bc2fcffa3a9870cab82b29667f2 \ - --hash=sha256:3a1917d3941dd58732c449c810fa7ce46cc305ce9325a11261d740118b85e6f3 \ - --hash=sha256:4081918147fc4c29fad328d5066cfc751da100a1098398742f9f364be63803fc \ - --hash=sha256:444fb776f58f4906d8d354eb6f6ce59d0a60f7b6a720da6c1ccb839db7c80eb9 \ - --hash=sha256:46deb310a276cc5c1fd27958e358cce68b1e8a515fa5a574c670a504c3a3fe30 \ - --hash=sha256:494efed2c761f0f37262815f9e3c4bb9917c5c69806abdee1d1cb6611a7174a0 \ - --hash=sha256:50627bf76abb6ba291ad08db583161939c2c5fab38c38181b7833423ab9c7de3 \ - --hash=sha256:5641927cc5ae66155d0c80195dc35726eae060e7defc18b7ab27600f39dd1fe7 \ - --hash=sha256:5b117d29433fc8393b18a696d794961464e37afb34a6eeb8b2c37b5f4128a83e \ - --hash=sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6 \ - --hash=sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142 \ - --hash=sha256:6f56515e7c6fae4529b731f6c117752247bef9cdad2b12fc5ddf8ca6a50965a5 \ - --hash=sha256:730c27978a0003b47b359935478b7d63fd8386dbb2dcd36c1e8de88cbfc1e9de \ - --hash=sha256:75a2e638042118118ab39d337da4c7908c1af74a8464cad59f19fbc5bbafec9b \ - --hash=sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb \ - --hash=sha256:7ee394502026d68652c2824348a40bf50f31351a668977b51437131a90d777ea \ - --hash=sha256:8468b40528fa1e15181cccec4198623b55dcd58306f8815a793803f51f6c474a \ - --hash=sha256:84c593aeff7a0171f639da92cb86d24954bbb61f8a1b530f74eb750a14685832 \ - --hash=sha256:913bac9d064cff033cf3719e855d4f1db9f1c179e0ecf3ba9fdef21c21c6a16a \ - --hash=sha256:9447c45df407d3ecb717d837af3b70cfef432138530712263730783b3d016512 \ - --hash=sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675 \ - --hash=sha256:9bc407a6af672da20da74823443707e38ece8b93a04009dca25856c2d9adadb1 \ - --hash=sha256:9e8e686a6db92a46111a1ee0ee6f7fbfae4048f0019de207149f43ac1812cf95 \ - --hash=sha256:9fc4e7973ed0e1fe689435842a6e6b330eb7ccc696080dda9a97b1a1b78e41db \ - --hash=sha256:a457ee72d9032e86730f62c5eeddf402e732fdf5ca8b13b41772aa8ae13a4563 \ - --hash=sha256:a628bba09ba72e472bf7b31018b6281fd4cc903f0888049a3724afba13b6e0b8 \ - --hash=sha256:a79d239fc22c3b8d9d3de492aa0c245533f4f4c7608e5749af866949c0f1b1b9 \ - --hash=sha256:aa4674cf3fa2bd9c322982644967f01eed0c91bb890f624e0e0daf7a5c3383e9 \ - --hash=sha256:acd2bd02f1a7adff3a1f33e431eb96ab6d7987b039d2946a9b39fe6fb16a1036 \ - --hash=sha256:b3b1bd7577c530eaf9d2bc52d1a93fef50ac516a8b1062c3d1b9bcec9ebe329b \ - --hash=sha256:b48d94386f1994db7c70c76b5808c12e23ed7a4ee13693c2fc5ab109d60243c0 \ - --hash=sha256:b64f747e92af7da3b85631a55d68c45a2d728b4036b03cdaba4bd94bcc85bd6f \ - --hash=sha256:b98c820608e2dca6442e786817f646d11057c09a23b68d2b3737e6dcb6e4a49b \ - --hash=sha256:c1baa49ab9fedbf19d40d93163b7d3e735d9cd8d5efe4cce9907902a6dad391f \ - --hash=sha256:c38c6536c2d71ca2f7e418acaf5bca30a3af7f2a2fa106083c7d738337848dbe \ - --hash=sha256:c78bfbc1a7bff053baf7e508449d2765964d67735c909b583204e3240a2aca45 \ - --hash=sha256:cd2bc0c8f2e8de7dd89a7f1c10b8844e291bca17d359373203ef2e6100819edd \ - --hash=sha256:d2eff2af97ea0b61381828b1ad6cd249bbd41d280e53aea5cccd7b2b31b8225c \ - --hash=sha256:d8834c14b8c3dd849005e06703469db9bf96ba2d66a3f88ecc539c9a8982e0ee \ - --hash=sha256:d912f0154a20a80ea449daada904a7eb6941c83281a9fab95de50529bfc3a1da \ - --hash=sha256:da1ef35fd79be2926ba80fbb36327463e3656c02526e9b5b4c2b366588b74d9a \ - --hash=sha256:dbe6fe7a1166b1ddd7b6d887ea6fa8389d3f28b5ed3f73a8f40ece1fc5a3d340 \ - --hash=sha256:dcd556c8fc37a342dd636d7eef150b1399f823a4462f8c968e11e1ebeabee769 \ - --hash=sha256:e13b31d1b4b68db60b3b29f8e337908f328c7f05b9add4b1b5c74e0691180109 \ - --hash=sha256:e1739496c2f0108013629aa095cc32a8c6363444361960c07493818d0dea2da4 \ - --hash=sha256:e43255a83835a129ef98f75d13d643844d8c646b258bebd11e4a0975203e018f \ - --hash=sha256:e626cefff8491bce356221c22af5a3ea528b0b41fbabc719c00ae233819ea0bf \ - --hash=sha256:eadad75bf91897f922e0fb3dca1b322a58b1726a953f98c2e5f0606bd8408621 \ - --hash=sha256:f33da6b5d19ad1bb5e7ad38bb8ba5c426d2178928bc2b2c44e8823ea0ecb6ff3 \ - --hash=sha256:f4052a8a4926d4468416fc7d4b2a7b2a3e35f25b39f4061a7e2a3a2748c4fc48 \ - --hash=sha256:f6ca38dd8d988eca8f07305125dec6f54ac1c518f1aaddcc14d08c01aebb6efc - # via matplotlib -cycler==0.11.0 \ - --hash=sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3 \ - --hash=sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f - # via matplotlib -decorator==5.1.1 \ - --hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \ - --hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 - # via validators -diffusers==0.7.2 \ - --hash=sha256:4a5f8b3a5fbd936bba7d459611cb35ec62875030367be32b232f9e19543e25a9 \ - --hash=sha256:fb814ffd150cc6f470380b8c6a521181a77beb2f44134d2aad2e4cd8aa2ced0e - # via -r installer/requirements.in -dnspython==2.2.1 \ - --hash=sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e \ - --hash=sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f - # via eventlet -docker-pycreds==0.4.0 \ - --hash=sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4 \ - --hash=sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49 - # via wandb -einops==0.5.0 \ - --hash=sha256:055de7eeb3cb9e9710ef3085a811090c6b52e809b7044e8785824ed185f486d1 \ - --hash=sha256:8b7a83cffc1ea88e306df099b7cbb9c3ba5003bd84d05ae44be5655864abb8d3 - # via k-diffusion -entrypoints==0.4 \ - --hash=sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4 \ - --hash=sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f - # via altair -eventlet==0.33.1 \ - --hash=sha256:a085922698e5029f820cf311a648ac324d73cec0e4792877609d978a4b5bbf31 \ - --hash=sha256:afbe17f06a58491e9aebd7a4a03e70b0b63fd4cf76d8307bae07f280479b1515 - # via -r installer/requirements.in -facexlib==0.2.5 \ - --hash=sha256:31e20cc4ed5d63562d380e4564bae14ac0d5d1899a079bad87621e13564567e4 \ - --hash=sha256:cc7ceb56c5424319c47223cf75eef6828c34c66082707c6eb35b95d39779f02d - # via - # gfpgan - # realesrgan -filelock==3.8.0 \ - --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ - --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 - # via - # diffusers - # huggingface-hub - # transformers -filterpy==1.4.5 \ - --hash=sha256:4f2a4d39e4ea601b9ab42b2db08b5918a9538c168cff1c6895ae26646f3d73b1 - # via facexlib -flask==2.2.2 \ - --hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \ - --hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526 - # via - # flask-cors - # flask-socketio -flask-cors==3.0.10 \ - --hash=sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438 \ - --hash=sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de - # via -r installer/requirements.in -flask-socketio==5.3.1 \ - --hash=sha256:fd0ed0fc1341671d92d5f5b2f5503916deb7aa7e2940e6636cfa2c087c828bf9 \ - --hash=sha256:ff0c721f20bff1e2cfba77948727a8db48f187e89a72fe50c34478ce6efb3353 - # via -r installer/requirements.in -flaskwebgui==0.3.7 \ - --hash=sha256:4a69955308eaa8bb256ba04a994dc8f58a48dcd6f9599694ab1bcd9f43d88a5d \ - --hash=sha256:535974ce2672dcc74787c254de24cceed4101be75d96952dae82014dd57f061e - # via -r installer/requirements.in -fonttools==4.38.0 \ - --hash=sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1 \ - --hash=sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb - # via matplotlib -frozenlist==1.3.1 \ - --hash=sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e \ - --hash=sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04 \ - --hash=sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944 \ - --hash=sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845 \ - --hash=sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f \ - --hash=sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f \ - --hash=sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb \ - --hash=sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2 \ - --hash=sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3 \ - --hash=sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f \ - --hash=sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a \ - --hash=sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131 \ - --hash=sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1 \ - --hash=sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa \ - --hash=sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8 \ - --hash=sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b \ - --hash=sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2 \ - --hash=sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e \ - --hash=sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b \ - --hash=sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b \ - --hash=sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10 \ - --hash=sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170 \ - --hash=sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8 \ - --hash=sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b \ - --hash=sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989 \ - --hash=sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd \ - --hash=sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03 \ - --hash=sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519 \ - --hash=sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189 \ - --hash=sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b \ - --hash=sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca \ - --hash=sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff \ - --hash=sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96 \ - --hash=sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d \ - --hash=sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5 \ - --hash=sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2 \ - --hash=sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204 \ - --hash=sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a \ - --hash=sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8 \ - --hash=sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141 \ - --hash=sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792 \ - --hash=sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9 \ - --hash=sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c \ - --hash=sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c \ - --hash=sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221 \ - --hash=sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d \ - --hash=sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc \ - --hash=sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f \ - --hash=sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9 \ - --hash=sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef \ - --hash=sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3 \ - --hash=sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab \ - --hash=sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b \ - --hash=sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db \ - --hash=sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988 \ - --hash=sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6 \ - --hash=sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc \ - --hash=sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83 \ - --hash=sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be - # via - # aiohttp - # aiosignal -fsspec[http]==2022.10.0 \ - --hash=sha256:6b7c6ab3b476cdf17efcfeccde7fca28ef5a48f73a71010aaceec5fc15bf9ebf \ - --hash=sha256:cb6092474e90487a51de768170f3afa50ca8982c26150a59072b16433879ff1d - # via pytorch-lightning -ftfy==6.1.1 \ - --hash=sha256:0ffd33fce16b54cccaec78d6ec73d95ad370e5df5a25255c8966a6147bd667ca \ - --hash=sha256:bfc2019f84fcd851419152320a6375604a0f1459c281b5b199b2cd0d2e727f8f - # via clip -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d - # via - # basicsr - # test-tube -getpass-asterisk==1.0.1 \ - --hash=sha256:20d45cafda0066d761961e0919728526baf7bb5151fbf48a7d5ea4034127d857 \ - --hash=sha256:7cc357a924cf62fa4e15b73cb4e5e30685c9084e464ffdc3fd9000a2b54ea9e9 - # via -r installer/requirements.in -gfpgan @ https://github.com/TencentARC/GFPGAN/archive/2eac2033893ca7f427f4035d80fe95b92649ac56.zip \ - --hash=sha256:79e6d71c8f1df7c7ccb0ac6b9a2ccb615ad5cde818c8b6f285a8711c05aebf85 - # via - # -r installer/requirements.in - # realesrgan -gitdb==4.0.9 \ - --hash=sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd \ - --hash=sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa - # via gitpython -gitpython==3.1.29 \ - --hash=sha256:41eea0deec2deea139b459ac03656f0dd28fc4a3387240ec1d3c259a2c47850f \ - --hash=sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd - # via - # streamlit - # wandb -google-auth==2.14.0 \ - --hash=sha256:1ad5b0e6eba5f69645971abb3d2c197537d5914070a8c6d30299dfdb07c5c700 \ - --hash=sha256:cf24817855d874ede2efd071aa22125445f555de1685b739a9782fcf408c2a3d - # via - # google-auth-oauthlib - # tb-nightly - # tensorboard -google-auth-oauthlib==0.4.6 \ - --hash=sha256:3f2a6e802eebbb6fb736a370fbf3b055edcb6b52878bf2f26330b5e041316c73 \ - --hash=sha256:a90a072f6993f2c327067bf65270046384cda5a8ecb20b94ea9a687f1f233a7a - # via - # tb-nightly - # tensorboard -greenlet==2.0.0.post0 \ - --hash=sha256:00ebdaf0fa51c284fd2172837d751731a15971e0c20d1a9163cfbdf620ce8b49 \ - --hash=sha256:029ca674b3a7e8427db8f5c65d5ed4e24a7417af2a415a5958598aefd71980c4 \ - --hash=sha256:02bdb1e373b275bd705c43b249426e776c4f8a8ff2afaf8ec5ea0dde487d8a14 \ - --hash=sha256:08dc04f49ed1ea5e6772bb5e8cf2a77d1b1744566f4eca471a55b35af1278b31 \ - --hash=sha256:08f44e938d142271b954405afb6570e0be48a9f556b6bf4d42d2e3ae6a251fad \ - --hash=sha256:0a5c03e2a68ec2ff1cba74ceaed899ec8cd353285f4f985c30c8cfbef9d3a3be \ - --hash=sha256:0fee3240093b745efc857392f09379514ad84db4ca324514594bbdf6380016c8 \ - --hash=sha256:118e708dd7bc88beaeeaa5a8601a7743b8835b7bbaf7c8f23ffa78f8bc8faf28 \ - --hash=sha256:13d492a807a5c7334b5931e9b6d9b181991ccc6a40555a7b177f189feff59b4b \ - --hash=sha256:1cac9e9895aeff26434325404558783ee54f4ff3aec8daa56b8706796f7b01a0 \ - --hash=sha256:2146d15429b4eeb412428737594acb5660a5bc0fdd1488d8a2a74a5ee32391fa \ - --hash=sha256:21ee1ae26d072b195edea764218623f6c15eba4ae06816908f33c82e0af018d3 \ - --hash=sha256:22eca421e3f2f3c18f4f54c0ff525aa9d397c6f116fce9ebd37b420174dbc027 \ - --hash=sha256:2bab49783858cf724fff6868395cbeb81d1188cba23616b53e79de0beda29f42 \ - --hash=sha256:2fbdec204ca40b3d0c0992a19c1ba627441c17983ac4ffc45baec7f5f53e20ca \ - --hash=sha256:30ce47525f9a1515566429ac7de6b1ae76d32c3ccede256e3517a1a6419cf659 \ - --hash=sha256:335dcf676d5e4122e4006c16ae11eda2467af5461b949c265ce120b6b959ffe2 \ - --hash=sha256:3407b843b05da71fef0f1dd666059c08ad0e0f4afc3b9c93c998a2e53fac95e5 \ - --hash=sha256:35827f98fd0d768862b8f15777e6dbb03fe6ac6e7bd1bee3f3ded4536f350347 \ - --hash=sha256:3a22e5988f9d66b3e9ae9583bf9d8ef792b09f23afeb78707e6a4f47ab57cc5e \ - --hash=sha256:3c3327da2bdab61078e42e695307465c425671a5a9251e6c29ee130d51943f28 \ - --hash=sha256:3ca723dfc2789c1fb991809822811896b198ecf0909dbccea4a07170d18c3e1b \ - --hash=sha256:46156ae88ee71c37b6c4f7af63fff5d3ab8f45ef72e1a660bcf6386c1647f106 \ - --hash=sha256:4bbe2d074292e3646704371eb640ee52c386d633ed72ff223dadcd3fe8ecd8f9 \ - --hash=sha256:4c4310f0e42154995d92810f27b44ab7116a4a696feb0ff141ae2de59196efd7 \ - --hash=sha256:4cfa629de5b2dea27c81b334c4536463e9a49ac0877e2008a276d58d4c72868a \ - --hash=sha256:4e144ab0de56b4d2a2cf0d2fb9d568b59fce49aab3e129badf17c12b0252047d \ - --hash=sha256:4ea67f303cec384b148774667c7e3cf02311e7026fc02bdcdcd206dfe4ea4fc9 \ - --hash=sha256:538c9e8f65a32413ace426f8117ef019021adf8175f7c491fed65f5fe2083e0c \ - --hash=sha256:56565ac9ab4ff3dd473bfe959e0bf2a5062aabb89b7c94cabb417beb162c9fff \ - --hash=sha256:5e22485256bb1c60bbcc6f8509b1a11042358a2462d5ecdb9a82dc472d2fdd60 \ - --hash=sha256:602a69c24f1a9755dd1760b3b31bdfc495c4613260c876a01b7e6d5eb9bcae1b \ - --hash=sha256:6393ec3cecda53b20241e88bc33d87cbd8126cc10870fc69fa16ca2e20a5ac1b \ - --hash=sha256:6442bbfb047dc1e47658954b72e1589f2bc4e12e67d51bbad0059a626180daa1 \ - --hash=sha256:666d2a0b269a68cd4fe0976544ab97970c5334d35d0e47ae9be1723f734d8204 \ - --hash=sha256:697cfbfc19815c40213badcfe5f076418e0f9100cd25a66f513f32c1026b8bf4 \ - --hash=sha256:6a1a6745c5dce202aa3f29a1736c53cf2179e9c3b280dc62cea9cb8c69977c83 \ - --hash=sha256:6fc73fc8dd81d9efa842a55033b6b4cb233b134a0270e127c6874d053ef2049b \ - --hash=sha256:7e9e0d4c5c618b0442396715ffe6c2f84a60d593dad7e0184388aed36d568a65 \ - --hash=sha256:81fdcf7c0c2df46a99ca421a552c4370117851c5e4dbd6cb53d569b896d62322 \ - --hash=sha256:8b26932be686f3582df039d79fe96f7ca13d63b39468162f816f9ff29584b9a4 \ - --hash=sha256:8b7e5191b974fb66fcbac1818ba494d3512da9cf6eaef7acd952f9862eaaa20c \ - --hash=sha256:8c80e9c41a83d8c90399af8c7dcdeae0c03c48b40b9d0ab84457533d5d7882bf \ - --hash=sha256:9f2f110b9cc325f6543e0e3f4ab8008c272a59052f9464047c29d4be4511ce05 \ - --hash=sha256:a339e510a079dc8372e39ce1c7629414db51966235c9670c58d529def79243a2 \ - --hash=sha256:ad9abc3e4d2370cecb524421cc5c8a664006aa11d5c1cb3c9250e3bf65ab546e \ - --hash=sha256:b043782c8f6cccc8fae3a16db397eca1d36a41b0706cbf6f514aea1e1a260bab \ - --hash=sha256:b31de27313abbb567c528ed123380fcf18a5dfd03134570dfd12227e21ac1184 \ - --hash=sha256:b75e5644cc353328cd57ec8dafaaf5f81b2c3ecf7c4b278b907e99ad53ba7839 \ - --hash=sha256:b8cfc8fc944bd7b704691bc28225a2635e377e92dc413459845868d3f7724982 \ - --hash=sha256:c2055c52260808d87622293b57df1c68aeb12ddd8a0cfc0223fb57a5f629e202 \ - --hash=sha256:c416106b3b8e905b6ab0e84ec90047a6401021aa023f9aa93978e57cd8f8189f \ - --hash=sha256:d0e210e17a6181a3fd3f8dce957043a4e74177ffa9f295514984b2b633940dce \ - --hash=sha256:d9453135e48cd631e3e9f06d9da9100d17c9f662e4a6d8b552c29be6c834a6b9 \ - --hash=sha256:dd0198006278291d9469309d655093df1f5e5107c0261e242b5f390baee32199 \ - --hash=sha256:e1781bda1e787d3ad33788cc3be47f6e47a9581676d02670c15ee36c9460adfe \ - --hash=sha256:e56a5a9f303e3ac011ba445a6d84f05d08666bf8db094afafcec5228622c30f5 \ - --hash=sha256:e93ae35f0fd3caf75e58c76a1cab71e6ece169aaa1b281782ef9efde0a6b83f2 \ - --hash=sha256:eb36b6570646227a63eda03916f1cc6f3744ee96d28f7a0a5629c59267a8055f \ - --hash=sha256:f8c425a130e04d5404edaf6f5906e5ab12f3aa1168a1828aba6dfadac5910469 - # via eventlet -grpcio==1.50.0 \ - --hash=sha256:05f7c248e440f538aaad13eee78ef35f0541e73498dd6f832fe284542ac4b298 \ - --hash=sha256:080b66253f29e1646ac53ef288c12944b131a2829488ac3bac8f52abb4413c0d \ - --hash=sha256:12b479839a5e753580b5e6053571de14006157f2ef9b71f38c56dc9b23b95ad6 \ - --hash=sha256:156f8009e36780fab48c979c5605eda646065d4695deea4cfcbcfdd06627ddb6 \ - --hash=sha256:15f9e6d7f564e8f0776770e6ef32dac172c6f9960c478616c366862933fa08b4 \ - --hash=sha256:177afaa7dba3ab5bfc211a71b90da1b887d441df33732e94e26860b3321434d9 \ - --hash=sha256:1a4cd8cb09d1bc70b3ea37802be484c5ae5a576108bad14728f2516279165dd7 \ - --hash=sha256:1d8d02dbb616c0a9260ce587eb751c9c7dc689bc39efa6a88cc4fa3e9c138a7b \ - --hash=sha256:2b71916fa8f9eb2abd93151fafe12e18cebb302686b924bd4ec39266211da525 \ - --hash=sha256:2d9fd6e38b16c4d286a01e1776fdf6c7a4123d99ae8d6b3f0b4a03a34bf6ce45 \ - --hash=sha256:3b611b3de3dfd2c47549ca01abfa9bbb95937eb0ea546ea1d762a335739887be \ - --hash=sha256:3e4244c09cc1b65c286d709658c061f12c61c814be0b7030a2d9966ff02611e0 \ - --hash=sha256:40838061e24f960b853d7bce85086c8e1b81c6342b1f4c47ff0edd44bbae2722 \ - --hash=sha256:4b123fbb7a777a2fedec684ca0b723d85e1d2379b6032a9a9b7851829ed3ca9a \ - --hash=sha256:531f8b46f3d3db91d9ef285191825d108090856b3bc86a75b7c3930f16ce432f \ - --hash=sha256:67dd41a31f6fc5c7db097a5c14a3fa588af54736ffc174af4411d34c4f306f68 \ - --hash=sha256:7489dbb901f4fdf7aec8d3753eadd40839c9085967737606d2c35b43074eea24 \ - --hash=sha256:8d4c8e73bf20fb53fe5a7318e768b9734cf122fe671fcce75654b98ba12dfb75 \ - --hash=sha256:8e69aa4e9b7f065f01d3fdcecbe0397895a772d99954bb82eefbb1682d274518 \ - --hash=sha256:8e8999a097ad89b30d584c034929f7c0be280cd7851ac23e9067111167dcbf55 \ - --hash=sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974 \ - --hash=sha256:92d7635d1059d40d2ec29c8bf5ec58900120b3ce5150ef7414119430a4b2dd5c \ - --hash=sha256:931e746d0f75b2a5cff0a1197d21827a3a2f400c06bace036762110f19d3d507 \ - --hash=sha256:95ce51f7a09491fb3da8cf3935005bff19983b77c4e9437ef77235d787b06842 \ - --hash=sha256:9eea18a878cffc804506d39c6682d71f6b42ec1c151d21865a95fae743fda500 \ - --hash=sha256:a23d47f2fc7111869f0ff547f771733661ff2818562b04b9ed674fa208e261f4 \ - --hash=sha256:a4c23e54f58e016761b576976da6a34d876420b993f45f66a2bfb00363ecc1f9 \ - --hash=sha256:a50a1be449b9e238b9bd43d3857d40edf65df9416dea988929891d92a9f8a778 \ - --hash=sha256:ab5d0e3590f0a16cb88de4a3fa78d10eb66a84ca80901eb2c17c1d2c308c230f \ - --hash=sha256:ae23daa7eda93c1c49a9ecc316e027ceb99adbad750fbd3a56fa9e4a2ffd5ae0 \ - --hash=sha256:af98d49e56605a2912cf330b4627e5286243242706c3a9fa0bcec6e6f68646fc \ - --hash=sha256:b2f77a90ba7b85bfb31329f8eab9d9540da2cf8a302128fb1241d7ea239a5469 \ - --hash=sha256:baab51dcc4f2aecabf4ed1e2f57bceab240987c8b03533f1cef90890e6502067 \ - --hash=sha256:ca8a2254ab88482936ce941485c1c20cdeaef0efa71a61dbad171ab6758ec998 \ - --hash=sha256:cb11464f480e6103c59d558a3875bd84eed6723f0921290325ebe97262ae1347 \ - --hash=sha256:ce8513aee0af9c159319692bfbf488b718d1793d764798c3d5cff827a09e25ef \ - --hash=sha256:cf151f97f5f381163912e8952eb5b3afe89dec9ed723d1561d59cabf1e219a35 \ - --hash=sha256:d144ad10eeca4c1d1ce930faa105899f86f5d99cecfe0d7224f3c4c76265c15e \ - --hash=sha256:d534d169673dd5e6e12fb57cc67664c2641361e1a0885545495e65a7b761b0f4 \ - --hash=sha256:d75061367a69808ab2e84c960e9dce54749bcc1e44ad3f85deee3a6c75b4ede9 \ - --hash=sha256:d84d04dec64cc4ed726d07c5d17b73c343c8ddcd6b59c7199c801d6bbb9d9ed1 \ - --hash=sha256:de411d2b030134b642c092e986d21aefb9d26a28bf5a18c47dd08ded411a3bc5 \ - --hash=sha256:e07fe0d7ae395897981d16be61f0db9791f482f03fee7d1851fe20ddb4f69c03 \ - --hash=sha256:ea8ccf95e4c7e20419b7827aa5b6da6f02720270686ac63bd3493a651830235c \ - --hash=sha256:f7025930039a011ed7d7e7ef95a1cb5f516e23c5a6ecc7947259b67bea8e06ca - # via - # tb-nightly - # tensorboard -huggingface-hub==0.10.1 \ - --hash=sha256:5c188d5b16bec4b78449f8681f9975ff9d321c16046cc29bcf0d7e464ff29276 \ - --hash=sha256:dc3b0e9a663fe6cad6a8522055c02a9d8673dbd527223288e2442bc028c253db - # via - # diffusers - # transformers -idna==2.10 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 - # via - # requests - # yarl -imageio==2.22.3 \ - --hash=sha256:63f007b7f2a082306e36922b3fd529a7aa305d2b78f46195bab8e22bbfe866e9 \ - --hash=sha256:a4b88f9f3d428b8c0ceeb7e297df8c346a642bb7e3111743eb85717d60b26f6f - # via - # scikit-image - # test-tube -imageio-ffmpeg==0.4.7 \ - --hash=sha256:27b48c32becae1658aa81c3a6b922538e4099edf5fbcbdb4ff5dbc84b8ffd3d3 \ - --hash=sha256:6514f1380daf42815bc8c83aad63f33e0b8b47133421ddafe7b410cd8dfbbea5 \ - --hash=sha256:6aba52ddf0a64442ffcb8d30ac6afb668186acec99ecbc7ae5bd171c4f500bbc \ - --hash=sha256:7a08838f97f363e37ca41821b864fd3fdc99ab1fe2421040c78eb5f56a9e723e \ - --hash=sha256:8e724d12dfe83e2a6eb39619e820243ca96c81c47c2648e66e05f7ee24e14312 \ - --hash=sha256:fc60686ef03c2d0f842901b206223c30051a6a120384458761390104470846fd - # via -r installer/requirements.in -importlib-metadata==5.0.0 \ - --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ - --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 - # via - # diffusers - # streamlit -itsdangerous==2.1.2 \ - --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ - --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a - # via flask -jinja2==3.1.2 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - # via - # altair - # flask - # pydeck -joblib==1.2.0 \ - --hash=sha256:091138ed78f800342968c523bdde947e7a305b8594b910a0fea2ab83c3c6d385 \ - --hash=sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018 - # via scikit-learn -jsonmerge==1.9.0 \ - --hash=sha256:a2d1f80021c5c1d70a49e31f862b5f068f9db066080d8561e80654de74a3584d - # via k-diffusion -jsonschema==4.17.0 \ - --hash=sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d \ - --hash=sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248 - # via - # altair - # jsonmerge -k-diffusion @ https://github.com/Birch-san/k-diffusion/archive/363386981fee88620709cf8f6f2eea167bd6cd74.zip \ - --hash=sha256:8eac5cdc08736e6d61908a1b2948f2b2f62691b01dc1aab978bddb3451af0d66 - # via -r installer/requirements.in -kiwisolver==1.4.4 \ - --hash=sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b \ - --hash=sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166 \ - --hash=sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c \ - --hash=sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c \ - --hash=sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0 \ - --hash=sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4 \ - --hash=sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9 \ - --hash=sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286 \ - --hash=sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767 \ - --hash=sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c \ - --hash=sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6 \ - --hash=sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b \ - --hash=sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004 \ - --hash=sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf \ - --hash=sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494 \ - --hash=sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac \ - --hash=sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626 \ - --hash=sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766 \ - --hash=sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514 \ - --hash=sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6 \ - --hash=sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f \ - --hash=sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d \ - --hash=sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191 \ - --hash=sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d \ - --hash=sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51 \ - --hash=sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f \ - --hash=sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8 \ - --hash=sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454 \ - --hash=sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb \ - --hash=sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da \ - --hash=sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8 \ - --hash=sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de \ - --hash=sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a \ - --hash=sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9 \ - --hash=sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008 \ - --hash=sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3 \ - --hash=sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32 \ - --hash=sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938 \ - --hash=sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1 \ - --hash=sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9 \ - --hash=sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d \ - --hash=sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824 \ - --hash=sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b \ - --hash=sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd \ - --hash=sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2 \ - --hash=sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5 \ - --hash=sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69 \ - --hash=sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3 \ - --hash=sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae \ - --hash=sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597 \ - --hash=sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e \ - --hash=sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955 \ - --hash=sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca \ - --hash=sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a \ - --hash=sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea \ - --hash=sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede \ - --hash=sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4 \ - --hash=sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6 \ - --hash=sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686 \ - --hash=sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408 \ - --hash=sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871 \ - --hash=sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29 \ - --hash=sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750 \ - --hash=sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897 \ - --hash=sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0 \ - --hash=sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2 \ - --hash=sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09 \ - --hash=sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c - # via matplotlib -kornia==0.6.8 \ - --hash=sha256:0985e02453c0ab4f030e8d22a3a7554dab312ffa8f8a54ec872190e6f0b58c56 \ - --hash=sha256:0d6d69330b4fd24da742337b8134da0ce01b4d7da66770db5498d58e8b4a0832 - # via k-diffusion -llvmlite==0.39.1 \ - --hash=sha256:03aee0ccd81735696474dc4f8b6be60774892a2929d6c05d093d17392c237f32 \ - --hash=sha256:1578f5000fdce513712e99543c50e93758a954297575610f48cb1fd71b27c08a \ - --hash=sha256:16f56eb1eec3cda3a5c526bc3f63594fc24e0c8d219375afeb336f289764c6c7 \ - --hash=sha256:1ec3d70b3e507515936e475d9811305f52d049281eaa6c8273448a61c9b5b7e2 \ - --hash=sha256:22d36591cd5d02038912321d9ab8e4668e53ae2211da5523f454e992b5e13c36 \ - --hash=sha256:3803f11ad5f6f6c3d2b545a303d68d9fabb1d50e06a8d6418e6fcd2d0df00959 \ - --hash=sha256:39dc2160aed36e989610fc403487f11b8764b6650017ff367e45384dff88ffbf \ - --hash=sha256:3fc14e757bc07a919221f0cbaacb512704ce5774d7fcada793f1996d6bc75f2a \ - --hash=sha256:4c6ebace910410daf0bebda09c1859504fc2f33d122e9a971c4c349c89cca630 \ - --hash=sha256:50aea09a2b933dab7c9df92361b1844ad3145bfb8dd2deb9cd8b8917d59306fb \ - --hash=sha256:60f8dd1e76f47b3dbdee4b38d9189f3e020d22a173c00f930b52131001d801f9 \ - --hash=sha256:62c0ea22e0b9dffb020601bb65cb11dd967a095a488be73f07d8867f4e327ca5 \ - --hash=sha256:6546bed4e02a1c3d53a22a0bced254b3b6894693318b16c16c8e43e29d6befb6 \ - --hash=sha256:6717c7a6e93c9d2c3d07c07113ec80ae24af45cde536b34363d4bcd9188091d9 \ - --hash=sha256:7ebf1eb9badc2a397d4f6a6c8717447c81ac011db00064a00408bc83c923c0e4 \ - --hash=sha256:9ffc84ade195abd4abcf0bd3b827b9140ae9ef90999429b9ea84d5df69c9058c \ - --hash=sha256:a3f331a323d0f0ada6b10d60182ef06c20a2f01be21699999d204c5750ffd0b4 \ - --hash=sha256:b1a0bbdb274fb683f993198775b957d29a6f07b45d184c571ef2a721ce4388cf \ - --hash=sha256:b43abd7c82e805261c425d50335be9a6c4f84264e34d6d6e475207300005d572 \ - --hash=sha256:c0f158e4708dda6367d21cf15afc58de4ebce979c7a1aa2f6b977aae737e2a54 \ - --hash=sha256:d0bfd18c324549c0fec2c5dc610fd024689de6f27c6cc67e4e24a07541d6e49b \ - --hash=sha256:ddab526c5a2c4ccb8c9ec4821fcea7606933dc53f510e2a6eebb45a418d3488a \ - --hash=sha256:e172c73fccf7d6db4bd6f7de963dedded900d1a5c6778733241d878ba613980e \ - --hash=sha256:e2c00ff204afa721b0bb9835b5bf1ba7fba210eefcec5552a9e05a63219ba0dc \ - --hash=sha256:e31f4b799d530255aaf0566e3da2df5bfc35d3cd9d6d5a3dcc251663656c27b1 \ - --hash=sha256:e4f212c018db951da3e1dc25c2651abc688221934739721f2dad5ff1dd5f90e7 \ - --hash=sha256:fa9b26939ae553bf30a9f5c4c754db0fb2d2677327f2511e674aa2f5df941789 \ - --hash=sha256:fb62fc7016b592435d3e3a8f680e3ea8897c3c9e62e6e6cc58011e7a4801439e - # via numba -lmdb==1.3.0 \ - --hash=sha256:008243762decf8f6c90430a9bced56290ebbcdb5e877d90e42343bb97033e494 \ - --hash=sha256:08f4b5129f4683802569b02581142e415c8dcc0ff07605983ec1b07804cecbad \ - --hash=sha256:17215a42a4b9814c383deabecb160581e4fb75d00198eef0e3cea54f230ffbea \ - --hash=sha256:18c69fabdaf04efaf246587739cc1062b3e57c6ef0743f5c418df89e5e7e7b9b \ - --hash=sha256:2cfa4aa9c67f8aee89b23005e98d1f3f32490b6b905fd1cb604b207cbd5755ab \ - --hash=sha256:2df38115dd9428a54d59ae7c712a4c7cce0d6b1d66056de4b1a8c38718066106 \ - --hash=sha256:394df860c3f93cfd92b6f4caba785f38208cc9614c18b3803f83a2cc1695042f \ - --hash=sha256:41318717ab5d15ad2d6d263d34fbf614a045210f64b25e59ce734bb2105e421f \ - --hash=sha256:4172fba19417d7b29409beca7d73c067b54e5d8ab1fb9b51d7b4c1445d20a167 \ - --hash=sha256:5a14aca2651c3af6f0d0a6b9168200eea0c8f2d27c40b01a442f33329a6e8dff \ - --hash=sha256:5ddd590e1c7fcb395931aa3782fb89b9db4550ab2d81d006ecd239e0d462bc41 \ - --hash=sha256:60a11efc21aaf009d06518996360eed346f6000bfc9de05114374230879f992e \ - --hash=sha256:6260a526e4ad85b1f374a5ba9475bf369fb07e7728ea6ec57226b02c40d1976b \ - --hash=sha256:62ab28e3593bdc318ea2f2fa1574e5fca3b6d1f264686d773ba54a637d4f563b \ - --hash=sha256:63cb73fe7ce9eb93d992d632c85a0476b4332670d9e6a2802b5062f603b7809f \ - --hash=sha256:65334eafa5d430b18d81ebd5362559a41483c362e1931f6e1b15bab2ecb7d75d \ - --hash=sha256:7da05d70fcc6561ac6b09e9fb1bf64b7ca294652c64c8a2889273970cee796b9 \ - --hash=sha256:abbc439cd9fe60ffd6197009087ea885ac150017dc85384093b1d376f83f0ec4 \ - --hash=sha256:c6adbd6f7f9048e97f31a069e652eb51020a81e80a0ce92dbb9810d21da2409a \ - --hash=sha256:d6a816954d212f40fd15007cd81ab7a6bebb77436d949a6a9ae04af57fc127f3 \ - --hash=sha256:d9103aa4908f0bca43c5911ca067d4e3d01f682dff0c0381a1239bd2bd757984 \ - --hash=sha256:df2724bad7820114a205472994091097d0fa65a3e5fff5a8e688d123fb8c6326 \ - --hash=sha256:e568ae0887ae196340947d9800136e90feaed6b86a261ef01f01b2ba65fc8106 \ - --hash=sha256:e6a704b3baced9182836c7f77b769f23856f3a8f62d0282b1bc1feaf81a86712 \ - --hash=sha256:eefb392f6b5cd43aada49258c5a79be11cb2c8cd3fc3e2d9319a1e0b9f906458 \ - --hash=sha256:f291e3f561f58dddf63a92a5a6a4b8af3a0920b6705d35e2f80e52e86ee238a2 \ - --hash=sha256:fa6439356e591d3249ab0e1778a6f8d8408e993f66dc911914c78208f5310309 - # via - # basicsr - # gfpgan -markdown==3.4.1 \ - --hash=sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186 \ - --hash=sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff - # via - # tb-nightly - # tensorboard -markupsafe==2.1.1 \ - --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ - --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ - --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ - --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ - --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ - --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ - --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ - --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ - --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ - --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ - --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ - --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ - --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ - --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ - --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ - --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ - --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ - --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ - --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ - --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ - --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ - --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ - --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ - --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ - --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ - --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ - --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ - --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ - --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ - --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ - --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ - --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ - --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ - --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ - --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ - --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ - --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ - --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ - --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ - --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 - # via - # jinja2 - # werkzeug -matplotlib==3.6.2 \ - --hash=sha256:0844523dfaaff566e39dbfa74e6f6dc42e92f7a365ce80929c5030b84caa563a \ - --hash=sha256:0eda9d1b43f265da91fb9ae10d6922b5a986e2234470a524e6b18f14095b20d2 \ - --hash=sha256:168093410b99f647ba61361b208f7b0d64dde1172b5b1796d765cd243cadb501 \ - --hash=sha256:1836f366272b1557a613f8265db220eb8dd883202bbbabe01bad5a4eadfd0c95 \ - --hash=sha256:19d61ee6414c44a04addbe33005ab1f87539d9f395e25afcbe9a3c50ce77c65c \ - --hash=sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267 \ - --hash=sha256:32d29c8c26362169c80c5718ce367e8c64f4dd068a424e7110df1dd2ed7bd428 \ - --hash=sha256:380d48c15ec41102a2b70858ab1dedfa33eb77b2c0982cb65a200ae67a48e9cb \ - --hash=sha256:3964934731fd7a289a91d315919cf757f293969a4244941ab10513d2351b4e83 \ - --hash=sha256:3cef89888a466228fc4e4b2954e740ce8e9afde7c4315fdd18caa1b8de58ca17 \ - --hash=sha256:4426c74761790bff46e3d906c14c7aab727543293eed5a924300a952e1a3a3c1 \ - --hash=sha256:5024b8ed83d7f8809982d095d8ab0b179bebc07616a9713f86d30cf4944acb73 \ - --hash=sha256:52c2bdd7cd0bf9d5ccdf9c1816568fd4ccd51a4d82419cc5480f548981b47dd0 \ - --hash=sha256:54fa9fe27f5466b86126ff38123261188bed568c1019e4716af01f97a12fe812 \ - --hash=sha256:5ba73aa3aca35d2981e0b31230d58abb7b5d7ca104e543ae49709208d8ce706a \ - --hash=sha256:5e16dcaecffd55b955aa5e2b8a804379789c15987e8ebd2f32f01398a81e975b \ - --hash=sha256:5ecfc6559132116dedfc482d0ad9df8a89dc5909eebffd22f3deb684132d002f \ - --hash=sha256:74153008bd24366cf099d1f1e83808d179d618c4e32edb0d489d526523a94d9f \ - --hash=sha256:78ec3c3412cf277e6252764ee4acbdbec6920cc87ad65862272aaa0e24381eee \ - --hash=sha256:795ad83940732b45d39b82571f87af0081c120feff2b12e748d96bb191169e33 \ - --hash=sha256:7f716b6af94dc1b6b97c46401774472f0867e44595990fe80a8ba390f7a0a028 \ - --hash=sha256:83dc89c5fd728fdb03b76f122f43b4dcee8c61f1489e232d9ad0f58020523e1c \ - --hash=sha256:8a0ae37576ed444fe853709bdceb2be4c7df6f7acae17b8378765bd28e61b3ae \ - --hash=sha256:8a8dbe2cb7f33ff54b16bb5c500673502a35f18ac1ed48625e997d40c922f9cc \ - --hash=sha256:8a9d899953c722b9afd7e88dbefd8fb276c686c3116a43c577cfabf636180558 \ - --hash=sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5 \ - --hash=sha256:9347cc6822f38db2b1d1ce992f375289670e595a2d1c15961aacbe0977407dfc \ - --hash=sha256:9f335e5625feb90e323d7e3868ec337f7b9ad88b5d633f876e3b778813021dab \ - --hash=sha256:b03fd10a1709d0101c054883b550f7c4c5e974f751e2680318759af005964990 \ - --hash=sha256:b0ca2c60d3966dfd6608f5f8c49b8a0fcf76de6654f2eda55fc6ef038d5a6f27 \ - --hash=sha256:b2604c6450f9dd2c42e223b1f5dca9643a23cfecc9fde4a94bb38e0d2693b136 \ - --hash=sha256:ca0e7a658fbafcddcaefaa07ba8dae9384be2343468a8e011061791588d839fa \ - --hash=sha256:d0e9ac04065a814d4cf2c6791a2ad563f739ae3ae830d716d54245c2b96fead6 \ - --hash=sha256:d50e8c1e571ee39b5dfbc295c11ad65988879f68009dd281a6e1edbc2ff6c18c \ - --hash=sha256:d840adcad7354be6f2ec28d0706528b0026e4c3934cc6566b84eac18633eab1b \ - --hash=sha256:e0bbee6c2a5bf2a0017a9b5e397babb88f230e6f07c3cdff4a4c4bc75ed7c617 \ - --hash=sha256:e5afe0a7ea0e3a7a257907060bee6724a6002b7eec55d0db16fd32409795f3e1 \ - --hash=sha256:e68be81cd8c22b029924b6d0ee814c337c0e706b8d88495a617319e5dd5441c3 \ - --hash=sha256:ec9be0f4826cdb3a3a517509dcc5f87f370251b76362051ab59e42b6b765f8c4 \ - --hash=sha256:f04f97797df35e442ed09f529ad1235d1f1c0f30878e2fe09a2676b71a8801e0 \ - --hash=sha256:f41e57ad63d336fe50d3a67bb8eaa26c09f6dda6a59f76777a99b8ccd8e26aec - # via - # clipseg - # filterpy -multidict==6.0.2 \ - --hash=sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60 \ - --hash=sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c \ - --hash=sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672 \ - --hash=sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51 \ - --hash=sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032 \ - --hash=sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2 \ - --hash=sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b \ - --hash=sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80 \ - --hash=sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88 \ - --hash=sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a \ - --hash=sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d \ - --hash=sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389 \ - --hash=sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c \ - --hash=sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9 \ - --hash=sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c \ - --hash=sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516 \ - --hash=sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b \ - --hash=sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43 \ - --hash=sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee \ - --hash=sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227 \ - --hash=sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d \ - --hash=sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae \ - --hash=sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7 \ - --hash=sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4 \ - --hash=sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9 \ - --hash=sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f \ - --hash=sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013 \ - --hash=sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9 \ - --hash=sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e \ - --hash=sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693 \ - --hash=sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a \ - --hash=sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15 \ - --hash=sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb \ - --hash=sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96 \ - --hash=sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87 \ - --hash=sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376 \ - --hash=sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658 \ - --hash=sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0 \ - --hash=sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071 \ - --hash=sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360 \ - --hash=sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc \ - --hash=sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3 \ - --hash=sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba \ - --hash=sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8 \ - --hash=sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9 \ - --hash=sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2 \ - --hash=sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3 \ - --hash=sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68 \ - --hash=sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8 \ - --hash=sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d \ - --hash=sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49 \ - --hash=sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608 \ - --hash=sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57 \ - --hash=sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86 \ - --hash=sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20 \ - --hash=sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293 \ - --hash=sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849 \ - --hash=sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937 \ - --hash=sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d - # via - # aiohttp - # yarl -networkx==2.8.8 \ - --hash=sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e \ - --hash=sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524 - # via scikit-image -numba==0.56.4 \ - --hash=sha256:0240f9026b015e336069329839208ebd70ec34ae5bfbf402e4fcc8e06197528e \ - --hash=sha256:03634579d10a6129181129de293dd6b5eaabee86881369d24d63f8fe352dd6cb \ - --hash=sha256:03fe94cd31e96185cce2fae005334a8cc712fc2ba7756e52dff8c9400718173f \ - --hash=sha256:0611e6d3eebe4cb903f1a836ffdb2bda8d18482bcd0a0dcc56e79e2aa3fefef5 \ - --hash=sha256:0da583c532cd72feefd8e551435747e0e0fbb3c0530357e6845fcc11e38d6aea \ - --hash=sha256:14dbbabf6ffcd96ee2ac827389afa59a70ffa9f089576500434c34abf9b054a4 \ - --hash=sha256:32d9fef412c81483d7efe0ceb6cf4d3310fde8b624a9cecca00f790573ac96ee \ - --hash=sha256:3a993349b90569518739009d8f4b523dfedd7e0049e6838c0e17435c3e70dcc4 \ - --hash=sha256:3cb1a07a082a61df80a468f232e452d818f5ae254b40c26390054e4e868556e0 \ - --hash=sha256:42f9e1be942b215df7e6cc9948cf9c15bb8170acc8286c063a9e57994ef82fd1 \ - --hash=sha256:4373da9757049db7c90591e9ec55a2e97b2b36ba7ae3bf9c956a513374077470 \ - --hash=sha256:4e08e203b163ace08bad500b0c16f6092b1eb34fd1fce4feaf31a67a3a5ecf3b \ - --hash=sha256:553da2ce74e8862e18a72a209ed3b6d2924403bdd0fb341fa891c6455545ba7c \ - --hash=sha256:720886b852a2d62619ae3900fe71f1852c62db4f287d0c275a60219e1643fc04 \ - --hash=sha256:85dbaed7a05ff96492b69a8900c5ba605551afb9b27774f7f10511095451137c \ - --hash=sha256:8a95ca9cc77ea4571081f6594e08bd272b66060634b8324e99cd1843020364f9 \ - --hash=sha256:91f021145a8081f881996818474ef737800bcc613ffb1e618a655725a0f9e246 \ - --hash=sha256:9f62672145f8669ec08762895fe85f4cf0ead08ce3164667f2b94b2f62ab23c3 \ - --hash=sha256:a12ef323c0f2101529d455cfde7f4135eaa147bad17afe10b48634f796d96abd \ - --hash=sha256:c602d015478b7958408d788ba00a50272649c5186ea8baa6cf71d4a1c761bba1 \ - --hash=sha256:c75e8a5f810ce80a0cfad6e74ee94f9fde9b40c81312949bf356b7304ef20740 \ - --hash=sha256:d0ae9270a7a5cc0ede63cd234b4ff1ce166c7a749b91dbbf45e0000c56d3eade \ - --hash=sha256:d69ad934e13c15684e7887100a8f5f0f61d7a8e57e0fd29d9993210089a5b531 \ - --hash=sha256:dbcc847bac2d225265d054993a7f910fda66e73d6662fe7156452cac0325b073 \ - --hash=sha256:e64d338b504c9394a4a34942df4627e1e6cb07396ee3b49fe7b8d6420aa5104f \ - --hash=sha256:f4cfc3a19d1e26448032049c79fc60331b104f694cf570a9e94f4e2c9d0932bb \ - --hash=sha256:fbfb45e7b297749029cb28694abf437a78695a100e7c2033983d69f0ba2698d4 \ - --hash=sha256:fcdf84ba3ed8124eb7234adfbb8792f311991cbf8aed1cad4b1b1a7ee08380c1 - # via facexlib -numpy==1.23.4 \ - --hash=sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8 \ - --hash=sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735 \ - --hash=sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd \ - --hash=sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810 \ - --hash=sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db \ - --hash=sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962 \ - --hash=sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79 \ - --hash=sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911 \ - --hash=sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d \ - --hash=sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488 \ - --hash=sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5 \ - --hash=sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0 \ - --hash=sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f \ - --hash=sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f \ - --hash=sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2 \ - --hash=sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0 \ - --hash=sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68 \ - --hash=sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3 \ - --hash=sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6 \ - --hash=sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71 \ - --hash=sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894 \ - --hash=sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f \ - --hash=sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329 \ - --hash=sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba \ - --hash=sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c \ - --hash=sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e \ - --hash=sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef \ - --hash=sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7 - # via - # accelerate - # albumentations - # altair - # basicsr - # clean-fid - # clipseg - # contourpy - # diffusers - # facexlib - # filterpy - # gfpgan - # imageio - # matplotlib - # numba - # opencv-python - # opencv-python-headless - # pandas - # pyarrow - # pydeck - # pypatchmatch - # pytorch-lightning - # pywavelets - # qudida - # realesrgan - # scikit-image - # scikit-learn - # scipy - # streamlit - # taming-transformers-rom1504 - # tb-nightly - # tensorboard - # test-tube - # tifffile - # torch-fidelity - # torchmetrics - # torchsde - # torchvision - # transformers -oauthlib==3.2.2 \ - --hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \ - --hash=sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918 - # via requests-oauthlib -omegaconf==2.2.3 \ - --hash=sha256:59ff9fba864ffbb5fb710b64e8a9ba37c68fa339a2e2bb4f1b648d6901552523 \ - --hash=sha256:d6f2cbf79a992899eb76c6cb1aedfcf0fe7456a8654382edd5ee0c1b199c0657 - # via taming-transformers-rom1504 -opencv-python==4.6.0.66 \ - --hash=sha256:0dc82a3d8630c099d2f3ac1b1aabee164e8188db54a786abb7a4e27eba309440 \ - --hash=sha256:5af8ba35a4fcb8913ffb86e92403e9a656a4bff4a645d196987468f0f8947875 \ - --hash=sha256:6e32af22e3202748bd233ed8f538741876191863882eba44e332d1a34993165b \ - --hash=sha256:c5bfae41ad4031e66bb10ec4a0a2ffd3e514d092652781e8b1ac98d1b59f1158 \ - --hash=sha256:dbdc84a9b4ea2cbae33861652d25093944b9959279200b7ae0badd32439f74de \ - --hash=sha256:e6e448b62afc95c5b58f97e87ef84699e6607fe5c58730a03301c52496005cae \ - --hash=sha256:f482e78de6e7b0b060ff994ffd859bddc3f7f382bb2019ef157b0ea8ca8712f5 - # via - # basicsr - # clipseg - # facexlib - # gfpgan - # realesrgan -opencv-python-headless==4.6.0.66 \ - --hash=sha256:21e70f8b0c04098cdf466d27184fe6c3820aaef944a22548db95099959c95889 \ - --hash=sha256:2c032c373e447c3fc2a670bca20e2918a1205a6e72854df60425fd3f82c78c32 \ - --hash=sha256:3bacd806cce1f1988e58f3d6f761538e0215d6621d316de94c009dc0acbd6ad3 \ - --hash=sha256:d5291d7e10aa2c19cab6fd86f0d61af8617290ecd2d7ffcb051e446868d04cc5 \ - --hash=sha256:e6c069bc963d7e8fcec21b3e33e594d35948badd63eccb2e80f88b0a12102c03 \ - --hash=sha256:eec6281054346103d6af93f173b7c6a206beb2663d3adc04aa3ddc66e85093df \ - --hash=sha256:ffbf26fcd697af996408440a93bc69c49c05a36845771f984156dfbeaa95d497 - # via - # albumentations - # qudida -packaging==21.3 \ - --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ - --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 - # via - # accelerate - # huggingface-hub - # kornia - # matplotlib - # pytorch-lightning - # scikit-image - # streamlit - # torchmetrics - # transformers -pandas==1.5.1 \ - --hash=sha256:04e51b01d5192499390c0015630975f57836cc95c7411415b499b599b05c0c96 \ - --hash=sha256:05c527c64ee02a47a24031c880ee0ded05af0623163494173204c5b72ddce658 \ - --hash=sha256:0a78e05ec09731c5b3bd7a9805927ea631fe6f6cb06f0e7c63191a9a778d52b4 \ - --hash=sha256:17da7035d9e6f9ea9cdc3a513161f8739b8f8489d31dc932bc5a29a27243f93d \ - --hash=sha256:249cec5f2a5b22096440bd85c33106b6102e0672204abd2d5c014106459804ee \ - --hash=sha256:2c25e5c16ee5c0feb6cf9d982b869eec94a22ddfda9aa2fbed00842cbb697624 \ - --hash=sha256:32e3d9f65606b3f6e76555bfd1d0b68d94aff0929d82010b791b6254bf5a4b96 \ - --hash=sha256:36aa1f8f680d7584e9b572c3203b20d22d697c31b71189322f16811d4ecfecd3 \ - --hash=sha256:5b0c970e2215572197b42f1cff58a908d734503ea54b326412c70d4692256391 \ - --hash=sha256:5cee0c74e93ed4f9d39007e439debcaadc519d7ea5c0afc3d590a3a7b2edf060 \ - --hash=sha256:669c8605dba6c798c1863157aefde959c1796671ffb342b80fcb80a4c0bc4c26 \ - --hash=sha256:66a1ad667b56e679e06ba73bb88c7309b3f48a4c279bd3afea29f65a766e9036 \ - --hash=sha256:683779e5728ac9138406c59a11e09cd98c7d2c12f0a5fc2b9c5eecdbb4a00075 \ - --hash=sha256:6bb391659a747cf4f181a227c3e64b6d197100d53da98dcd766cc158bdd9ec68 \ - --hash=sha256:81f0674fa50b38b6793cd84fae5d67f58f74c2d974d2cb4e476d26eee33343d0 \ - --hash=sha256:927e59c694e039c75d7023465d311277a1fc29ed7236b5746e9dddf180393113 \ - --hash=sha256:932d2d7d3cab44cfa275601c982f30c2d874722ef6396bb539e41e4dc4618ed4 \ - --hash=sha256:a52419d9ba5906db516109660b114faf791136c94c1a636ed6b29cbfff9187ee \ - --hash=sha256:b156a971bc451c68c9e1f97567c94fd44155f073e3bceb1b0d195fd98ed12048 \ - --hash=sha256:bcf1a82b770b8f8c1e495b19a20d8296f875a796c4fe6e91da5ef107f18c5ecb \ - --hash=sha256:cb2a9cf1150302d69bb99861c5cddc9c25aceacb0a4ef5299785d0f5389a3209 \ - --hash=sha256:d8c709f4700573deb2036d240d140934df7e852520f4a584b2a8d5443b71f54d \ - --hash=sha256:db45b94885000981522fb92349e6b76f5aee0924cc5315881239c7859883117d \ - --hash=sha256:ddf46b940ef815af4e542697eaf071f0531449407a7607dd731bf23d156e20a7 \ - --hash=sha256:e675f8fe9aa6c418dc8d3aac0087b5294c1a4527f1eacf9fe5ea671685285454 \ - --hash=sha256:eb7e8cf2cf11a2580088009b43de84cabbf6f5dae94ceb489f28dba01a17cb77 \ - --hash=sha256:f340331a3f411910adfb4bbe46c2ed5872d9e473a783d7f14ecf49bc0869c594 - # via - # altair - # streamlit - # test-tube -pathtools==0.1.2 \ - --hash=sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0 - # via wandb -picklescan==0.0.5 \ - --hash=sha256:368cf1b9a075bc1b6460ad82b694f260532b836c82f99d13846cd36e1bbe7f9a \ - --hash=sha256:57153eca04d5df5009f2cdd595aef261b8a6f27e03046a1c84f672aa6869c592 - # via -r installer/requirements.in -pillow==9.3.0 \ - --hash=sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040 \ - --hash=sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8 \ - --hash=sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65 \ - --hash=sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2 \ - --hash=sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627 \ - --hash=sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07 \ - --hash=sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef \ - --hash=sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535 \ - --hash=sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c \ - --hash=sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc \ - --hash=sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3 \ - --hash=sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1 \ - --hash=sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c \ - --hash=sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa \ - --hash=sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32 \ - --hash=sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502 \ - --hash=sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4 \ - --hash=sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f \ - --hash=sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812 \ - --hash=sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636 \ - --hash=sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20 \ - --hash=sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c \ - --hash=sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91 \ - --hash=sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe \ - --hash=sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b \ - --hash=sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad \ - --hash=sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9 \ - --hash=sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72 \ - --hash=sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4 \ - --hash=sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de \ - --hash=sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29 \ - --hash=sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee \ - --hash=sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c \ - --hash=sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7 \ - --hash=sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11 \ - --hash=sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c \ - --hash=sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c \ - --hash=sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448 \ - --hash=sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b \ - --hash=sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20 \ - --hash=sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228 \ - --hash=sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd \ - --hash=sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699 \ - --hash=sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b \ - --hash=sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2 \ - --hash=sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4 \ - --hash=sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c \ - --hash=sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f \ - --hash=sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2 \ - --hash=sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c \ - --hash=sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3 \ - --hash=sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193 \ - --hash=sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48 \ - --hash=sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02 \ - --hash=sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8 \ - --hash=sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e \ - --hash=sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f \ - --hash=sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b \ - --hash=sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74 \ - --hash=sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb \ - --hash=sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0 - # via - # basicsr - # clean-fid - # diffusers - # facexlib - # imageio - # k-diffusion - # matplotlib - # pypatchmatch - # realesrgan - # scikit-image - # streamlit - # torch-fidelity - # torchvision -promise==2.3 \ - --hash=sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0 - # via wandb -protobuf==3.19.6 \ - --hash=sha256:010be24d5a44be7b0613750ab40bc8b8cedc796db468eae6c779b395f50d1fa1 \ - --hash=sha256:0469bc66160180165e4e29de7f445e57a34ab68f49357392c5b2f54c656ab25e \ - --hash=sha256:0c0714b025ec057b5a7600cb66ce7c693815f897cfda6d6efb58201c472e3437 \ - --hash=sha256:11478547958c2dfea921920617eb457bc26867b0d1aa065ab05f35080c5d9eb6 \ - --hash=sha256:14082457dc02be946f60b15aad35e9f5c69e738f80ebbc0900a19bc83734a5a4 \ - --hash=sha256:2b2d2913bcda0e0ec9a784d194bc490f5dc3d9d71d322d070b11a0ade32ff6ba \ - --hash=sha256:30a15015d86b9c3b8d6bf78d5b8c7749f2512c29f168ca259c9d7727604d0e39 \ - --hash=sha256:30f5370d50295b246eaa0296533403961f7e64b03ea12265d6dfce3a391d8992 \ - --hash=sha256:347b393d4dd06fb93a77620781e11c058b3b0a5289262f094379ada2920a3730 \ - --hash=sha256:4bc98de3cdccfb5cd769620d5785b92c662b6bfad03a202b83799b6ed3fa1fa7 \ - --hash=sha256:5057c64052a1f1dd7d4450e9aac25af6bf36cfbfb3a1cd89d16393a036c49157 \ - --hash=sha256:559670e006e3173308c9254d63facb2c03865818f22204037ab76f7a0ff70b5f \ - --hash=sha256:5a0d7539a1b1fb7e76bf5faa0b44b30f812758e989e59c40f77a7dab320e79b9 \ - --hash=sha256:5f5540d57a43042389e87661c6eaa50f47c19c6176e8cf1c4f287aeefeccb5c4 \ - --hash=sha256:7a552af4dc34793803f4e735aabe97ffc45962dfd3a237bdde242bff5a3de684 \ - --hash=sha256:84a04134866861b11556a82dd91ea6daf1f4925746b992f277b84013a7cc1229 \ - --hash=sha256:878b4cd080a21ddda6ac6d1e163403ec6eea2e206cf225982ae04567d39be7b0 \ - --hash=sha256:90b0d02163c4e67279ddb6dc25e063db0130fc299aefabb5d481053509fae5c8 \ - --hash=sha256:91d5f1e139ff92c37e0ff07f391101df77e55ebb97f46bbc1535298d72019462 \ - --hash=sha256:a8ce5ae0de28b51dff886fb922012dad885e66176663950cb2344c0439ecb473 \ - --hash=sha256:aa3b82ca1f24ab5326dcf4ea00fcbda703e986b22f3d27541654f749564d778b \ - --hash=sha256:bb6776bd18f01ffe9920e78e03a8676530a5d6c5911934c6a1ac6eb78973ecb6 \ - --hash=sha256:bbf5cea5048272e1c60d235c7bd12ce1b14b8a16e76917f371c718bd3005f045 \ - --hash=sha256:c0ccd3f940fe7f3b35a261b1dd1b4fc850c8fde9f74207015431f174be5976b3 \ - --hash=sha256:d0b635cefebd7a8a0f92020562dead912f81f401af7e71f16bf9506ff3bdbb38 - # via - # streamlit - # tb-nightly - # tensorboard - # wandb -psutil==5.9.3 \ - --hash=sha256:07d880053c6461c9b89cd5d4808f3b8336665fa3acdefd6777662c5ed73a851a \ - --hash=sha256:12500d761ac091f2426567f19f95fd3f15a197d96befb44a5c1e3cbe6db5752c \ - --hash=sha256:1b540599481c73408f6b392cdffef5b01e8ff7a2ac8caae0a91b8222e88e8f1e \ - --hash=sha256:35feafe232d1aaf35d51bd42790cbccb882456f9f18cdc411532902370d660df \ - --hash=sha256:3a7826e68b0cf4ce2c1ee385d64eab7d70e3133171376cac53d7c1790357ec8f \ - --hash=sha256:46907fa62acaac364fff0b8a9da7b360265d217e4fdeaca0a2397a6883dffba2 \ - --hash=sha256:4bd4854f0c83aa84a5a40d3b5d0eb1f3c128f4146371e03baed4589fe4f3c931 \ - --hash=sha256:538fcf6ae856b5e12d13d7da25ad67f02113c96f5989e6ad44422cb5994ca7fc \ - --hash=sha256:547ebb02031fdada635452250ff39942db8310b5c4a8102dfe9384ee5791e650 \ - --hash=sha256:5e8b50241dd3c2ed498507f87a6602825073c07f3b7e9560c58411c14fe1e1c9 \ - --hash=sha256:5fa88e3d5d0b480602553d362c4b33a63e0c40bfea7312a7bf78799e01e0810b \ - --hash=sha256:68fa227c32240c52982cb931801c5707a7f96dd8927f9102d6c7771ea1ff5698 \ - --hash=sha256:6ced1ad823ecfa7d3ce26fe8aa4996e2e53fb49b7fed8ad81c80958501ec0619 \ - --hash=sha256:71b1206e7909792d16933a0d2c1c7f04ae196186c51ba8567abae1d041f06dcb \ - --hash=sha256:767ef4fa33acda16703725c0473a91e1832d296c37c63896c7153ba81698f1ab \ - --hash=sha256:7ccfcdfea4fc4b0a02ca2c31de7fcd186beb9cff8207800e14ab66f79c773af6 \ - --hash=sha256:7e4939ff75149b67aef77980409f156f0082fa36accc475d45c705bb00c6c16a \ - --hash=sha256:828c9dc9478b34ab96be75c81942d8df0c2bb49edbb481f597314d92b6441d89 \ - --hash=sha256:8a4e07611997acf178ad13b842377e3d8e9d0a5bac43ece9bfc22a96735d9a4f \ - --hash=sha256:941a6c2c591da455d760121b44097781bc970be40e0e43081b9139da485ad5b7 \ - --hash=sha256:9a4af6ed1094f867834f5f07acd1250605a0874169a5fcadbcec864aec2496a6 \ - --hash=sha256:9ec296f565191f89c48f33d9544d8d82b0d2af7dd7d2d4e6319f27a818f8d1cc \ - --hash=sha256:9ec95df684583b5596c82bb380c53a603bb051cf019d5c849c47e117c5064395 \ - --hash=sha256:a04a1836894c8279e5e0a0127c0db8e198ca133d28be8a2a72b4db16f6cf99c1 \ - --hash=sha256:a3d81165b8474087bb90ec4f333a638ccfd1d69d34a9b4a1a7eaac06648f9fbe \ - --hash=sha256:b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71 \ - --hash=sha256:ba38cf9984d5462b506e239cf4bc24e84ead4b1d71a3be35e66dad0d13ded7c1 \ - --hash=sha256:beb57d8a1ca0ae0eb3d08ccaceb77e1a6d93606f0e1754f0d60a6ebd5c288837 \ - --hash=sha256:d266cd05bd4a95ca1c2b9b5aac50d249cf7c94a542f47e0b22928ddf8b80d1ef \ - --hash=sha256:d8c3cc6bb76492133474e130a12351a325336c01c96a24aae731abf5a47fe088 \ - --hash=sha256:db8e62016add2235cc87fb7ea000ede9e4ca0aa1f221b40cef049d02d5d2593d \ - --hash=sha256:e7507f6c7b0262d3e7b0eeda15045bf5881f4ada70473b87bc7b7c93b992a7d7 \ - --hash=sha256:ed15edb14f52925869250b1375f0ff58ca5c4fa8adefe4883cfb0737d32f5c02 \ - --hash=sha256:f57d63a2b5beaf797b87024d018772439f9d3103a395627b77d17a8d72009543 \ - --hash=sha256:fa5e32c7d9b60b2528108ade2929b115167fe98d59f89555574715054f50fa31 \ - --hash=sha256:fe79b4ad4836e3da6c4650cb85a663b3a51aef22e1a829c384e18fae87e5e727 - # via - # accelerate - # wandb -pyarrow==10.0.0 \ - --hash=sha256:10e031794d019425d34406edffe7e32157359e9455f9edb97a1732f8dabf802f \ - --hash=sha256:25f51dca780fc22cfd7ac30f6bdfe70eb99145aee9acfda987f2c49955d66ed9 \ - --hash=sha256:2d326a9d47ac237d81b8c4337e9d30a0b361835b536fc7ea53991455ce761fbd \ - --hash=sha256:3d2694f08c8d4482d14e3798ff036dbd81ae6b1c47948f52515e1aa90fbec3f0 \ - --hash=sha256:4051664d354b14939b5da35cfa77821ade594bc1cf56dd2032b3068c96697d74 \ - --hash=sha256:511735040b83f2993f78d7fb615e7b88253d75f41500e87e587c40156ff88120 \ - --hash=sha256:65d4a312f3ced318423704355acaccc7f7bdfe242472e59bdd54aa0f8837adf8 \ - --hash=sha256:68ccb82c04c0f7abf7a95541d5e9d9d94290fc66a2d36d3f6ea0777f40c15654 \ - --hash=sha256:69b8a1fd99201178799b02f18498633847109b701856ec762f314352a431b7d0 \ - --hash=sha256:758284e1ebd3f2a9abb30544bfec28d151a398bb7c0f2578cbca5ee5b000364a \ - --hash=sha256:7be7f42f713068293308c989a4a3a2de03b70199bdbe753901c6595ff8640c64 \ - --hash=sha256:7ce026274cd5d9934cd3694e89edecde4e036018bbc6cb735fd33b9e967e7d47 \ - --hash=sha256:7e6b837cc44cd62a0e280c8fc4de94ebce503d6d1190e6e94157ab49a8bea67b \ - --hash=sha256:b153b05765393557716e3729cf988442b3ae4f5567364ded40d58c07feed27c2 \ - --hash=sha256:b3e3148468d3eed3779d68241f1d13ed4ee7cca4c6dbc7c07e5062b93ad4da33 \ - --hash=sha256:b45f969ed924282e9d4ede38f3430630d809c36dbff65452cabce03141943d28 \ - --hash=sha256:b9f63ceb8346aac0bcb487fafe9faca642ad448ca649fcf66a027c6e120cbc12 \ - --hash=sha256:c79300e1a3e23f2bf4defcf0d70ff5ea25ef6ebf6f121d8670ee14bb662bb7ca \ - --hash=sha256:d45a59e2f47826544c0ca70bc0f7ed8ffa5ad23f93b0458230c7e983bcad1acf \ - --hash=sha256:e4c6da9f9e1ff96781ee1478f7cc0860e66c23584887b8e297c4b9905c3c9066 \ - --hash=sha256:f329951d56b3b943c353f7b27c894e02367a7efbb9fef7979c6b24e02dbfcf55 \ - --hash=sha256:f76157d9579571c865860e5fd004537c03e21139db76692d96fd8a186adab1f2 - # via streamlit -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 - # via google-auth -pydeck==0.8.0 \ - --hash=sha256:07edde833f7cfcef6749124351195aa7dcd24663d4909fd7898dbd0b6fbc01ec \ - --hash=sha256:a8fa7757c6f24bba033af39db3147cb020eef44012ba7e60d954de187f9ed4d5 - # via streamlit -pydeprecate==0.3.2 \ - --hash=sha256:d481116cc5d7f6c473e7c4be820efdd9b90a16b594b350276e9e66a6cb5bdd29 \ - --hash=sha256:ed86b68ed837e6465245904a3de2f59bf9eef78ac7a2502ee280533d04802457 - # via pytorch-lightning -pygments==2.13.0 \ - --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ - --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 - # via rich -pympler==1.0.1 \ - --hash=sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa \ - --hash=sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d - # via streamlit -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc - # via - # matplotlib - # packaging -pypatchmatch @ https://github.com/invoke-ai/PyPatchMatch/archive/129863937a8ab37f6bbcec327c994c0f932abdbc.zip \ - --hash=sha256:4ad6ec95379e7d122d494ff76633cc7cf9b71330d5efda147fceba81e3dc6cd2 - # via -r installer/requirements.in -pyreadline3==3.4.1 \ - --hash=sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae \ - --hash=sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb - # via -r installer/requirements.in -pyrsistent==0.19.2 \ - --hash=sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed \ - --hash=sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb \ - --hash=sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a \ - --hash=sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95 \ - --hash=sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712 \ - --hash=sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73 \ - --hash=sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41 \ - --hash=sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b \ - --hash=sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78 \ - --hash=sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab \ - --hash=sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308 \ - --hash=sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425 \ - --hash=sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2 \ - --hash=sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e \ - --hash=sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6 \ - --hash=sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2 \ - --hash=sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a \ - --hash=sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291 \ - --hash=sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584 \ - --hash=sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a \ - --hash=sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0 \ - --hash=sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770 - # via jsonschema -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 - # via - # matplotlib - # pandas - # streamlit -python-engineio==4.3.4 \ - --hash=sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c \ - --hash=sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae - # via python-socketio -python-socketio==5.7.2 \ - --hash=sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097 \ - --hash=sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3 - # via flask-socketio -pytorch-lightning==1.7.7 \ - --hash=sha256:27c2dd01a18db2415168e3fa3775ccb5a1fa1e2961a50439ad9365507fe9d4ae \ - --hash=sha256:4438b8284d7f7fdb06cf3566a7b5b6f102ac8971cf7bb6d3f1b1de64628241f3 - # via taming-transformers-rom1504 -pytz==2022.6 \ - --hash=sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427 \ - --hash=sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2 - # via pandas -pytz-deprecation-shim==0.1.0.post0 \ - --hash=sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6 \ - --hash=sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d - # via tzlocal -pywavelets==1.4.1 \ - --hash=sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b \ - --hash=sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4 \ - --hash=sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4 \ - --hash=sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6 \ - --hash=sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de \ - --hash=sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c \ - --hash=sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa \ - --hash=sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93 \ - --hash=sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875 \ - --hash=sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd \ - --hash=sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356 \ - --hash=sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1 \ - --hash=sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c \ - --hash=sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2 \ - --hash=sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784 \ - --hash=sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4 \ - --hash=sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b \ - --hash=sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc \ - --hash=sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966 \ - --hash=sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e \ - --hash=sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426 \ - --hash=sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c \ - --hash=sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202 \ - --hash=sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc \ - --hash=sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd - # via scikit-image -pyyaml==6.0 \ - --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \ - --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \ - --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \ - --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \ - --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \ - --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \ - --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \ - --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \ - --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \ - --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \ - --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \ - --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \ - --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \ - --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \ - --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \ - --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \ - --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \ - --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \ - --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \ - --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \ - --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \ - --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \ - --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \ - --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \ - --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \ - --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \ - --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \ - --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \ - --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \ - --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \ - --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \ - --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \ - --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \ - --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \ - --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \ - --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \ - --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \ - --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \ - --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \ - --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5 - # via - # accelerate - # albumentations - # basicsr - # gfpgan - # huggingface-hub - # omegaconf - # pytorch-lightning - # transformers - # wandb -qudida==0.0.4 \ - --hash=sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6 \ - --hash=sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8 - # via albumentations -realesrgan==0.3.0 \ - --hash=sha256:0d36da96ab9f447071606e91f502ccdfb08f80cc82ee4f8caf720c7745ccec7e \ - --hash=sha256:59336c16c30dd5130eff350dd27424acb9b7281d18a6810130e265606c9a6088 - # via -r installer/requirements.in -regex==2022.10.31 \ - --hash=sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad \ - --hash=sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4 \ - --hash=sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd \ - --hash=sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc \ - --hash=sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d \ - --hash=sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066 \ - --hash=sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec \ - --hash=sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9 \ - --hash=sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e \ - --hash=sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8 \ - --hash=sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e \ - --hash=sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783 \ - --hash=sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6 \ - --hash=sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1 \ - --hash=sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c \ - --hash=sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4 \ - --hash=sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1 \ - --hash=sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1 \ - --hash=sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7 \ - --hash=sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8 \ - --hash=sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe \ - --hash=sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d \ - --hash=sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b \ - --hash=sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8 \ - --hash=sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c \ - --hash=sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af \ - --hash=sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49 \ - --hash=sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714 \ - --hash=sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542 \ - --hash=sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318 \ - --hash=sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e \ - --hash=sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5 \ - --hash=sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc \ - --hash=sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144 \ - --hash=sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453 \ - --hash=sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5 \ - --hash=sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61 \ - --hash=sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11 \ - --hash=sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a \ - --hash=sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54 \ - --hash=sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73 \ - --hash=sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc \ - --hash=sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347 \ - --hash=sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c \ - --hash=sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66 \ - --hash=sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c \ - --hash=sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93 \ - --hash=sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443 \ - --hash=sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc \ - --hash=sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1 \ - --hash=sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892 \ - --hash=sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8 \ - --hash=sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001 \ - --hash=sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa \ - --hash=sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90 \ - --hash=sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c \ - --hash=sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0 \ - --hash=sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692 \ - --hash=sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4 \ - --hash=sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5 \ - --hash=sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690 \ - --hash=sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83 \ - --hash=sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66 \ - --hash=sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f \ - --hash=sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f \ - --hash=sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4 \ - --hash=sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee \ - --hash=sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81 \ - --hash=sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95 \ - --hash=sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9 \ - --hash=sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff \ - --hash=sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e \ - --hash=sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5 \ - --hash=sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6 \ - --hash=sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7 \ - --hash=sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1 \ - --hash=sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394 \ - --hash=sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6 \ - --hash=sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742 \ - --hash=sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57 \ - --hash=sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b \ - --hash=sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7 \ - --hash=sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b \ - --hash=sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244 \ - --hash=sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af \ - --hash=sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185 \ - --hash=sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8 \ - --hash=sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5 - # via - # clip - # diffusers - # transformers -requests==2.25.1 \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e - # via - # basicsr - # clean-fid - # diffusers - # fsspec - # huggingface-hub - # requests-oauthlib - # streamlit - # tb-nightly - # tensorboard - # torchvision - # transformers - # wandb -requests-oauthlib==1.3.1 \ - --hash=sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5 \ - --hash=sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a - # via google-auth-oauthlib -resize-right==0.0.2 \ - --hash=sha256:78351ca3eda0872208fcbc90861b45de559f90fb4027ce41825fdeb9b995005c \ - --hash=sha256:7dc35b72ce4012b77f7cc9049835163793ab98a58ab8893610fb119fe59af520 - # via k-diffusion -rich==12.6.0 \ - --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ - --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 - # via streamlit -rsa==4.9 \ - --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ - --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 - # via google-auth -scikit-image==0.19.3 \ - --hash=sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099 \ - --hash=sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d \ - --hash=sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790 \ - --hash=sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450 \ - --hash=sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef \ - --hash=sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245 \ - --hash=sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919 \ - --hash=sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6 \ - --hash=sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8 \ - --hash=sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d \ - --hash=sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19 \ - --hash=sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2 \ - --hash=sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92 \ - --hash=sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced \ - --hash=sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e \ - --hash=sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732 \ - --hash=sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34 \ - --hash=sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83 \ - --hash=sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f \ - --hash=sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9 \ - --hash=sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe \ - --hash=sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7 \ - --hash=sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b \ - --hash=sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6 \ - --hash=sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc \ - --hash=sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5 - # via - # albumentations - # basicsr - # k-diffusion -scikit-learn==1.1.3 \ - --hash=sha256:23fb9e74b813cc2528b5167d82ed08950b11106ccf50297161875e45152fb311 \ - --hash=sha256:250da993701da88bf475e7c5746abf1285ea0ae47e4d0917cd13afd6600bb162 \ - --hash=sha256:28b2bd6a1419acd522ff45d282c8ba23dbccb5338802ab0ee12baa4ade0aba4c \ - --hash=sha256:2ee2c649f2231b68511aabb0dc827edd8936aad682acc6263c34aed11bc95dac \ - --hash=sha256:30e27721adc308e8fd9f419f43068e43490005f911edf4476a9e585059fa8a83 \ - --hash=sha256:38814f66285318f2e241305cca545eaa9b4126c65aa5dd78c69371f235f78e2b \ - --hash=sha256:4d3a19166d4e1cdfcab975c68f471e046ce01e74c42a9a33fa89a14c2fcedf60 \ - --hash=sha256:5699cded6c0685426433c7e5afe0fecad80ec831ec7fa264940e50c796775cc5 \ - --hash=sha256:6785b8a3093329bf90ac01801be5525551728ae73edb11baa175df660820add4 \ - --hash=sha256:6d1c1394e38a3319ace620381f6f23cc807d8780e9915c152449a86fc8f1db21 \ - --hash=sha256:701181792a28c82fecae12adb5d15d0ecf57bffab7cf4bdbb52c7b3fd428d540 \ - --hash=sha256:748f2bd632d6993e8918d43f1a26c380aeda4e122a88840d4c3a9af99d4239fe \ - --hash=sha256:8e9dd76c7274055d1acf4526b8efb16a3531c26dcda714a0c16da99bf9d41900 \ - --hash=sha256:bef51978a51ec19977700fe7b86aecea49c825884f3811756b74a3b152bb4e35 \ - --hash=sha256:cd55c6fbef7608dbce1f22baf289dfcc6eb323247daa3c3542f73d389c724786 \ - --hash=sha256:da5a2e95fef9805b1750e4abda4e834bf8835d26fc709a391543b53feee7bd0e \ - --hash=sha256:ee47f68d973cee7009f06edb956f2f5588a0f230f24a2a70175fd0ecf36e2653 \ - --hash=sha256:f4931f2a6c06e02c6c17a05f8ae397e2545965bc7a0a6cb38c8cd7d4fba8624d \ - --hash=sha256:f5644663987ee221f5d1f47a593271b966c271c236fe05634e6bdc06041b5a2b \ - --hash=sha256:f5d4231af7199531e77da1b78a4cc6b3d960a00b1ec672578ac818aae2b9c35d \ - --hash=sha256:fd3ee69d36d42a7dcbb17e355a5653af5fd241a7dfd9133080b3dde8d9e2aafb - # via qudida -scipy==1.9.3 \ - --hash=sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31 \ - --hash=sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108 \ - --hash=sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0 \ - --hash=sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b \ - --hash=sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e \ - --hash=sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e \ - --hash=sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5 \ - --hash=sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840 \ - --hash=sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58 \ - --hash=sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523 \ - --hash=sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd \ - --hash=sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab \ - --hash=sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c \ - --hash=sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb \ - --hash=sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096 \ - --hash=sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0 \ - --hash=sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc \ - --hash=sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9 \ - --hash=sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c \ - --hash=sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95 \ - --hash=sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027 - # via - # albumentations - # basicsr - # clean-fid - # clipseg - # facexlib - # filterpy - # gfpgan - # k-diffusion - # scikit-image - # scikit-learn - # torch-fidelity - # torchdiffeq - # torchsde -semver==2.13.0 \ - --hash=sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4 \ - --hash=sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f - # via streamlit -send2trash==1.8.0 \ - --hash=sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d \ - --hash=sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08 - # via -r installer/requirements.in -sentry-sdk==1.10.1 \ - --hash=sha256:06c0fa9ccfdc80d7e3b5d2021978d6eb9351fa49db9b5847cf4d1f2a473414ad \ - --hash=sha256:105faf7bd7b7fa25653404619ee261527266b14103fe1389e0ce077bd23a9691 - # via wandb -setproctitle==1.3.2 \ - --hash=sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b \ - --hash=sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9 \ - --hash=sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31 \ - --hash=sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83 \ - --hash=sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f \ - --hash=sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca \ - --hash=sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494 \ - --hash=sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe \ - --hash=sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172 \ - --hash=sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084 \ - --hash=sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4 \ - --hash=sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1 \ - --hash=sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c \ - --hash=sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973 \ - --hash=sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c \ - --hash=sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202 \ - --hash=sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5 \ - --hash=sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65 \ - --hash=sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18 \ - --hash=sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13 \ - --hash=sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005 \ - --hash=sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02 \ - --hash=sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7 \ - --hash=sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665 \ - --hash=sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f \ - --hash=sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1 \ - --hash=sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989 \ - --hash=sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827 \ - --hash=sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42 \ - --hash=sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927 \ - --hash=sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122 \ - --hash=sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9 \ - --hash=sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f \ - --hash=sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d \ - --hash=sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98 \ - --hash=sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796 \ - --hash=sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb \ - --hash=sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd \ - --hash=sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe \ - --hash=sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806 \ - --hash=sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484 \ - --hash=sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e \ - --hash=sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575 \ - --hash=sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76 \ - --hash=sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246 \ - --hash=sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb \ - --hash=sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099 \ - --hash=sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c \ - --hash=sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258 \ - --hash=sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865 \ - --hash=sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b \ - --hash=sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d \ - --hash=sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10 \ - --hash=sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f \ - --hash=sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94 \ - --hash=sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6 \ - --hash=sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963 \ - --hash=sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4 \ - --hash=sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8 \ - --hash=sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761 - # via wandb -shortuuid==1.0.9 \ - --hash=sha256:459f12fa1acc34ff213b1371467c0325169645a31ed989e268872339af7563d5 \ - --hash=sha256:b2bb9eb7773170e253bb7ba25971023acb473517a8b76803d9618668cb1dd46f - # via wandb -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # docker-pycreds - # eventlet - # flask-cors - # google-auth - # grpcio - # promise - # python-dateutil - # validators - # wandb -smmap==5.0.0 \ - --hash=sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94 \ - --hash=sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936 - # via gitdb -streamlit==1.14.0 \ - --hash=sha256:62556d873567e1b3427bcd118a57ee6946619f363bd6bba38df2d1f8225ecba0 \ - --hash=sha256:e078b8143d150ba721bdb9194218e311c5fe1d6d4156473a2dea6cc848a6c9fc - # via -r installer/requirements.in -taming-transformers-rom1504==0.0.6 \ - --hash=sha256:051b5804c58caa247bcd51d17ddb525b4d5f892a29d42dc460f40e3e9e34e5d8 \ - --hash=sha256:73fe5fc1108accee4236ee6976e0987ab236afad0af06cb9f037641a908d2c32 - # via -r installer/requirements.in -tb-nightly==2.11.0a20221106 \ - --hash=sha256:8940457ee42db92f01da8bcdbbea1a476735eda559dde5976f5728919960af4a - # via - # basicsr - # gfpgan -tensorboard==2.10.1 \ - --hash=sha256:fb9222c1750e2fa35ef170d998a1e229f626eeced3004494a8849c88c15d8c1c - # via - # pytorch-lightning - # test-tube -tensorboard-data-server==0.6.1 \ - --hash=sha256:809fe9887682d35c1f7d1f54f0f40f98bb1f771b14265b453ca051e2ce58fca7 \ - --hash=sha256:d8237580755e58eff68d1f3abefb5b1e39ae5c8b127cc40920f9c4fb33f4b98a \ - --hash=sha256:fa8cef9be4fcae2f2363c88176638baf2da19c5ec90addb49b1cde05c95c88ee - # via - # tb-nightly - # tensorboard -tensorboard-plugin-wit==1.8.1 \ - --hash=sha256:ff26bdd583d155aa951ee3b152b3d0cffae8005dc697f72b44a8e8c2a77a8cbe - # via - # tb-nightly - # tensorboard -test-tube==0.7.5 \ - --hash=sha256:1379c33eb8cde3e9b36610f87da0f16c2e06496b1cfebac473df4e7be2faa124 - # via -r installer/requirements.in -threadpoolctl==3.1.0 \ - --hash=sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b \ - --hash=sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380 - # via scikit-learn -tifffile==2022.10.10 \ - --hash=sha256:50b61ba943b866d191295bc38a00191c9fdab23ece063544c7f1a264e3f6aa8e \ - --hash=sha256:87f3aee8a0d06b74655269a105de75c1958a24653e1930d523eb516100043503 - # via scikit-image -tokenizers==0.13.1 \ - --hash=sha256:0a3412830ad66a643723d6b0fc3202e64e9e299bd9c9eda04b2914b5b1e0ddb0 \ - --hash=sha256:126bcb18a77cf65961ece04f54bd10ef3713412195e543d9d3eda2f0e4fd677c \ - --hash=sha256:16434c61c5eb72f6692b9bc52052b07ca92d3eba9dd72a1bc371890e1bdc3f07 \ - --hash=sha256:1d4acfdb6e7ef974677bb8445462db7fed240185fdc0f5b061db357d4ef8d85d \ - --hash=sha256:3333d1cee5c8f47c96362ea0abc1f81c77c9b92c6c3d11cbf1d01985f0d5cf1d \ - --hash=sha256:3acf3cae4c4739fc9ec49fa0e6cce224c1aa0fabc8f8d1358fd7de5c7d49cdca \ - --hash=sha256:3ba43b3f6f6b41c97c1d041785342cd72ba142999f6c4605d628e8e937398f20 \ - --hash=sha256:3c69a8389fd88bc32115e99db70f63bef577ba5c54f40a632580038a49612856 \ - --hash=sha256:3de653a551cc616a442a123da21706cb3a3163cf6919973f978f0921eee1bdf0 \ - --hash=sha256:4b3be8af87b357340b9b049d28067287b5e5e296e3120b6e4875d3b8469b67e6 \ - --hash=sha256:680bc0e357b7da6d0d01634bffbd002e866fdaccde303e1d1af58f32464cf308 \ - --hash=sha256:70de69681a264a5808d39f4bb6331be9a4dec51fd48cd1b959a94da76c4939cc \ - --hash=sha256:73198cda6e1d991c583ed798514863e16763aa600eb7aa6df7722373290575b2 \ - --hash=sha256:80864f456f715829f901ad5bb85af97e9ae52fc902270944804f6476ab8c6699 \ - --hash=sha256:80b9552295fdce0a2513dcb795a3f8591eca1a8dcf8afe0de3214209e6924ad1 \ - --hash=sha256:84fa41b58a8d3b7363ecdf3397d4b38f345fcf7d4dd14565b4360e7bffc9cae0 \ - --hash=sha256:890d2139100b5c8ac6d585438d5e145ede1d7b32b4090a6c078db6db0ef1daea \ - --hash=sha256:8b3f97041f7716998e474d3c7ffd19ac6941f117616696aef2b5ba927bf091e3 \ - --hash=sha256:910479e92d5fbdf91e8106b4c658fd43d418893d7cfd5fb11983c54a1ff53869 \ - --hash=sha256:96a1beef1e64d44597627f4e29d794047a66ad4d7474d93daf5a0ee27928e012 \ - --hash=sha256:98bef54cf51ac335fda1408112df7ff3e584107633bd9066616033e12b0bd519 \ - --hash=sha256:afcb1bd6d9ed59d5c8e633765589cab12f98aae09804f156b5965b4463b8b8e3 \ - --hash=sha256:b72dec85488af3e1e8d58fb4b86b5dbe5171c176002b5e098ea6d52a70004bb5 \ - --hash=sha256:c3109ba62bea56c68c7c2a976250b040afee61b5f86fc791f17afaa2a09fce94 \ - --hash=sha256:c73b9e6c107e980e65077b89c54311d8d645f6a9efdde82990777fa43c0a8cae \ - --hash=sha256:d8fca8b492a4697b0182e0c40b164cb0c44a9669d9c98033fec2f88174605eb0 \ - --hash=sha256:db6872294339bf35c158219fc65bad939ba87d14c936ae7a33a3ca2d1532c5b1 \ - --hash=sha256:e1a90bc97f53600f52e902f3ae097424de712d8ae0e42d957efc7ed908573a20 \ - --hash=sha256:f75f476fe183c03c515a0f0f5d195cb05d93fcdc76e31fe3c9753d01f3ee990b \ - --hash=sha256:fd17b14f84bec0b171869abd17ca0d9bfc564aa1e7f3451f44da526949a911c1 \ - --hash=sha256:fea71780b66f8c278ebae7221c8959404cf7343b8d2f4b7308aa668cf6f02364 - # via transformers -toml==0.10.2 \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f - # via streamlit -toolz==0.12.0 \ - --hash=sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f \ - --hash=sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194 - # via altair -torch==1.12.1 ; platform_system == "Darwin" \ - --hash=sha256:03e31c37711db2cd201e02de5826de875529e45a55631d317aadce2f1ed45aa8 \ - --hash=sha256:0b44601ec56f7dd44ad8afc00846051162ef9c26a8579dda0a02194327f2d55e \ - --hash=sha256:42e115dab26f60c29e298559dbec88444175528b729ae994ec4c65d56fe267dd \ - --hash=sha256:42f639501928caabb9d1d55ddd17f07cd694de146686c24489ab8c615c2871f2 \ - --hash=sha256:4e1b9c14cf13fd2ab8d769529050629a0e68a6fc5cb8e84b4a3cc1dd8c4fe541 \ - --hash=sha256:68104e4715a55c4bb29a85c6a8d57d820e0757da363be1ba680fa8cc5be17b52 \ - --hash=sha256:69fe2cae7c39ccadd65a123793d30e0db881f1c1927945519c5c17323131437e \ - --hash=sha256:6cf6f54b43c0c30335428195589bd00e764a6d27f3b9ba637aaa8c11aaf93073 \ - --hash=sha256:743784ccea0dc8f2a3fe6a536bec8c4763bd82c1352f314937cb4008d4805de1 \ - --hash=sha256:8a34a2fbbaa07c921e1b203f59d3d6e00ed379f2b384445773bd14e328a5b6c8 \ - --hash=sha256:976c3f997cea38ee91a0dd3c3a42322785414748d1761ef926b789dfa97c6134 \ - --hash=sha256:9b356aea223772cd754edb4d9ecf2a025909b8615a7668ac7d5130f86e7ec421 \ - --hash=sha256:9c038662db894a23e49e385df13d47b2a777ffd56d9bcd5b832593fab0a7e286 \ - --hash=sha256:a8320ba9ad87e80ca5a6a016e46ada4d1ba0c54626e135d99b2129a4541c509d \ - --hash=sha256:b5dbcca369800ce99ba7ae6dee3466607a66958afca3b740690d88168752abcf \ - --hash=sha256:bfec2843daa654f04fda23ba823af03e7b6f7650a873cdb726752d0e3718dada \ - --hash=sha256:cd26d8c5640c3a28c526d41ccdca14cf1cbca0d0f2e14e8263a7ac17194ab1d2 \ - --hash=sha256:e9c8f4a311ac29fc7e8e955cfb7733deb5dbe1bdaabf5d4af2765695824b7e0d \ - --hash=sha256:f00c721f489089dc6364a01fd84906348fe02243d0af737f944fddb36003400d \ - --hash=sha256:f3b52a634e62821e747e872084ab32fbcb01b7fa7dbb7471b6218279f02a178a - # via - # -r installer/requirements.in - # accelerate - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # kornia - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # test-tube - # torch-fidelity - # torchdiffeq - # torchmetrics - # torchsde - # torchvision -torch-fidelity==0.3.0 \ - --hash=sha256:3d3e33db98919759cc4f3f24cb27e1e74bdc7c905d90a780630e4e1c18492b66 \ - --hash=sha256:d01284825595feb7dc3eae3dc9a0d8ced02be764813a3483f109bc142b52a1d3 - # via -r installer/requirements.in -torchdiffeq==0.2.3 \ - --hash=sha256:b5b01ec1294a2d8d5f77e567bf17c5de1237c0573cb94deefa88326f0e18c338 \ - --hash=sha256:fe75f434b9090ac0c27702e02bed21472b0f87035be6581f51edc5d4013ea31a - # via k-diffusion -torchmetrics==0.10.2 \ - --hash=sha256:43757d82266969906fc74b6e80766fcb2a0d52d6c3d09e3b7c98cf3b733fd20c \ - --hash=sha256:daa29d96bff5cff04d80eec5b9f5076993d6ac9c2d2163e88b6b31f8d38f7c25 - # via pytorch-lightning -torchsde==0.2.5 \ - --hash=sha256:222be9e15610d37a4b5a71cfa0c442178f9fd9ca02f6522a3e11c370b3d0906b \ - --hash=sha256:4c34373a94a357bdf60bbfee00c850f3563d634491555820b900c9a4f7eff300 - # via k-diffusion -torchvision==0.13.1 ; platform_system == "Darwin" \ - --hash=sha256:0298bae3b09ac361866088434008d82b99d6458fe8888c8df90720ef4b347d44 \ - --hash=sha256:08f592ea61836ebeceb5c97f4d7a813b9d7dc651bbf7ce4401563ccfae6a21fc \ - --hash=sha256:099874088df104d54d8008f2a28539ca0117b512daed8bf3c2bbfa2b7ccb187a \ - --hash=sha256:0e77706cc90462653620e336bb90daf03d7bf1b88c3a9a3037df8d111823a56e \ - --hash=sha256:19286a733c69dcbd417b86793df807bd227db5786ed787c17297741a9b0d0fc7 \ - --hash=sha256:3567fb3def829229ec217c1e38f08c5128ff7fb65854cac17ebac358ff7aa309 \ - --hash=sha256:4d8bf321c4380854ef04613935fdd415dce29d1088a7ff99e06e113f0efe9203 \ - --hash=sha256:5e631241bee3661de64f83616656224af2e3512eb2580da7c08e08b8c965a8ac \ - --hash=sha256:7552e80fa222252b8b217a951c85e172a710ea4cad0ae0c06fbb67addece7871 \ - --hash=sha256:7cb789ceefe6dcd0dc8eeda37bfc45efb7cf34770eac9533861d51ca508eb5b3 \ - --hash=sha256:83e9e2457f23110fd53b0177e1bc621518d6ea2108f570e853b768ce36b7c679 \ - --hash=sha256:87c137f343197769a51333076e66bfcd576301d2cd8614b06657187c71b06c4f \ - --hash=sha256:899eec0b9f3b99b96d6f85b9aa58c002db41c672437677b553015b9135b3be7e \ - --hash=sha256:8e4d02e4d8a203e0c09c10dfb478214c224d080d31efc0dbf36d9c4051f7f3c6 \ - --hash=sha256:b167934a5943242da7b1e59318f911d2d253feeca0d13ad5d832b58eed943401 \ - --hash=sha256:c5ed609c8bc88c575226400b2232e0309094477c82af38952e0373edef0003fd \ - --hash=sha256:e9a563894f9fa40692e24d1aa58c3ef040450017cfed3598ff9637f404f3fe3b \ - --hash=sha256:ef5fe3ec1848123cd0ec74c07658192b3147dcd38e507308c790d5943e87b88c \ - --hash=sha256:f230a1a40ed70d51e463ce43df243ec520902f8725de2502e485efc5eea9d864 - # via - # -r installer/requirements.in - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity -tornado==6.2 \ - --hash=sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca \ - --hash=sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72 \ - --hash=sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23 \ - --hash=sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8 \ - --hash=sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b \ - --hash=sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9 \ - --hash=sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13 \ - --hash=sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75 \ - --hash=sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac \ - --hash=sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e \ - --hash=sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b - # via streamlit -tqdm==4.64.1 \ - --hash=sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4 \ - --hash=sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1 - # via - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # huggingface-hub - # k-diffusion - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity - # transformers -trampoline==0.1.2 \ - --hash=sha256:36cc9a4ff9811843d177fc0e0740efbd7da39eadfe6e50c9e2937cbc06d899d9 - # via torchsde -transformers==4.24.0 \ - --hash=sha256:486f353a8e594002e48be0e2aba723d96eda839e63bfe274702a4b5eda85559b \ - --hash=sha256:b7ab50039ef9bf817eff14ab974f306fd20a72350bdc9df3a858fd009419322e - # via -r installer/requirements.in -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e - # via - # huggingface-hub - # pytorch-lightning - # qudida - # streamlit - # torch - # torchvision -tzdata==2022.6 \ - --hash=sha256:04a680bdc5b15750c39c12a448885a51134a27ec9af83667663f0b3a1bf3f342 \ - --hash=sha256:91f11db4503385928c15598c98573e3af07e7229181bee5375bd30f1695ddcae - # via pytz-deprecation-shim -tzlocal==4.2 \ - --hash=sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745 \ - --hash=sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7 - # via streamlit -urllib3==1.26.12 \ - --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ - --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 - # via - # requests - # sentry-sdk -validators==0.18.2 \ - --hash=sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd \ - --hash=sha256:37cd9a9213278538ad09b5b9f9134266e7c226ab1fede1d500e29e0a8fbb9ea6 - # via streamlit -wandb==0.13.5 \ - --hash=sha256:11f30a22e30abaa9c187e8b6aa4c12d76160b40bbe98a6f14b0dde9297bbfbe2 \ - --hash=sha256:60d5bcc524b8a314c8e072c03f7702dbd5406261b00a4ce75e7556b805fdc765 - # via k-diffusion -wcwidth==0.2.5 \ - --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ - --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 - # via ftfy -werkzeug==2.2.2 \ - --hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \ - --hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5 - # via - # flask - # tb-nightly - # tensorboard -wheel==0.38.2 \ - --hash=sha256:3d492ef22379a156ec923d2a77051cedfd4df4b667864e9e41ba83f0b70b7149 \ - --hash=sha256:7a5a3095dceca97a3cac869b8fef4e89b83fafde21b6688f47b6fda7600eb441 - # via - # tb-nightly - # tensorboard -whichcraft==0.6.1 \ - --hash=sha256:acdbb91b63d6a15efbd6430d1d7b2d36e44a71697e93e19b7ded477afd9fce87 \ - --hash=sha256:deda9266fbb22b8c64fd3ee45c050d61139cd87419765f588e37c8d23e236dd9 - # via flaskwebgui -yapf==0.32.0 \ - --hash=sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32 \ - --hash=sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b - # via - # basicsr - # gfpgan -yarl==1.8.1 \ - --hash=sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb \ - --hash=sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3 \ - --hash=sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035 \ - --hash=sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453 \ - --hash=sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d \ - --hash=sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a \ - --hash=sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231 \ - --hash=sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f \ - --hash=sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae \ - --hash=sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b \ - --hash=sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3 \ - --hash=sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507 \ - --hash=sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd \ - --hash=sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae \ - --hash=sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe \ - --hash=sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c \ - --hash=sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4 \ - --hash=sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64 \ - --hash=sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357 \ - --hash=sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54 \ - --hash=sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461 \ - --hash=sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4 \ - --hash=sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497 \ - --hash=sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0 \ - --hash=sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1 \ - --hash=sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957 \ - --hash=sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350 \ - --hash=sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780 \ - --hash=sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843 \ - --hash=sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548 \ - --hash=sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6 \ - --hash=sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40 \ - --hash=sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee \ - --hash=sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b \ - --hash=sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6 \ - --hash=sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0 \ - --hash=sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e \ - --hash=sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880 \ - --hash=sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc \ - --hash=sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e \ - --hash=sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead \ - --hash=sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28 \ - --hash=sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf \ - --hash=sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd \ - --hash=sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae \ - --hash=sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0 \ - --hash=sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0 \ - --hash=sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae \ - --hash=sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda \ - --hash=sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546 \ - --hash=sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802 \ - --hash=sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be \ - --hash=sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07 \ - --hash=sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936 \ - --hash=sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272 \ - --hash=sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc \ - --hash=sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a \ - --hash=sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28 \ - --hash=sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b - # via aiohttp -zipp==3.10.0 \ - --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ - --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -setuptools==65.5.1 \ - --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ - --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f - # via - # numba - # tb-nightly - # tensorboard - # wandb diff --git a/binary_installer/py3.10-darwin-x86_64-cpu-reqs.txt b/binary_installer/py3.10-darwin-x86_64-cpu-reqs.txt deleted file mode 100644 index e37c887f80..0000000000 --- a/binary_installer/py3.10-darwin-x86_64-cpu-reqs.txt +++ /dev/null @@ -1,2077 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: -# -# pip-compile --allow-unsafe --generate-hashes --output-file=installer/py3.10-darwin-x86_64-cpu-reqs.txt installer/requirements.in -# ---extra-index-url https://download.pytorch.org/whl/cu116 ---trusted-host https - -absl-py==1.3.0 \ - --hash=sha256:34995df9bd7a09b3b8749e230408f5a2a2dd7a68a0d33c12a3d0cb15a041a507 \ - --hash=sha256:463c38a08d2e4cef6c498b76ba5bd4858e4c6ef51da1a5a1f27139a022e20248 - # via - # tb-nightly - # tensorboard -accelerate==0.14.0 \ - --hash=sha256:31c5bcc40564ef849b5bc1c4424a43ccaf9e26413b7df89c2e36bf81f070fd44 \ - --hash=sha256:b15d562c0889d0cf441b01faa025dfc29b163d061b6cc7d489c2c83b0a55ffab - # via - # -r installer/requirements.in - # k-diffusion -addict==2.4.0 \ - --hash=sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc \ - --hash=sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494 - # via basicsr -aiohttp==3.8.3 \ - --hash=sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8 \ - --hash=sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142 \ - --hash=sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18 \ - --hash=sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34 \ - --hash=sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a \ - --hash=sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033 \ - --hash=sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06 \ - --hash=sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4 \ - --hash=sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d \ - --hash=sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b \ - --hash=sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc \ - --hash=sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091 \ - --hash=sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d \ - --hash=sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85 \ - --hash=sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb \ - --hash=sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937 \ - --hash=sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf \ - --hash=sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1 \ - --hash=sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b \ - --hash=sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d \ - --hash=sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269 \ - --hash=sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da \ - --hash=sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346 \ - --hash=sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494 \ - --hash=sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697 \ - --hash=sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4 \ - --hash=sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585 \ - --hash=sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c \ - --hash=sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da \ - --hash=sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad \ - --hash=sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2 \ - --hash=sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6 \ - --hash=sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c \ - --hash=sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849 \ - --hash=sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa \ - --hash=sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b \ - --hash=sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb \ - --hash=sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7 \ - --hash=sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715 \ - --hash=sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76 \ - --hash=sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d \ - --hash=sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276 \ - --hash=sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6 \ - --hash=sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37 \ - --hash=sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb \ - --hash=sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d \ - --hash=sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c \ - --hash=sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446 \ - --hash=sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008 \ - --hash=sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342 \ - --hash=sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d \ - --hash=sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7 \ - --hash=sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061 \ - --hash=sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba \ - --hash=sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7 \ - --hash=sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290 \ - --hash=sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0 \ - --hash=sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d \ - --hash=sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8 \ - --hash=sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f \ - --hash=sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48 \ - --hash=sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502 \ - --hash=sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62 \ - --hash=sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9 \ - --hash=sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403 \ - --hash=sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77 \ - --hash=sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476 \ - --hash=sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e \ - --hash=sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96 \ - --hash=sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5 \ - --hash=sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784 \ - --hash=sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091 \ - --hash=sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b \ - --hash=sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97 \ - --hash=sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a \ - --hash=sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2 \ - --hash=sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9 \ - --hash=sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d \ - --hash=sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73 \ - --hash=sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017 \ - --hash=sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363 \ - --hash=sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c \ - --hash=sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d \ - --hash=sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618 \ - --hash=sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491 \ - --hash=sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b \ - --hash=sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca - # via fsspec -aiosignal==1.2.0 \ - --hash=sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a \ - --hash=sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2 - # via aiohttp -albumentations==1.3.0 \ - --hash=sha256:294165d87d03bc8323e484927f0a5c1a3c64b0e7b9c32a979582a6c93c363bdf \ - --hash=sha256:be1af36832c8893314f2a5550e8ac19801e04770734c1b70fa3c996b41f37bed - # via -r installer/requirements.in -altair==4.2.0 \ - --hash=sha256:0c724848ae53410c13fa28be2b3b9a9dcb7b5caa1a70f7f217bd663bb419935a \ - --hash=sha256:d87d9372e63b48cd96b2a6415f0cf9457f50162ab79dc7a31cd7e024dd840026 - # via streamlit -antlr4-python3-runtime==4.9.3 \ - --hash=sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b - # via omegaconf -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c - # via aiohttp -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c - # via - # aiohttp - # jsonschema -basicsr==1.4.2 \ - --hash=sha256:b89b595a87ef964cda9913b4d99380ddb6554c965577c0c10cb7b78e31301e87 - # via - # gfpgan - # realesrgan -bidict==0.22.0 \ - --hash=sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0 \ - --hash=sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8 - # via python-socketio -blinker==1.5 \ - --hash=sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36 \ - --hash=sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462 - # via streamlit -cachetools==5.2.0 \ - --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ - --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db - # via - # google-auth - # streamlit -certifi==2022.9.24 \ - --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ - --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 - # via - # requests - # sentry-sdk -chardet==4.0.0 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 - # via requests -charset-normalizer==2.1.1 \ - --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ - --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f - # via aiohttp -clean-fid==0.1.34 \ - --hash=sha256:2997f85a67a28c95adaae7899a33fc10537164fef4cdd424e3257bffad79a901 - # via k-diffusion -click==8.1.3 \ - --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ - --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 - # via - # flask - # streamlit - # wandb -clip @ https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip \ - --hash=sha256:b5842c25da441d6c581b53a5c60e0c2127ebafe0f746f8e15561a006c6c3be6a - # via - # -r installer/requirements.in - # clipseg -clipseg @ https://github.com/invoke-ai/clipseg/archive/1f754751c85d7d4255fa681f4491ff5711c1c288.zip \ - --hash=sha256:14f43ed42f90be3fe57f06de483cb8be0f67f87a6f62a011339d45a39f4b4189 - # via -r installer/requirements.in -commonmark==0.9.1 \ - --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ - --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 - # via rich -contourpy==1.0.6 \ - --hash=sha256:0236875c5a0784215b49d00ebbe80c5b6b5d5244b3655a36dda88105334dea17 \ - --hash=sha256:03d1b9c6b44a9e30d554654c72be89af94fab7510b4b9f62356c64c81cec8b7d \ - --hash=sha256:0537cc1195245bbe24f2913d1f9211b8f04eb203de9044630abd3664c6cc339c \ - --hash=sha256:06ca79e1efbbe2df795822df2fa173d1a2b38b6e0f047a0ec7903fbca1d1847e \ - --hash=sha256:08e8d09d96219ace6cb596506fb9b64ea5f270b2fb9121158b976d88871fcfd1 \ - --hash=sha256:0b1e66346acfb17694d46175a0cea7d9036f12ed0c31dfe86f0f405eedde2bdd \ - --hash=sha256:0b97454ed5b1368b66ed414c754cba15b9750ce69938fc6153679787402e4cdf \ - --hash=sha256:0e4854cc02006ad6684ce092bdadab6f0912d131f91c2450ce6dbdea78ee3c0b \ - --hash=sha256:12a7dc8439544ed05c6553bf026d5e8fa7fad48d63958a95d61698df0e00092b \ - --hash=sha256:1b1ee48a130da4dd0eb8055bbab34abf3f6262957832fd575e0cab4979a15a41 \ - --hash=sha256:1c0e1308307a75e07d1f1b5f0f56b5af84538a5e9027109a7bcf6cb47c434e72 \ - --hash=sha256:1dedf4c64185a216c35eb488e6f433297c660321275734401760dafaeb0ad5c2 \ - --hash=sha256:208bc904889c910d95aafcf7be9e677726df9ef71e216780170dbb7e37d118fa \ - --hash=sha256:211dfe2bd43bf5791d23afbe23a7952e8ac8b67591d24be3638cabb648b3a6eb \ - --hash=sha256:341330ed19074f956cb20877ad8d2ae50e458884bfa6a6df3ae28487cc76c768 \ - --hash=sha256:344cb3badf6fc7316ad51835f56ac387bdf86c8e1b670904f18f437d70da4183 \ - --hash=sha256:358f6364e4873f4d73360b35da30066f40387dd3c427a3e5432c6b28dd24a8fa \ - --hash=sha256:371f6570a81dfdddbb837ba432293a63b4babb942a9eb7aaa699997adfb53278 \ - --hash=sha256:375d81366afd547b8558c4720337218345148bc2fcffa3a9870cab82b29667f2 \ - --hash=sha256:3a1917d3941dd58732c449c810fa7ce46cc305ce9325a11261d740118b85e6f3 \ - --hash=sha256:4081918147fc4c29fad328d5066cfc751da100a1098398742f9f364be63803fc \ - --hash=sha256:444fb776f58f4906d8d354eb6f6ce59d0a60f7b6a720da6c1ccb839db7c80eb9 \ - --hash=sha256:46deb310a276cc5c1fd27958e358cce68b1e8a515fa5a574c670a504c3a3fe30 \ - --hash=sha256:494efed2c761f0f37262815f9e3c4bb9917c5c69806abdee1d1cb6611a7174a0 \ - --hash=sha256:50627bf76abb6ba291ad08db583161939c2c5fab38c38181b7833423ab9c7de3 \ - --hash=sha256:5641927cc5ae66155d0c80195dc35726eae060e7defc18b7ab27600f39dd1fe7 \ - --hash=sha256:5b117d29433fc8393b18a696d794961464e37afb34a6eeb8b2c37b5f4128a83e \ - --hash=sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6 \ - --hash=sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142 \ - --hash=sha256:6f56515e7c6fae4529b731f6c117752247bef9cdad2b12fc5ddf8ca6a50965a5 \ - --hash=sha256:730c27978a0003b47b359935478b7d63fd8386dbb2dcd36c1e8de88cbfc1e9de \ - --hash=sha256:75a2e638042118118ab39d337da4c7908c1af74a8464cad59f19fbc5bbafec9b \ - --hash=sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb \ - --hash=sha256:7ee394502026d68652c2824348a40bf50f31351a668977b51437131a90d777ea \ - --hash=sha256:8468b40528fa1e15181cccec4198623b55dcd58306f8815a793803f51f6c474a \ - --hash=sha256:84c593aeff7a0171f639da92cb86d24954bbb61f8a1b530f74eb750a14685832 \ - --hash=sha256:913bac9d064cff033cf3719e855d4f1db9f1c179e0ecf3ba9fdef21c21c6a16a \ - --hash=sha256:9447c45df407d3ecb717d837af3b70cfef432138530712263730783b3d016512 \ - --hash=sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675 \ - --hash=sha256:9bc407a6af672da20da74823443707e38ece8b93a04009dca25856c2d9adadb1 \ - --hash=sha256:9e8e686a6db92a46111a1ee0ee6f7fbfae4048f0019de207149f43ac1812cf95 \ - --hash=sha256:9fc4e7973ed0e1fe689435842a6e6b330eb7ccc696080dda9a97b1a1b78e41db \ - --hash=sha256:a457ee72d9032e86730f62c5eeddf402e732fdf5ca8b13b41772aa8ae13a4563 \ - --hash=sha256:a628bba09ba72e472bf7b31018b6281fd4cc903f0888049a3724afba13b6e0b8 \ - --hash=sha256:a79d239fc22c3b8d9d3de492aa0c245533f4f4c7608e5749af866949c0f1b1b9 \ - --hash=sha256:aa4674cf3fa2bd9c322982644967f01eed0c91bb890f624e0e0daf7a5c3383e9 \ - --hash=sha256:acd2bd02f1a7adff3a1f33e431eb96ab6d7987b039d2946a9b39fe6fb16a1036 \ - --hash=sha256:b3b1bd7577c530eaf9d2bc52d1a93fef50ac516a8b1062c3d1b9bcec9ebe329b \ - --hash=sha256:b48d94386f1994db7c70c76b5808c12e23ed7a4ee13693c2fc5ab109d60243c0 \ - --hash=sha256:b64f747e92af7da3b85631a55d68c45a2d728b4036b03cdaba4bd94bcc85bd6f \ - --hash=sha256:b98c820608e2dca6442e786817f646d11057c09a23b68d2b3737e6dcb6e4a49b \ - --hash=sha256:c1baa49ab9fedbf19d40d93163b7d3e735d9cd8d5efe4cce9907902a6dad391f \ - --hash=sha256:c38c6536c2d71ca2f7e418acaf5bca30a3af7f2a2fa106083c7d738337848dbe \ - --hash=sha256:c78bfbc1a7bff053baf7e508449d2765964d67735c909b583204e3240a2aca45 \ - --hash=sha256:cd2bc0c8f2e8de7dd89a7f1c10b8844e291bca17d359373203ef2e6100819edd \ - --hash=sha256:d2eff2af97ea0b61381828b1ad6cd249bbd41d280e53aea5cccd7b2b31b8225c \ - --hash=sha256:d8834c14b8c3dd849005e06703469db9bf96ba2d66a3f88ecc539c9a8982e0ee \ - --hash=sha256:d912f0154a20a80ea449daada904a7eb6941c83281a9fab95de50529bfc3a1da \ - --hash=sha256:da1ef35fd79be2926ba80fbb36327463e3656c02526e9b5b4c2b366588b74d9a \ - --hash=sha256:dbe6fe7a1166b1ddd7b6d887ea6fa8389d3f28b5ed3f73a8f40ece1fc5a3d340 \ - --hash=sha256:dcd556c8fc37a342dd636d7eef150b1399f823a4462f8c968e11e1ebeabee769 \ - --hash=sha256:e13b31d1b4b68db60b3b29f8e337908f328c7f05b9add4b1b5c74e0691180109 \ - --hash=sha256:e1739496c2f0108013629aa095cc32a8c6363444361960c07493818d0dea2da4 \ - --hash=sha256:e43255a83835a129ef98f75d13d643844d8c646b258bebd11e4a0975203e018f \ - --hash=sha256:e626cefff8491bce356221c22af5a3ea528b0b41fbabc719c00ae233819ea0bf \ - --hash=sha256:eadad75bf91897f922e0fb3dca1b322a58b1726a953f98c2e5f0606bd8408621 \ - --hash=sha256:f33da6b5d19ad1bb5e7ad38bb8ba5c426d2178928bc2b2c44e8823ea0ecb6ff3 \ - --hash=sha256:f4052a8a4926d4468416fc7d4b2a7b2a3e35f25b39f4061a7e2a3a2748c4fc48 \ - --hash=sha256:f6ca38dd8d988eca8f07305125dec6f54ac1c518f1aaddcc14d08c01aebb6efc - # via matplotlib -cycler==0.11.0 \ - --hash=sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3 \ - --hash=sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f - # via matplotlib -decorator==5.1.1 \ - --hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \ - --hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 - # via validators -diffusers==0.7.2 \ - --hash=sha256:4a5f8b3a5fbd936bba7d459611cb35ec62875030367be32b232f9e19543e25a9 \ - --hash=sha256:fb814ffd150cc6f470380b8c6a521181a77beb2f44134d2aad2e4cd8aa2ced0e - # via -r installer/requirements.in -dnspython==2.2.1 \ - --hash=sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e \ - --hash=sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f - # via eventlet -docker-pycreds==0.4.0 \ - --hash=sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4 \ - --hash=sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49 - # via wandb -einops==0.5.0 \ - --hash=sha256:055de7eeb3cb9e9710ef3085a811090c6b52e809b7044e8785824ed185f486d1 \ - --hash=sha256:8b7a83cffc1ea88e306df099b7cbb9c3ba5003bd84d05ae44be5655864abb8d3 - # via k-diffusion -entrypoints==0.4 \ - --hash=sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4 \ - --hash=sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f - # via altair -eventlet==0.33.1 \ - --hash=sha256:a085922698e5029f820cf311a648ac324d73cec0e4792877609d978a4b5bbf31 \ - --hash=sha256:afbe17f06a58491e9aebd7a4a03e70b0b63fd4cf76d8307bae07f280479b1515 - # via -r installer/requirements.in -facexlib==0.2.5 \ - --hash=sha256:31e20cc4ed5d63562d380e4564bae14ac0d5d1899a079bad87621e13564567e4 \ - --hash=sha256:cc7ceb56c5424319c47223cf75eef6828c34c66082707c6eb35b95d39779f02d - # via - # gfpgan - # realesrgan -filelock==3.8.0 \ - --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ - --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 - # via - # diffusers - # huggingface-hub - # transformers -filterpy==1.4.5 \ - --hash=sha256:4f2a4d39e4ea601b9ab42b2db08b5918a9538c168cff1c6895ae26646f3d73b1 - # via facexlib -flask==2.2.2 \ - --hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \ - --hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526 - # via - # flask-cors - # flask-socketio -flask-cors==3.0.10 \ - --hash=sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438 \ - --hash=sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de - # via -r installer/requirements.in -flask-socketio==5.3.1 \ - --hash=sha256:fd0ed0fc1341671d92d5f5b2f5503916deb7aa7e2940e6636cfa2c087c828bf9 \ - --hash=sha256:ff0c721f20bff1e2cfba77948727a8db48f187e89a72fe50c34478ce6efb3353 - # via -r installer/requirements.in -flaskwebgui==0.3.7 \ - --hash=sha256:4a69955308eaa8bb256ba04a994dc8f58a48dcd6f9599694ab1bcd9f43d88a5d \ - --hash=sha256:535974ce2672dcc74787c254de24cceed4101be75d96952dae82014dd57f061e - # via -r installer/requirements.in -fonttools==4.38.0 \ - --hash=sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1 \ - --hash=sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb - # via matplotlib -frozenlist==1.3.1 \ - --hash=sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e \ - --hash=sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04 \ - --hash=sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944 \ - --hash=sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845 \ - --hash=sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f \ - --hash=sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f \ - --hash=sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb \ - --hash=sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2 \ - --hash=sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3 \ - --hash=sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f \ - --hash=sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a \ - --hash=sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131 \ - --hash=sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1 \ - --hash=sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa \ - --hash=sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8 \ - --hash=sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b \ - --hash=sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2 \ - --hash=sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e \ - --hash=sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b \ - --hash=sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b \ - --hash=sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10 \ - --hash=sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170 \ - --hash=sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8 \ - --hash=sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b \ - --hash=sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989 \ - --hash=sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd \ - --hash=sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03 \ - --hash=sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519 \ - --hash=sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189 \ - --hash=sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b \ - --hash=sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca \ - --hash=sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff \ - --hash=sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96 \ - --hash=sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d \ - --hash=sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5 \ - --hash=sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2 \ - --hash=sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204 \ - --hash=sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a \ - --hash=sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8 \ - --hash=sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141 \ - --hash=sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792 \ - --hash=sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9 \ - --hash=sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c \ - --hash=sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c \ - --hash=sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221 \ - --hash=sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d \ - --hash=sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc \ - --hash=sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f \ - --hash=sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9 \ - --hash=sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef \ - --hash=sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3 \ - --hash=sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab \ - --hash=sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b \ - --hash=sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db \ - --hash=sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988 \ - --hash=sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6 \ - --hash=sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc \ - --hash=sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83 \ - --hash=sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be - # via - # aiohttp - # aiosignal -fsspec[http]==2022.10.0 \ - --hash=sha256:6b7c6ab3b476cdf17efcfeccde7fca28ef5a48f73a71010aaceec5fc15bf9ebf \ - --hash=sha256:cb6092474e90487a51de768170f3afa50ca8982c26150a59072b16433879ff1d - # via pytorch-lightning -ftfy==6.1.1 \ - --hash=sha256:0ffd33fce16b54cccaec78d6ec73d95ad370e5df5a25255c8966a6147bd667ca \ - --hash=sha256:bfc2019f84fcd851419152320a6375604a0f1459c281b5b199b2cd0d2e727f8f - # via clip -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d - # via - # basicsr - # test-tube -getpass-asterisk==1.0.1 \ - --hash=sha256:20d45cafda0066d761961e0919728526baf7bb5151fbf48a7d5ea4034127d857 \ - --hash=sha256:7cc357a924cf62fa4e15b73cb4e5e30685c9084e464ffdc3fd9000a2b54ea9e9 - # via -r installer/requirements.in -gfpgan @ https://github.com/TencentARC/GFPGAN/archive/2eac2033893ca7f427f4035d80fe95b92649ac56.zip \ - --hash=sha256:79e6d71c8f1df7c7ccb0ac6b9a2ccb615ad5cde818c8b6f285a8711c05aebf85 - # via - # -r installer/requirements.in - # realesrgan -gitdb==4.0.9 \ - --hash=sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd \ - --hash=sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa - # via gitpython -gitpython==3.1.29 \ - --hash=sha256:41eea0deec2deea139b459ac03656f0dd28fc4a3387240ec1d3c259a2c47850f \ - --hash=sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd - # via - # streamlit - # wandb -google-auth==2.14.0 \ - --hash=sha256:1ad5b0e6eba5f69645971abb3d2c197537d5914070a8c6d30299dfdb07c5c700 \ - --hash=sha256:cf24817855d874ede2efd071aa22125445f555de1685b739a9782fcf408c2a3d - # via - # google-auth-oauthlib - # tb-nightly - # tensorboard -google-auth-oauthlib==0.4.6 \ - --hash=sha256:3f2a6e802eebbb6fb736a370fbf3b055edcb6b52878bf2f26330b5e041316c73 \ - --hash=sha256:a90a072f6993f2c327067bf65270046384cda5a8ecb20b94ea9a687f1f233a7a - # via - # tb-nightly - # tensorboard -greenlet==2.0.0.post0 \ - --hash=sha256:00ebdaf0fa51c284fd2172837d751731a15971e0c20d1a9163cfbdf620ce8b49 \ - --hash=sha256:029ca674b3a7e8427db8f5c65d5ed4e24a7417af2a415a5958598aefd71980c4 \ - --hash=sha256:02bdb1e373b275bd705c43b249426e776c4f8a8ff2afaf8ec5ea0dde487d8a14 \ - --hash=sha256:08dc04f49ed1ea5e6772bb5e8cf2a77d1b1744566f4eca471a55b35af1278b31 \ - --hash=sha256:08f44e938d142271b954405afb6570e0be48a9f556b6bf4d42d2e3ae6a251fad \ - --hash=sha256:0a5c03e2a68ec2ff1cba74ceaed899ec8cd353285f4f985c30c8cfbef9d3a3be \ - --hash=sha256:0fee3240093b745efc857392f09379514ad84db4ca324514594bbdf6380016c8 \ - --hash=sha256:118e708dd7bc88beaeeaa5a8601a7743b8835b7bbaf7c8f23ffa78f8bc8faf28 \ - --hash=sha256:13d492a807a5c7334b5931e9b6d9b181991ccc6a40555a7b177f189feff59b4b \ - --hash=sha256:1cac9e9895aeff26434325404558783ee54f4ff3aec8daa56b8706796f7b01a0 \ - --hash=sha256:2146d15429b4eeb412428737594acb5660a5bc0fdd1488d8a2a74a5ee32391fa \ - --hash=sha256:21ee1ae26d072b195edea764218623f6c15eba4ae06816908f33c82e0af018d3 \ - --hash=sha256:22eca421e3f2f3c18f4f54c0ff525aa9d397c6f116fce9ebd37b420174dbc027 \ - --hash=sha256:2bab49783858cf724fff6868395cbeb81d1188cba23616b53e79de0beda29f42 \ - --hash=sha256:2fbdec204ca40b3d0c0992a19c1ba627441c17983ac4ffc45baec7f5f53e20ca \ - --hash=sha256:30ce47525f9a1515566429ac7de6b1ae76d32c3ccede256e3517a1a6419cf659 \ - --hash=sha256:335dcf676d5e4122e4006c16ae11eda2467af5461b949c265ce120b6b959ffe2 \ - --hash=sha256:3407b843b05da71fef0f1dd666059c08ad0e0f4afc3b9c93c998a2e53fac95e5 \ - --hash=sha256:35827f98fd0d768862b8f15777e6dbb03fe6ac6e7bd1bee3f3ded4536f350347 \ - --hash=sha256:3a22e5988f9d66b3e9ae9583bf9d8ef792b09f23afeb78707e6a4f47ab57cc5e \ - --hash=sha256:3c3327da2bdab61078e42e695307465c425671a5a9251e6c29ee130d51943f28 \ - --hash=sha256:3ca723dfc2789c1fb991809822811896b198ecf0909dbccea4a07170d18c3e1b \ - --hash=sha256:46156ae88ee71c37b6c4f7af63fff5d3ab8f45ef72e1a660bcf6386c1647f106 \ - --hash=sha256:4bbe2d074292e3646704371eb640ee52c386d633ed72ff223dadcd3fe8ecd8f9 \ - --hash=sha256:4c4310f0e42154995d92810f27b44ab7116a4a696feb0ff141ae2de59196efd7 \ - --hash=sha256:4cfa629de5b2dea27c81b334c4536463e9a49ac0877e2008a276d58d4c72868a \ - --hash=sha256:4e144ab0de56b4d2a2cf0d2fb9d568b59fce49aab3e129badf17c12b0252047d \ - --hash=sha256:4ea67f303cec384b148774667c7e3cf02311e7026fc02bdcdcd206dfe4ea4fc9 \ - --hash=sha256:538c9e8f65a32413ace426f8117ef019021adf8175f7c491fed65f5fe2083e0c \ - --hash=sha256:56565ac9ab4ff3dd473bfe959e0bf2a5062aabb89b7c94cabb417beb162c9fff \ - --hash=sha256:5e22485256bb1c60bbcc6f8509b1a11042358a2462d5ecdb9a82dc472d2fdd60 \ - --hash=sha256:602a69c24f1a9755dd1760b3b31bdfc495c4613260c876a01b7e6d5eb9bcae1b \ - --hash=sha256:6393ec3cecda53b20241e88bc33d87cbd8126cc10870fc69fa16ca2e20a5ac1b \ - --hash=sha256:6442bbfb047dc1e47658954b72e1589f2bc4e12e67d51bbad0059a626180daa1 \ - --hash=sha256:666d2a0b269a68cd4fe0976544ab97970c5334d35d0e47ae9be1723f734d8204 \ - --hash=sha256:697cfbfc19815c40213badcfe5f076418e0f9100cd25a66f513f32c1026b8bf4 \ - --hash=sha256:6a1a6745c5dce202aa3f29a1736c53cf2179e9c3b280dc62cea9cb8c69977c83 \ - --hash=sha256:6fc73fc8dd81d9efa842a55033b6b4cb233b134a0270e127c6874d053ef2049b \ - --hash=sha256:7e9e0d4c5c618b0442396715ffe6c2f84a60d593dad7e0184388aed36d568a65 \ - --hash=sha256:81fdcf7c0c2df46a99ca421a552c4370117851c5e4dbd6cb53d569b896d62322 \ - --hash=sha256:8b26932be686f3582df039d79fe96f7ca13d63b39468162f816f9ff29584b9a4 \ - --hash=sha256:8b7e5191b974fb66fcbac1818ba494d3512da9cf6eaef7acd952f9862eaaa20c \ - --hash=sha256:8c80e9c41a83d8c90399af8c7dcdeae0c03c48b40b9d0ab84457533d5d7882bf \ - --hash=sha256:9f2f110b9cc325f6543e0e3f4ab8008c272a59052f9464047c29d4be4511ce05 \ - --hash=sha256:a339e510a079dc8372e39ce1c7629414db51966235c9670c58d529def79243a2 \ - --hash=sha256:ad9abc3e4d2370cecb524421cc5c8a664006aa11d5c1cb3c9250e3bf65ab546e \ - --hash=sha256:b043782c8f6cccc8fae3a16db397eca1d36a41b0706cbf6f514aea1e1a260bab \ - --hash=sha256:b31de27313abbb567c528ed123380fcf18a5dfd03134570dfd12227e21ac1184 \ - --hash=sha256:b75e5644cc353328cd57ec8dafaaf5f81b2c3ecf7c4b278b907e99ad53ba7839 \ - --hash=sha256:b8cfc8fc944bd7b704691bc28225a2635e377e92dc413459845868d3f7724982 \ - --hash=sha256:c2055c52260808d87622293b57df1c68aeb12ddd8a0cfc0223fb57a5f629e202 \ - --hash=sha256:c416106b3b8e905b6ab0e84ec90047a6401021aa023f9aa93978e57cd8f8189f \ - --hash=sha256:d0e210e17a6181a3fd3f8dce957043a4e74177ffa9f295514984b2b633940dce \ - --hash=sha256:d9453135e48cd631e3e9f06d9da9100d17c9f662e4a6d8b552c29be6c834a6b9 \ - --hash=sha256:dd0198006278291d9469309d655093df1f5e5107c0261e242b5f390baee32199 \ - --hash=sha256:e1781bda1e787d3ad33788cc3be47f6e47a9581676d02670c15ee36c9460adfe \ - --hash=sha256:e56a5a9f303e3ac011ba445a6d84f05d08666bf8db094afafcec5228622c30f5 \ - --hash=sha256:e93ae35f0fd3caf75e58c76a1cab71e6ece169aaa1b281782ef9efde0a6b83f2 \ - --hash=sha256:eb36b6570646227a63eda03916f1cc6f3744ee96d28f7a0a5629c59267a8055f \ - --hash=sha256:f8c425a130e04d5404edaf6f5906e5ab12f3aa1168a1828aba6dfadac5910469 - # via eventlet -grpcio==1.50.0 \ - --hash=sha256:05f7c248e440f538aaad13eee78ef35f0541e73498dd6f832fe284542ac4b298 \ - --hash=sha256:080b66253f29e1646ac53ef288c12944b131a2829488ac3bac8f52abb4413c0d \ - --hash=sha256:12b479839a5e753580b5e6053571de14006157f2ef9b71f38c56dc9b23b95ad6 \ - --hash=sha256:156f8009e36780fab48c979c5605eda646065d4695deea4cfcbcfdd06627ddb6 \ - --hash=sha256:15f9e6d7f564e8f0776770e6ef32dac172c6f9960c478616c366862933fa08b4 \ - --hash=sha256:177afaa7dba3ab5bfc211a71b90da1b887d441df33732e94e26860b3321434d9 \ - --hash=sha256:1a4cd8cb09d1bc70b3ea37802be484c5ae5a576108bad14728f2516279165dd7 \ - --hash=sha256:1d8d02dbb616c0a9260ce587eb751c9c7dc689bc39efa6a88cc4fa3e9c138a7b \ - --hash=sha256:2b71916fa8f9eb2abd93151fafe12e18cebb302686b924bd4ec39266211da525 \ - --hash=sha256:2d9fd6e38b16c4d286a01e1776fdf6c7a4123d99ae8d6b3f0b4a03a34bf6ce45 \ - --hash=sha256:3b611b3de3dfd2c47549ca01abfa9bbb95937eb0ea546ea1d762a335739887be \ - --hash=sha256:3e4244c09cc1b65c286d709658c061f12c61c814be0b7030a2d9966ff02611e0 \ - --hash=sha256:40838061e24f960b853d7bce85086c8e1b81c6342b1f4c47ff0edd44bbae2722 \ - --hash=sha256:4b123fbb7a777a2fedec684ca0b723d85e1d2379b6032a9a9b7851829ed3ca9a \ - --hash=sha256:531f8b46f3d3db91d9ef285191825d108090856b3bc86a75b7c3930f16ce432f \ - --hash=sha256:67dd41a31f6fc5c7db097a5c14a3fa588af54736ffc174af4411d34c4f306f68 \ - --hash=sha256:7489dbb901f4fdf7aec8d3753eadd40839c9085967737606d2c35b43074eea24 \ - --hash=sha256:8d4c8e73bf20fb53fe5a7318e768b9734cf122fe671fcce75654b98ba12dfb75 \ - --hash=sha256:8e69aa4e9b7f065f01d3fdcecbe0397895a772d99954bb82eefbb1682d274518 \ - --hash=sha256:8e8999a097ad89b30d584c034929f7c0be280cd7851ac23e9067111167dcbf55 \ - --hash=sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974 \ - --hash=sha256:92d7635d1059d40d2ec29c8bf5ec58900120b3ce5150ef7414119430a4b2dd5c \ - --hash=sha256:931e746d0f75b2a5cff0a1197d21827a3a2f400c06bace036762110f19d3d507 \ - --hash=sha256:95ce51f7a09491fb3da8cf3935005bff19983b77c4e9437ef77235d787b06842 \ - --hash=sha256:9eea18a878cffc804506d39c6682d71f6b42ec1c151d21865a95fae743fda500 \ - --hash=sha256:a23d47f2fc7111869f0ff547f771733661ff2818562b04b9ed674fa208e261f4 \ - --hash=sha256:a4c23e54f58e016761b576976da6a34d876420b993f45f66a2bfb00363ecc1f9 \ - --hash=sha256:a50a1be449b9e238b9bd43d3857d40edf65df9416dea988929891d92a9f8a778 \ - --hash=sha256:ab5d0e3590f0a16cb88de4a3fa78d10eb66a84ca80901eb2c17c1d2c308c230f \ - --hash=sha256:ae23daa7eda93c1c49a9ecc316e027ceb99adbad750fbd3a56fa9e4a2ffd5ae0 \ - --hash=sha256:af98d49e56605a2912cf330b4627e5286243242706c3a9fa0bcec6e6f68646fc \ - --hash=sha256:b2f77a90ba7b85bfb31329f8eab9d9540da2cf8a302128fb1241d7ea239a5469 \ - --hash=sha256:baab51dcc4f2aecabf4ed1e2f57bceab240987c8b03533f1cef90890e6502067 \ - --hash=sha256:ca8a2254ab88482936ce941485c1c20cdeaef0efa71a61dbad171ab6758ec998 \ - --hash=sha256:cb11464f480e6103c59d558a3875bd84eed6723f0921290325ebe97262ae1347 \ - --hash=sha256:ce8513aee0af9c159319692bfbf488b718d1793d764798c3d5cff827a09e25ef \ - --hash=sha256:cf151f97f5f381163912e8952eb5b3afe89dec9ed723d1561d59cabf1e219a35 \ - --hash=sha256:d144ad10eeca4c1d1ce930faa105899f86f5d99cecfe0d7224f3c4c76265c15e \ - --hash=sha256:d534d169673dd5e6e12fb57cc67664c2641361e1a0885545495e65a7b761b0f4 \ - --hash=sha256:d75061367a69808ab2e84c960e9dce54749bcc1e44ad3f85deee3a6c75b4ede9 \ - --hash=sha256:d84d04dec64cc4ed726d07c5d17b73c343c8ddcd6b59c7199c801d6bbb9d9ed1 \ - --hash=sha256:de411d2b030134b642c092e986d21aefb9d26a28bf5a18c47dd08ded411a3bc5 \ - --hash=sha256:e07fe0d7ae395897981d16be61f0db9791f482f03fee7d1851fe20ddb4f69c03 \ - --hash=sha256:ea8ccf95e4c7e20419b7827aa5b6da6f02720270686ac63bd3493a651830235c \ - --hash=sha256:f7025930039a011ed7d7e7ef95a1cb5f516e23c5a6ecc7947259b67bea8e06ca - # via - # tb-nightly - # tensorboard -huggingface-hub==0.10.1 \ - --hash=sha256:5c188d5b16bec4b78449f8681f9975ff9d321c16046cc29bcf0d7e464ff29276 \ - --hash=sha256:dc3b0e9a663fe6cad6a8522055c02a9d8673dbd527223288e2442bc028c253db - # via - # diffusers - # transformers -idna==2.10 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 - # via - # requests - # yarl -imageio==2.22.3 \ - --hash=sha256:63f007b7f2a082306e36922b3fd529a7aa305d2b78f46195bab8e22bbfe866e9 \ - --hash=sha256:a4b88f9f3d428b8c0ceeb7e297df8c346a642bb7e3111743eb85717d60b26f6f - # via - # scikit-image - # test-tube -imageio-ffmpeg==0.4.7 \ - --hash=sha256:27b48c32becae1658aa81c3a6b922538e4099edf5fbcbdb4ff5dbc84b8ffd3d3 \ - --hash=sha256:6514f1380daf42815bc8c83aad63f33e0b8b47133421ddafe7b410cd8dfbbea5 \ - --hash=sha256:6aba52ddf0a64442ffcb8d30ac6afb668186acec99ecbc7ae5bd171c4f500bbc \ - --hash=sha256:7a08838f97f363e37ca41821b864fd3fdc99ab1fe2421040c78eb5f56a9e723e \ - --hash=sha256:8e724d12dfe83e2a6eb39619e820243ca96c81c47c2648e66e05f7ee24e14312 \ - --hash=sha256:fc60686ef03c2d0f842901b206223c30051a6a120384458761390104470846fd - # via -r installer/requirements.in -importlib-metadata==5.0.0 \ - --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ - --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 - # via - # diffusers - # streamlit -itsdangerous==2.1.2 \ - --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ - --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a - # via flask -jinja2==3.1.2 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - # via - # altair - # flask - # pydeck -joblib==1.2.0 \ - --hash=sha256:091138ed78f800342968c523bdde947e7a305b8594b910a0fea2ab83c3c6d385 \ - --hash=sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018 - # via scikit-learn -jsonmerge==1.9.0 \ - --hash=sha256:a2d1f80021c5c1d70a49e31f862b5f068f9db066080d8561e80654de74a3584d - # via k-diffusion -jsonschema==4.17.0 \ - --hash=sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d \ - --hash=sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248 - # via - # altair - # jsonmerge -k-diffusion @ https://github.com/invoke-ai/k-diffusion/archive/7f16b2c33411f26b3eae78d10648d625cb0c1095.zip \ - --hash=sha256:c3f2c84036aa98c3abf4552fafab04df5ca472aa639982795e05bb1db43ce5e4 - # via -r installer/requirements.in -kiwisolver==1.4.4 \ - --hash=sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b \ - --hash=sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166 \ - --hash=sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c \ - --hash=sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c \ - --hash=sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0 \ - --hash=sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4 \ - --hash=sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9 \ - --hash=sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286 \ - --hash=sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767 \ - --hash=sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c \ - --hash=sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6 \ - --hash=sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b \ - --hash=sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004 \ - --hash=sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf \ - --hash=sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494 \ - --hash=sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac \ - --hash=sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626 \ - --hash=sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766 \ - --hash=sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514 \ - --hash=sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6 \ - --hash=sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f \ - --hash=sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d \ - --hash=sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191 \ - --hash=sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d \ - --hash=sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51 \ - --hash=sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f \ - --hash=sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8 \ - --hash=sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454 \ - --hash=sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb \ - --hash=sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da \ - --hash=sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8 \ - --hash=sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de \ - --hash=sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a \ - --hash=sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9 \ - --hash=sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008 \ - --hash=sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3 \ - --hash=sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32 \ - --hash=sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938 \ - --hash=sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1 \ - --hash=sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9 \ - --hash=sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d \ - --hash=sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824 \ - --hash=sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b \ - --hash=sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd \ - --hash=sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2 \ - --hash=sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5 \ - --hash=sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69 \ - --hash=sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3 \ - --hash=sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae \ - --hash=sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597 \ - --hash=sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e \ - --hash=sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955 \ - --hash=sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca \ - --hash=sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a \ - --hash=sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea \ - --hash=sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede \ - --hash=sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4 \ - --hash=sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6 \ - --hash=sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686 \ - --hash=sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408 \ - --hash=sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871 \ - --hash=sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29 \ - --hash=sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750 \ - --hash=sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897 \ - --hash=sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0 \ - --hash=sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2 \ - --hash=sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09 \ - --hash=sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c - # via matplotlib -kornia==0.6.8 \ - --hash=sha256:0985e02453c0ab4f030e8d22a3a7554dab312ffa8f8a54ec872190e6f0b58c56 \ - --hash=sha256:0d6d69330b4fd24da742337b8134da0ce01b4d7da66770db5498d58e8b4a0832 - # via k-diffusion -llvmlite==0.39.1 \ - --hash=sha256:03aee0ccd81735696474dc4f8b6be60774892a2929d6c05d093d17392c237f32 \ - --hash=sha256:1578f5000fdce513712e99543c50e93758a954297575610f48cb1fd71b27c08a \ - --hash=sha256:16f56eb1eec3cda3a5c526bc3f63594fc24e0c8d219375afeb336f289764c6c7 \ - --hash=sha256:1ec3d70b3e507515936e475d9811305f52d049281eaa6c8273448a61c9b5b7e2 \ - --hash=sha256:22d36591cd5d02038912321d9ab8e4668e53ae2211da5523f454e992b5e13c36 \ - --hash=sha256:3803f11ad5f6f6c3d2b545a303d68d9fabb1d50e06a8d6418e6fcd2d0df00959 \ - --hash=sha256:39dc2160aed36e989610fc403487f11b8764b6650017ff367e45384dff88ffbf \ - --hash=sha256:3fc14e757bc07a919221f0cbaacb512704ce5774d7fcada793f1996d6bc75f2a \ - --hash=sha256:4c6ebace910410daf0bebda09c1859504fc2f33d122e9a971c4c349c89cca630 \ - --hash=sha256:50aea09a2b933dab7c9df92361b1844ad3145bfb8dd2deb9cd8b8917d59306fb \ - --hash=sha256:60f8dd1e76f47b3dbdee4b38d9189f3e020d22a173c00f930b52131001d801f9 \ - --hash=sha256:62c0ea22e0b9dffb020601bb65cb11dd967a095a488be73f07d8867f4e327ca5 \ - --hash=sha256:6546bed4e02a1c3d53a22a0bced254b3b6894693318b16c16c8e43e29d6befb6 \ - --hash=sha256:6717c7a6e93c9d2c3d07c07113ec80ae24af45cde536b34363d4bcd9188091d9 \ - --hash=sha256:7ebf1eb9badc2a397d4f6a6c8717447c81ac011db00064a00408bc83c923c0e4 \ - --hash=sha256:9ffc84ade195abd4abcf0bd3b827b9140ae9ef90999429b9ea84d5df69c9058c \ - --hash=sha256:a3f331a323d0f0ada6b10d60182ef06c20a2f01be21699999d204c5750ffd0b4 \ - --hash=sha256:b1a0bbdb274fb683f993198775b957d29a6f07b45d184c571ef2a721ce4388cf \ - --hash=sha256:b43abd7c82e805261c425d50335be9a6c4f84264e34d6d6e475207300005d572 \ - --hash=sha256:c0f158e4708dda6367d21cf15afc58de4ebce979c7a1aa2f6b977aae737e2a54 \ - --hash=sha256:d0bfd18c324549c0fec2c5dc610fd024689de6f27c6cc67e4e24a07541d6e49b \ - --hash=sha256:ddab526c5a2c4ccb8c9ec4821fcea7606933dc53f510e2a6eebb45a418d3488a \ - --hash=sha256:e172c73fccf7d6db4bd6f7de963dedded900d1a5c6778733241d878ba613980e \ - --hash=sha256:e2c00ff204afa721b0bb9835b5bf1ba7fba210eefcec5552a9e05a63219ba0dc \ - --hash=sha256:e31f4b799d530255aaf0566e3da2df5bfc35d3cd9d6d5a3dcc251663656c27b1 \ - --hash=sha256:e4f212c018db951da3e1dc25c2651abc688221934739721f2dad5ff1dd5f90e7 \ - --hash=sha256:fa9b26939ae553bf30a9f5c4c754db0fb2d2677327f2511e674aa2f5df941789 \ - --hash=sha256:fb62fc7016b592435d3e3a8f680e3ea8897c3c9e62e6e6cc58011e7a4801439e - # via numba -lmdb==1.3.0 \ - --hash=sha256:008243762decf8f6c90430a9bced56290ebbcdb5e877d90e42343bb97033e494 \ - --hash=sha256:08f4b5129f4683802569b02581142e415c8dcc0ff07605983ec1b07804cecbad \ - --hash=sha256:17215a42a4b9814c383deabecb160581e4fb75d00198eef0e3cea54f230ffbea \ - --hash=sha256:18c69fabdaf04efaf246587739cc1062b3e57c6ef0743f5c418df89e5e7e7b9b \ - --hash=sha256:2cfa4aa9c67f8aee89b23005e98d1f3f32490b6b905fd1cb604b207cbd5755ab \ - --hash=sha256:2df38115dd9428a54d59ae7c712a4c7cce0d6b1d66056de4b1a8c38718066106 \ - --hash=sha256:394df860c3f93cfd92b6f4caba785f38208cc9614c18b3803f83a2cc1695042f \ - --hash=sha256:41318717ab5d15ad2d6d263d34fbf614a045210f64b25e59ce734bb2105e421f \ - --hash=sha256:4172fba19417d7b29409beca7d73c067b54e5d8ab1fb9b51d7b4c1445d20a167 \ - --hash=sha256:5a14aca2651c3af6f0d0a6b9168200eea0c8f2d27c40b01a442f33329a6e8dff \ - --hash=sha256:5ddd590e1c7fcb395931aa3782fb89b9db4550ab2d81d006ecd239e0d462bc41 \ - --hash=sha256:60a11efc21aaf009d06518996360eed346f6000bfc9de05114374230879f992e \ - --hash=sha256:6260a526e4ad85b1f374a5ba9475bf369fb07e7728ea6ec57226b02c40d1976b \ - --hash=sha256:62ab28e3593bdc318ea2f2fa1574e5fca3b6d1f264686d773ba54a637d4f563b \ - --hash=sha256:63cb73fe7ce9eb93d992d632c85a0476b4332670d9e6a2802b5062f603b7809f \ - --hash=sha256:65334eafa5d430b18d81ebd5362559a41483c362e1931f6e1b15bab2ecb7d75d \ - --hash=sha256:7da05d70fcc6561ac6b09e9fb1bf64b7ca294652c64c8a2889273970cee796b9 \ - --hash=sha256:abbc439cd9fe60ffd6197009087ea885ac150017dc85384093b1d376f83f0ec4 \ - --hash=sha256:c6adbd6f7f9048e97f31a069e652eb51020a81e80a0ce92dbb9810d21da2409a \ - --hash=sha256:d6a816954d212f40fd15007cd81ab7a6bebb77436d949a6a9ae04af57fc127f3 \ - --hash=sha256:d9103aa4908f0bca43c5911ca067d4e3d01f682dff0c0381a1239bd2bd757984 \ - --hash=sha256:df2724bad7820114a205472994091097d0fa65a3e5fff5a8e688d123fb8c6326 \ - --hash=sha256:e568ae0887ae196340947d9800136e90feaed6b86a261ef01f01b2ba65fc8106 \ - --hash=sha256:e6a704b3baced9182836c7f77b769f23856f3a8f62d0282b1bc1feaf81a86712 \ - --hash=sha256:eefb392f6b5cd43aada49258c5a79be11cb2c8cd3fc3e2d9319a1e0b9f906458 \ - --hash=sha256:f291e3f561f58dddf63a92a5a6a4b8af3a0920b6705d35e2f80e52e86ee238a2 \ - --hash=sha256:fa6439356e591d3249ab0e1778a6f8d8408e993f66dc911914c78208f5310309 - # via - # basicsr - # gfpgan -markdown==3.4.1 \ - --hash=sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186 \ - --hash=sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff - # via - # tb-nightly - # tensorboard -markupsafe==2.1.1 \ - --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ - --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ - --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ - --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ - --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ - --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ - --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ - --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ - --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ - --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ - --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ - --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ - --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ - --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ - --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ - --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ - --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ - --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ - --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ - --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ - --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ - --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ - --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ - --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ - --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ - --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ - --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ - --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ - --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ - --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ - --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ - --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ - --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ - --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ - --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ - --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ - --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ - --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ - --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ - --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 - # via - # jinja2 - # werkzeug -matplotlib==3.6.2 \ - --hash=sha256:0844523dfaaff566e39dbfa74e6f6dc42e92f7a365ce80929c5030b84caa563a \ - --hash=sha256:0eda9d1b43f265da91fb9ae10d6922b5a986e2234470a524e6b18f14095b20d2 \ - --hash=sha256:168093410b99f647ba61361b208f7b0d64dde1172b5b1796d765cd243cadb501 \ - --hash=sha256:1836f366272b1557a613f8265db220eb8dd883202bbbabe01bad5a4eadfd0c95 \ - --hash=sha256:19d61ee6414c44a04addbe33005ab1f87539d9f395e25afcbe9a3c50ce77c65c \ - --hash=sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267 \ - --hash=sha256:32d29c8c26362169c80c5718ce367e8c64f4dd068a424e7110df1dd2ed7bd428 \ - --hash=sha256:380d48c15ec41102a2b70858ab1dedfa33eb77b2c0982cb65a200ae67a48e9cb \ - --hash=sha256:3964934731fd7a289a91d315919cf757f293969a4244941ab10513d2351b4e83 \ - --hash=sha256:3cef89888a466228fc4e4b2954e740ce8e9afde7c4315fdd18caa1b8de58ca17 \ - --hash=sha256:4426c74761790bff46e3d906c14c7aab727543293eed5a924300a952e1a3a3c1 \ - --hash=sha256:5024b8ed83d7f8809982d095d8ab0b179bebc07616a9713f86d30cf4944acb73 \ - --hash=sha256:52c2bdd7cd0bf9d5ccdf9c1816568fd4ccd51a4d82419cc5480f548981b47dd0 \ - --hash=sha256:54fa9fe27f5466b86126ff38123261188bed568c1019e4716af01f97a12fe812 \ - --hash=sha256:5ba73aa3aca35d2981e0b31230d58abb7b5d7ca104e543ae49709208d8ce706a \ - --hash=sha256:5e16dcaecffd55b955aa5e2b8a804379789c15987e8ebd2f32f01398a81e975b \ - --hash=sha256:5ecfc6559132116dedfc482d0ad9df8a89dc5909eebffd22f3deb684132d002f \ - --hash=sha256:74153008bd24366cf099d1f1e83808d179d618c4e32edb0d489d526523a94d9f \ - --hash=sha256:78ec3c3412cf277e6252764ee4acbdbec6920cc87ad65862272aaa0e24381eee \ - --hash=sha256:795ad83940732b45d39b82571f87af0081c120feff2b12e748d96bb191169e33 \ - --hash=sha256:7f716b6af94dc1b6b97c46401774472f0867e44595990fe80a8ba390f7a0a028 \ - --hash=sha256:83dc89c5fd728fdb03b76f122f43b4dcee8c61f1489e232d9ad0f58020523e1c \ - --hash=sha256:8a0ae37576ed444fe853709bdceb2be4c7df6f7acae17b8378765bd28e61b3ae \ - --hash=sha256:8a8dbe2cb7f33ff54b16bb5c500673502a35f18ac1ed48625e997d40c922f9cc \ - --hash=sha256:8a9d899953c722b9afd7e88dbefd8fb276c686c3116a43c577cfabf636180558 \ - --hash=sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5 \ - --hash=sha256:9347cc6822f38db2b1d1ce992f375289670e595a2d1c15961aacbe0977407dfc \ - --hash=sha256:9f335e5625feb90e323d7e3868ec337f7b9ad88b5d633f876e3b778813021dab \ - --hash=sha256:b03fd10a1709d0101c054883b550f7c4c5e974f751e2680318759af005964990 \ - --hash=sha256:b0ca2c60d3966dfd6608f5f8c49b8a0fcf76de6654f2eda55fc6ef038d5a6f27 \ - --hash=sha256:b2604c6450f9dd2c42e223b1f5dca9643a23cfecc9fde4a94bb38e0d2693b136 \ - --hash=sha256:ca0e7a658fbafcddcaefaa07ba8dae9384be2343468a8e011061791588d839fa \ - --hash=sha256:d0e9ac04065a814d4cf2c6791a2ad563f739ae3ae830d716d54245c2b96fead6 \ - --hash=sha256:d50e8c1e571ee39b5dfbc295c11ad65988879f68009dd281a6e1edbc2ff6c18c \ - --hash=sha256:d840adcad7354be6f2ec28d0706528b0026e4c3934cc6566b84eac18633eab1b \ - --hash=sha256:e0bbee6c2a5bf2a0017a9b5e397babb88f230e6f07c3cdff4a4c4bc75ed7c617 \ - --hash=sha256:e5afe0a7ea0e3a7a257907060bee6724a6002b7eec55d0db16fd32409795f3e1 \ - --hash=sha256:e68be81cd8c22b029924b6d0ee814c337c0e706b8d88495a617319e5dd5441c3 \ - --hash=sha256:ec9be0f4826cdb3a3a517509dcc5f87f370251b76362051ab59e42b6b765f8c4 \ - --hash=sha256:f04f97797df35e442ed09f529ad1235d1f1c0f30878e2fe09a2676b71a8801e0 \ - --hash=sha256:f41e57ad63d336fe50d3a67bb8eaa26c09f6dda6a59f76777a99b8ccd8e26aec - # via - # clipseg - # filterpy -multidict==6.0.2 \ - --hash=sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60 \ - --hash=sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c \ - --hash=sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672 \ - --hash=sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51 \ - --hash=sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032 \ - --hash=sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2 \ - --hash=sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b \ - --hash=sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80 \ - --hash=sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88 \ - --hash=sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a \ - --hash=sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d \ - --hash=sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389 \ - --hash=sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c \ - --hash=sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9 \ - --hash=sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c \ - --hash=sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516 \ - --hash=sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b \ - --hash=sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43 \ - --hash=sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee \ - --hash=sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227 \ - --hash=sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d \ - --hash=sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae \ - --hash=sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7 \ - --hash=sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4 \ - --hash=sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9 \ - --hash=sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f \ - --hash=sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013 \ - --hash=sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9 \ - --hash=sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e \ - --hash=sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693 \ - --hash=sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a \ - --hash=sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15 \ - --hash=sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb \ - --hash=sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96 \ - --hash=sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87 \ - --hash=sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376 \ - --hash=sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658 \ - --hash=sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0 \ - --hash=sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071 \ - --hash=sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360 \ - --hash=sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc \ - --hash=sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3 \ - --hash=sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba \ - --hash=sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8 \ - --hash=sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9 \ - --hash=sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2 \ - --hash=sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3 \ - --hash=sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68 \ - --hash=sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8 \ - --hash=sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d \ - --hash=sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49 \ - --hash=sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608 \ - --hash=sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57 \ - --hash=sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86 \ - --hash=sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20 \ - --hash=sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293 \ - --hash=sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849 \ - --hash=sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937 \ - --hash=sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d - # via - # aiohttp - # yarl -networkx==2.8.8 \ - --hash=sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e \ - --hash=sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524 - # via scikit-image -numba==0.56.4 \ - --hash=sha256:0240f9026b015e336069329839208ebd70ec34ae5bfbf402e4fcc8e06197528e \ - --hash=sha256:03634579d10a6129181129de293dd6b5eaabee86881369d24d63f8fe352dd6cb \ - --hash=sha256:03fe94cd31e96185cce2fae005334a8cc712fc2ba7756e52dff8c9400718173f \ - --hash=sha256:0611e6d3eebe4cb903f1a836ffdb2bda8d18482bcd0a0dcc56e79e2aa3fefef5 \ - --hash=sha256:0da583c532cd72feefd8e551435747e0e0fbb3c0530357e6845fcc11e38d6aea \ - --hash=sha256:14dbbabf6ffcd96ee2ac827389afa59a70ffa9f089576500434c34abf9b054a4 \ - --hash=sha256:32d9fef412c81483d7efe0ceb6cf4d3310fde8b624a9cecca00f790573ac96ee \ - --hash=sha256:3a993349b90569518739009d8f4b523dfedd7e0049e6838c0e17435c3e70dcc4 \ - --hash=sha256:3cb1a07a082a61df80a468f232e452d818f5ae254b40c26390054e4e868556e0 \ - --hash=sha256:42f9e1be942b215df7e6cc9948cf9c15bb8170acc8286c063a9e57994ef82fd1 \ - --hash=sha256:4373da9757049db7c90591e9ec55a2e97b2b36ba7ae3bf9c956a513374077470 \ - --hash=sha256:4e08e203b163ace08bad500b0c16f6092b1eb34fd1fce4feaf31a67a3a5ecf3b \ - --hash=sha256:553da2ce74e8862e18a72a209ed3b6d2924403bdd0fb341fa891c6455545ba7c \ - --hash=sha256:720886b852a2d62619ae3900fe71f1852c62db4f287d0c275a60219e1643fc04 \ - --hash=sha256:85dbaed7a05ff96492b69a8900c5ba605551afb9b27774f7f10511095451137c \ - --hash=sha256:8a95ca9cc77ea4571081f6594e08bd272b66060634b8324e99cd1843020364f9 \ - --hash=sha256:91f021145a8081f881996818474ef737800bcc613ffb1e618a655725a0f9e246 \ - --hash=sha256:9f62672145f8669ec08762895fe85f4cf0ead08ce3164667f2b94b2f62ab23c3 \ - --hash=sha256:a12ef323c0f2101529d455cfde7f4135eaa147bad17afe10b48634f796d96abd \ - --hash=sha256:c602d015478b7958408d788ba00a50272649c5186ea8baa6cf71d4a1c761bba1 \ - --hash=sha256:c75e8a5f810ce80a0cfad6e74ee94f9fde9b40c81312949bf356b7304ef20740 \ - --hash=sha256:d0ae9270a7a5cc0ede63cd234b4ff1ce166c7a749b91dbbf45e0000c56d3eade \ - --hash=sha256:d69ad934e13c15684e7887100a8f5f0f61d7a8e57e0fd29d9993210089a5b531 \ - --hash=sha256:dbcc847bac2d225265d054993a7f910fda66e73d6662fe7156452cac0325b073 \ - --hash=sha256:e64d338b504c9394a4a34942df4627e1e6cb07396ee3b49fe7b8d6420aa5104f \ - --hash=sha256:f4cfc3a19d1e26448032049c79fc60331b104f694cf570a9e94f4e2c9d0932bb \ - --hash=sha256:fbfb45e7b297749029cb28694abf437a78695a100e7c2033983d69f0ba2698d4 \ - --hash=sha256:fcdf84ba3ed8124eb7234adfbb8792f311991cbf8aed1cad4b1b1a7ee08380c1 - # via facexlib -numpy==1.23.4 \ - --hash=sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8 \ - --hash=sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735 \ - --hash=sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd \ - --hash=sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810 \ - --hash=sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db \ - --hash=sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962 \ - --hash=sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79 \ - --hash=sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911 \ - --hash=sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d \ - --hash=sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488 \ - --hash=sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5 \ - --hash=sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0 \ - --hash=sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f \ - --hash=sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f \ - --hash=sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2 \ - --hash=sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0 \ - --hash=sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68 \ - --hash=sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3 \ - --hash=sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6 \ - --hash=sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71 \ - --hash=sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894 \ - --hash=sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f \ - --hash=sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329 \ - --hash=sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba \ - --hash=sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c \ - --hash=sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e \ - --hash=sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef \ - --hash=sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7 - # via - # accelerate - # albumentations - # altair - # basicsr - # clean-fid - # clipseg - # contourpy - # diffusers - # facexlib - # filterpy - # gfpgan - # imageio - # matplotlib - # numba - # opencv-python - # opencv-python-headless - # pandas - # pyarrow - # pydeck - # pytorch-lightning - # pywavelets - # qudida - # realesrgan - # scikit-image - # scikit-learn - # scipy - # streamlit - # taming-transformers-rom1504 - # tb-nightly - # tensorboard - # test-tube - # tifffile - # torch-fidelity - # torchmetrics - # torchvision - # transformers -oauthlib==3.2.2 \ - --hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \ - --hash=sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918 - # via requests-oauthlib -omegaconf==2.2.3 \ - --hash=sha256:59ff9fba864ffbb5fb710b64e8a9ba37c68fa339a2e2bb4f1b648d6901552523 \ - --hash=sha256:d6f2cbf79a992899eb76c6cb1aedfcf0fe7456a8654382edd5ee0c1b199c0657 - # via taming-transformers-rom1504 -opencv-python==4.6.0.66 \ - --hash=sha256:0dc82a3d8630c099d2f3ac1b1aabee164e8188db54a786abb7a4e27eba309440 \ - --hash=sha256:5af8ba35a4fcb8913ffb86e92403e9a656a4bff4a645d196987468f0f8947875 \ - --hash=sha256:6e32af22e3202748bd233ed8f538741876191863882eba44e332d1a34993165b \ - --hash=sha256:c5bfae41ad4031e66bb10ec4a0a2ffd3e514d092652781e8b1ac98d1b59f1158 \ - --hash=sha256:dbdc84a9b4ea2cbae33861652d25093944b9959279200b7ae0badd32439f74de \ - --hash=sha256:e6e448b62afc95c5b58f97e87ef84699e6607fe5c58730a03301c52496005cae \ - --hash=sha256:f482e78de6e7b0b060ff994ffd859bddc3f7f382bb2019ef157b0ea8ca8712f5 - # via - # basicsr - # clipseg - # facexlib - # gfpgan - # realesrgan -opencv-python-headless==4.6.0.66 \ - --hash=sha256:21e70f8b0c04098cdf466d27184fe6c3820aaef944a22548db95099959c95889 \ - --hash=sha256:2c032c373e447c3fc2a670bca20e2918a1205a6e72854df60425fd3f82c78c32 \ - --hash=sha256:3bacd806cce1f1988e58f3d6f761538e0215d6621d316de94c009dc0acbd6ad3 \ - --hash=sha256:d5291d7e10aa2c19cab6fd86f0d61af8617290ecd2d7ffcb051e446868d04cc5 \ - --hash=sha256:e6c069bc963d7e8fcec21b3e33e594d35948badd63eccb2e80f88b0a12102c03 \ - --hash=sha256:eec6281054346103d6af93f173b7c6a206beb2663d3adc04aa3ddc66e85093df \ - --hash=sha256:ffbf26fcd697af996408440a93bc69c49c05a36845771f984156dfbeaa95d497 - # via - # albumentations - # qudida -packaging==21.3 \ - --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ - --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 - # via - # accelerate - # huggingface-hub - # kornia - # matplotlib - # pytorch-lightning - # scikit-image - # streamlit - # torchmetrics - # transformers -pandas==1.5.1 \ - --hash=sha256:04e51b01d5192499390c0015630975f57836cc95c7411415b499b599b05c0c96 \ - --hash=sha256:05c527c64ee02a47a24031c880ee0ded05af0623163494173204c5b72ddce658 \ - --hash=sha256:0a78e05ec09731c5b3bd7a9805927ea631fe6f6cb06f0e7c63191a9a778d52b4 \ - --hash=sha256:17da7035d9e6f9ea9cdc3a513161f8739b8f8489d31dc932bc5a29a27243f93d \ - --hash=sha256:249cec5f2a5b22096440bd85c33106b6102e0672204abd2d5c014106459804ee \ - --hash=sha256:2c25e5c16ee5c0feb6cf9d982b869eec94a22ddfda9aa2fbed00842cbb697624 \ - --hash=sha256:32e3d9f65606b3f6e76555bfd1d0b68d94aff0929d82010b791b6254bf5a4b96 \ - --hash=sha256:36aa1f8f680d7584e9b572c3203b20d22d697c31b71189322f16811d4ecfecd3 \ - --hash=sha256:5b0c970e2215572197b42f1cff58a908d734503ea54b326412c70d4692256391 \ - --hash=sha256:5cee0c74e93ed4f9d39007e439debcaadc519d7ea5c0afc3d590a3a7b2edf060 \ - --hash=sha256:669c8605dba6c798c1863157aefde959c1796671ffb342b80fcb80a4c0bc4c26 \ - --hash=sha256:66a1ad667b56e679e06ba73bb88c7309b3f48a4c279bd3afea29f65a766e9036 \ - --hash=sha256:683779e5728ac9138406c59a11e09cd98c7d2c12f0a5fc2b9c5eecdbb4a00075 \ - --hash=sha256:6bb391659a747cf4f181a227c3e64b6d197100d53da98dcd766cc158bdd9ec68 \ - --hash=sha256:81f0674fa50b38b6793cd84fae5d67f58f74c2d974d2cb4e476d26eee33343d0 \ - --hash=sha256:927e59c694e039c75d7023465d311277a1fc29ed7236b5746e9dddf180393113 \ - --hash=sha256:932d2d7d3cab44cfa275601c982f30c2d874722ef6396bb539e41e4dc4618ed4 \ - --hash=sha256:a52419d9ba5906db516109660b114faf791136c94c1a636ed6b29cbfff9187ee \ - --hash=sha256:b156a971bc451c68c9e1f97567c94fd44155f073e3bceb1b0d195fd98ed12048 \ - --hash=sha256:bcf1a82b770b8f8c1e495b19a20d8296f875a796c4fe6e91da5ef107f18c5ecb \ - --hash=sha256:cb2a9cf1150302d69bb99861c5cddc9c25aceacb0a4ef5299785d0f5389a3209 \ - --hash=sha256:d8c709f4700573deb2036d240d140934df7e852520f4a584b2a8d5443b71f54d \ - --hash=sha256:db45b94885000981522fb92349e6b76f5aee0924cc5315881239c7859883117d \ - --hash=sha256:ddf46b940ef815af4e542697eaf071f0531449407a7607dd731bf23d156e20a7 \ - --hash=sha256:e675f8fe9aa6c418dc8d3aac0087b5294c1a4527f1eacf9fe5ea671685285454 \ - --hash=sha256:eb7e8cf2cf11a2580088009b43de84cabbf6f5dae94ceb489f28dba01a17cb77 \ - --hash=sha256:f340331a3f411910adfb4bbe46c2ed5872d9e473a783d7f14ecf49bc0869c594 - # via - # altair - # streamlit - # test-tube -pathtools==0.1.2 \ - --hash=sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0 - # via wandb -picklescan==0.0.5 \ - --hash=sha256:368cf1b9a075bc1b6460ad82b694f260532b836c82f99d13846cd36e1bbe7f9a \ - --hash=sha256:57153eca04d5df5009f2cdd595aef261b8a6f27e03046a1c84f672aa6869c592 - # via -r installer/requirements.in -pillow==9.3.0 \ - --hash=sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040 \ - --hash=sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8 \ - --hash=sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65 \ - --hash=sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2 \ - --hash=sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627 \ - --hash=sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07 \ - --hash=sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef \ - --hash=sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535 \ - --hash=sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c \ - --hash=sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc \ - --hash=sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3 \ - --hash=sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1 \ - --hash=sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c \ - --hash=sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa \ - --hash=sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32 \ - --hash=sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502 \ - --hash=sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4 \ - --hash=sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f \ - --hash=sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812 \ - --hash=sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636 \ - --hash=sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20 \ - --hash=sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c \ - --hash=sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91 \ - --hash=sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe \ - --hash=sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b \ - --hash=sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad \ - --hash=sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9 \ - --hash=sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72 \ - --hash=sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4 \ - --hash=sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de \ - --hash=sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29 \ - --hash=sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee \ - --hash=sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c \ - --hash=sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7 \ - --hash=sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11 \ - --hash=sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c \ - --hash=sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c \ - --hash=sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448 \ - --hash=sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b \ - --hash=sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20 \ - --hash=sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228 \ - --hash=sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd \ - --hash=sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699 \ - --hash=sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b \ - --hash=sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2 \ - --hash=sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4 \ - --hash=sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c \ - --hash=sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f \ - --hash=sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2 \ - --hash=sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c \ - --hash=sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3 \ - --hash=sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193 \ - --hash=sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48 \ - --hash=sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02 \ - --hash=sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8 \ - --hash=sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e \ - --hash=sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f \ - --hash=sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b \ - --hash=sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74 \ - --hash=sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb \ - --hash=sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0 - # via - # basicsr - # clean-fid - # diffusers - # facexlib - # imageio - # k-diffusion - # matplotlib - # realesrgan - # scikit-image - # streamlit - # torch-fidelity - # torchvision -promise==2.3 \ - --hash=sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0 - # via wandb -protobuf==3.19.6 \ - --hash=sha256:010be24d5a44be7b0613750ab40bc8b8cedc796db468eae6c779b395f50d1fa1 \ - --hash=sha256:0469bc66160180165e4e29de7f445e57a34ab68f49357392c5b2f54c656ab25e \ - --hash=sha256:0c0714b025ec057b5a7600cb66ce7c693815f897cfda6d6efb58201c472e3437 \ - --hash=sha256:11478547958c2dfea921920617eb457bc26867b0d1aa065ab05f35080c5d9eb6 \ - --hash=sha256:14082457dc02be946f60b15aad35e9f5c69e738f80ebbc0900a19bc83734a5a4 \ - --hash=sha256:2b2d2913bcda0e0ec9a784d194bc490f5dc3d9d71d322d070b11a0ade32ff6ba \ - --hash=sha256:30a15015d86b9c3b8d6bf78d5b8c7749f2512c29f168ca259c9d7727604d0e39 \ - --hash=sha256:30f5370d50295b246eaa0296533403961f7e64b03ea12265d6dfce3a391d8992 \ - --hash=sha256:347b393d4dd06fb93a77620781e11c058b3b0a5289262f094379ada2920a3730 \ - --hash=sha256:4bc98de3cdccfb5cd769620d5785b92c662b6bfad03a202b83799b6ed3fa1fa7 \ - --hash=sha256:5057c64052a1f1dd7d4450e9aac25af6bf36cfbfb3a1cd89d16393a036c49157 \ - --hash=sha256:559670e006e3173308c9254d63facb2c03865818f22204037ab76f7a0ff70b5f \ - --hash=sha256:5a0d7539a1b1fb7e76bf5faa0b44b30f812758e989e59c40f77a7dab320e79b9 \ - --hash=sha256:5f5540d57a43042389e87661c6eaa50f47c19c6176e8cf1c4f287aeefeccb5c4 \ - --hash=sha256:7a552af4dc34793803f4e735aabe97ffc45962dfd3a237bdde242bff5a3de684 \ - --hash=sha256:84a04134866861b11556a82dd91ea6daf1f4925746b992f277b84013a7cc1229 \ - --hash=sha256:878b4cd080a21ddda6ac6d1e163403ec6eea2e206cf225982ae04567d39be7b0 \ - --hash=sha256:90b0d02163c4e67279ddb6dc25e063db0130fc299aefabb5d481053509fae5c8 \ - --hash=sha256:91d5f1e139ff92c37e0ff07f391101df77e55ebb97f46bbc1535298d72019462 \ - --hash=sha256:a8ce5ae0de28b51dff886fb922012dad885e66176663950cb2344c0439ecb473 \ - --hash=sha256:aa3b82ca1f24ab5326dcf4ea00fcbda703e986b22f3d27541654f749564d778b \ - --hash=sha256:bb6776bd18f01ffe9920e78e03a8676530a5d6c5911934c6a1ac6eb78973ecb6 \ - --hash=sha256:bbf5cea5048272e1c60d235c7bd12ce1b14b8a16e76917f371c718bd3005f045 \ - --hash=sha256:c0ccd3f940fe7f3b35a261b1dd1b4fc850c8fde9f74207015431f174be5976b3 \ - --hash=sha256:d0b635cefebd7a8a0f92020562dead912f81f401af7e71f16bf9506ff3bdbb38 - # via - # streamlit - # tb-nightly - # tensorboard - # wandb -psutil==5.9.3 \ - --hash=sha256:07d880053c6461c9b89cd5d4808f3b8336665fa3acdefd6777662c5ed73a851a \ - --hash=sha256:12500d761ac091f2426567f19f95fd3f15a197d96befb44a5c1e3cbe6db5752c \ - --hash=sha256:1b540599481c73408f6b392cdffef5b01e8ff7a2ac8caae0a91b8222e88e8f1e \ - --hash=sha256:35feafe232d1aaf35d51bd42790cbccb882456f9f18cdc411532902370d660df \ - --hash=sha256:3a7826e68b0cf4ce2c1ee385d64eab7d70e3133171376cac53d7c1790357ec8f \ - --hash=sha256:46907fa62acaac364fff0b8a9da7b360265d217e4fdeaca0a2397a6883dffba2 \ - --hash=sha256:4bd4854f0c83aa84a5a40d3b5d0eb1f3c128f4146371e03baed4589fe4f3c931 \ - --hash=sha256:538fcf6ae856b5e12d13d7da25ad67f02113c96f5989e6ad44422cb5994ca7fc \ - --hash=sha256:547ebb02031fdada635452250ff39942db8310b5c4a8102dfe9384ee5791e650 \ - --hash=sha256:5e8b50241dd3c2ed498507f87a6602825073c07f3b7e9560c58411c14fe1e1c9 \ - --hash=sha256:5fa88e3d5d0b480602553d362c4b33a63e0c40bfea7312a7bf78799e01e0810b \ - --hash=sha256:68fa227c32240c52982cb931801c5707a7f96dd8927f9102d6c7771ea1ff5698 \ - --hash=sha256:6ced1ad823ecfa7d3ce26fe8aa4996e2e53fb49b7fed8ad81c80958501ec0619 \ - --hash=sha256:71b1206e7909792d16933a0d2c1c7f04ae196186c51ba8567abae1d041f06dcb \ - --hash=sha256:767ef4fa33acda16703725c0473a91e1832d296c37c63896c7153ba81698f1ab \ - --hash=sha256:7ccfcdfea4fc4b0a02ca2c31de7fcd186beb9cff8207800e14ab66f79c773af6 \ - --hash=sha256:7e4939ff75149b67aef77980409f156f0082fa36accc475d45c705bb00c6c16a \ - --hash=sha256:828c9dc9478b34ab96be75c81942d8df0c2bb49edbb481f597314d92b6441d89 \ - --hash=sha256:8a4e07611997acf178ad13b842377e3d8e9d0a5bac43ece9bfc22a96735d9a4f \ - --hash=sha256:941a6c2c591da455d760121b44097781bc970be40e0e43081b9139da485ad5b7 \ - --hash=sha256:9a4af6ed1094f867834f5f07acd1250605a0874169a5fcadbcec864aec2496a6 \ - --hash=sha256:9ec296f565191f89c48f33d9544d8d82b0d2af7dd7d2d4e6319f27a818f8d1cc \ - --hash=sha256:9ec95df684583b5596c82bb380c53a603bb051cf019d5c849c47e117c5064395 \ - --hash=sha256:a04a1836894c8279e5e0a0127c0db8e198ca133d28be8a2a72b4db16f6cf99c1 \ - --hash=sha256:a3d81165b8474087bb90ec4f333a638ccfd1d69d34a9b4a1a7eaac06648f9fbe \ - --hash=sha256:b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71 \ - --hash=sha256:ba38cf9984d5462b506e239cf4bc24e84ead4b1d71a3be35e66dad0d13ded7c1 \ - --hash=sha256:beb57d8a1ca0ae0eb3d08ccaceb77e1a6d93606f0e1754f0d60a6ebd5c288837 \ - --hash=sha256:d266cd05bd4a95ca1c2b9b5aac50d249cf7c94a542f47e0b22928ddf8b80d1ef \ - --hash=sha256:d8c3cc6bb76492133474e130a12351a325336c01c96a24aae731abf5a47fe088 \ - --hash=sha256:db8e62016add2235cc87fb7ea000ede9e4ca0aa1f221b40cef049d02d5d2593d \ - --hash=sha256:e7507f6c7b0262d3e7b0eeda15045bf5881f4ada70473b87bc7b7c93b992a7d7 \ - --hash=sha256:ed15edb14f52925869250b1375f0ff58ca5c4fa8adefe4883cfb0737d32f5c02 \ - --hash=sha256:f57d63a2b5beaf797b87024d018772439f9d3103a395627b77d17a8d72009543 \ - --hash=sha256:fa5e32c7d9b60b2528108ade2929b115167fe98d59f89555574715054f50fa31 \ - --hash=sha256:fe79b4ad4836e3da6c4650cb85a663b3a51aef22e1a829c384e18fae87e5e727 - # via - # accelerate - # wandb -pyarrow==10.0.0 \ - --hash=sha256:10e031794d019425d34406edffe7e32157359e9455f9edb97a1732f8dabf802f \ - --hash=sha256:25f51dca780fc22cfd7ac30f6bdfe70eb99145aee9acfda987f2c49955d66ed9 \ - --hash=sha256:2d326a9d47ac237d81b8c4337e9d30a0b361835b536fc7ea53991455ce761fbd \ - --hash=sha256:3d2694f08c8d4482d14e3798ff036dbd81ae6b1c47948f52515e1aa90fbec3f0 \ - --hash=sha256:4051664d354b14939b5da35cfa77821ade594bc1cf56dd2032b3068c96697d74 \ - --hash=sha256:511735040b83f2993f78d7fb615e7b88253d75f41500e87e587c40156ff88120 \ - --hash=sha256:65d4a312f3ced318423704355acaccc7f7bdfe242472e59bdd54aa0f8837adf8 \ - --hash=sha256:68ccb82c04c0f7abf7a95541d5e9d9d94290fc66a2d36d3f6ea0777f40c15654 \ - --hash=sha256:69b8a1fd99201178799b02f18498633847109b701856ec762f314352a431b7d0 \ - --hash=sha256:758284e1ebd3f2a9abb30544bfec28d151a398bb7c0f2578cbca5ee5b000364a \ - --hash=sha256:7be7f42f713068293308c989a4a3a2de03b70199bdbe753901c6595ff8640c64 \ - --hash=sha256:7ce026274cd5d9934cd3694e89edecde4e036018bbc6cb735fd33b9e967e7d47 \ - --hash=sha256:7e6b837cc44cd62a0e280c8fc4de94ebce503d6d1190e6e94157ab49a8bea67b \ - --hash=sha256:b153b05765393557716e3729cf988442b3ae4f5567364ded40d58c07feed27c2 \ - --hash=sha256:b3e3148468d3eed3779d68241f1d13ed4ee7cca4c6dbc7c07e5062b93ad4da33 \ - --hash=sha256:b45f969ed924282e9d4ede38f3430630d809c36dbff65452cabce03141943d28 \ - --hash=sha256:b9f63ceb8346aac0bcb487fafe9faca642ad448ca649fcf66a027c6e120cbc12 \ - --hash=sha256:c79300e1a3e23f2bf4defcf0d70ff5ea25ef6ebf6f121d8670ee14bb662bb7ca \ - --hash=sha256:d45a59e2f47826544c0ca70bc0f7ed8ffa5ad23f93b0458230c7e983bcad1acf \ - --hash=sha256:e4c6da9f9e1ff96781ee1478f7cc0860e66c23584887b8e297c4b9905c3c9066 \ - --hash=sha256:f329951d56b3b943c353f7b27c894e02367a7efbb9fef7979c6b24e02dbfcf55 \ - --hash=sha256:f76157d9579571c865860e5fd004537c03e21139db76692d96fd8a186adab1f2 - # via streamlit -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 - # via google-auth -pydeck==0.8.0 \ - --hash=sha256:07edde833f7cfcef6749124351195aa7dcd24663d4909fd7898dbd0b6fbc01ec \ - --hash=sha256:a8fa7757c6f24bba033af39db3147cb020eef44012ba7e60d954de187f9ed4d5 - # via streamlit -pydeprecate==0.3.2 \ - --hash=sha256:d481116cc5d7f6c473e7c4be820efdd9b90a16b594b350276e9e66a6cb5bdd29 \ - --hash=sha256:ed86b68ed837e6465245904a3de2f59bf9eef78ac7a2502ee280533d04802457 - # via pytorch-lightning -pygments==2.13.0 \ - --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ - --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 - # via rich -pympler==1.0.1 \ - --hash=sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa \ - --hash=sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d - # via streamlit -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc - # via - # matplotlib - # packaging -pyreadline3==3.4.1 \ - --hash=sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae \ - --hash=sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb - # via -r installer/requirements.in -pyrsistent==0.19.2 \ - --hash=sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed \ - --hash=sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb \ - --hash=sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a \ - --hash=sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95 \ - --hash=sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712 \ - --hash=sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73 \ - --hash=sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41 \ - --hash=sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b \ - --hash=sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78 \ - --hash=sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab \ - --hash=sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308 \ - --hash=sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425 \ - --hash=sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2 \ - --hash=sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e \ - --hash=sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6 \ - --hash=sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2 \ - --hash=sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a \ - --hash=sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291 \ - --hash=sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584 \ - --hash=sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a \ - --hash=sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0 \ - --hash=sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770 - # via jsonschema -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 - # via - # matplotlib - # pandas - # streamlit -python-engineio==4.3.4 \ - --hash=sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c \ - --hash=sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae - # via python-socketio -python-socketio==5.7.2 \ - --hash=sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097 \ - --hash=sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3 - # via flask-socketio -pytorch-lightning==1.7.7 \ - --hash=sha256:27c2dd01a18db2415168e3fa3775ccb5a1fa1e2961a50439ad9365507fe9d4ae \ - --hash=sha256:4438b8284d7f7fdb06cf3566a7b5b6f102ac8971cf7bb6d3f1b1de64628241f3 - # via taming-transformers-rom1504 -pytz==2022.6 \ - --hash=sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427 \ - --hash=sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2 - # via pandas -pytz-deprecation-shim==0.1.0.post0 \ - --hash=sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6 \ - --hash=sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d - # via tzlocal -pywavelets==1.4.1 \ - --hash=sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b \ - --hash=sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4 \ - --hash=sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4 \ - --hash=sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6 \ - --hash=sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de \ - --hash=sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c \ - --hash=sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa \ - --hash=sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93 \ - --hash=sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875 \ - --hash=sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd \ - --hash=sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356 \ - --hash=sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1 \ - --hash=sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c \ - --hash=sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2 \ - --hash=sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784 \ - --hash=sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4 \ - --hash=sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b \ - --hash=sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc \ - --hash=sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966 \ - --hash=sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e \ - --hash=sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426 \ - --hash=sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c \ - --hash=sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202 \ - --hash=sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc \ - --hash=sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd - # via scikit-image -pyyaml==6.0 \ - --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \ - --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \ - --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \ - --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \ - --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \ - --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \ - --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \ - --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \ - --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \ - --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \ - --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \ - --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \ - --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \ - --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \ - --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \ - --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \ - --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \ - --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \ - --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \ - --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \ - --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \ - --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \ - --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \ - --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \ - --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \ - --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \ - --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \ - --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \ - --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \ - --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \ - --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \ - --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \ - --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \ - --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \ - --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \ - --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \ - --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \ - --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \ - --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \ - --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5 - # via - # accelerate - # albumentations - # basicsr - # gfpgan - # huggingface-hub - # omegaconf - # pytorch-lightning - # transformers - # wandb -qudida==0.0.4 \ - --hash=sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6 \ - --hash=sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8 - # via albumentations -realesrgan==0.3.0 \ - --hash=sha256:0d36da96ab9f447071606e91f502ccdfb08f80cc82ee4f8caf720c7745ccec7e \ - --hash=sha256:59336c16c30dd5130eff350dd27424acb9b7281d18a6810130e265606c9a6088 - # via -r installer/requirements.in -regex==2022.10.31 \ - --hash=sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad \ - --hash=sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4 \ - --hash=sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd \ - --hash=sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc \ - --hash=sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d \ - --hash=sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066 \ - --hash=sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec \ - --hash=sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9 \ - --hash=sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e \ - --hash=sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8 \ - --hash=sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e \ - --hash=sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783 \ - --hash=sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6 \ - --hash=sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1 \ - --hash=sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c \ - --hash=sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4 \ - --hash=sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1 \ - --hash=sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1 \ - --hash=sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7 \ - --hash=sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8 \ - --hash=sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe \ - --hash=sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d \ - --hash=sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b \ - --hash=sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8 \ - --hash=sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c \ - --hash=sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af \ - --hash=sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49 \ - --hash=sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714 \ - --hash=sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542 \ - --hash=sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318 \ - --hash=sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e \ - --hash=sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5 \ - --hash=sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc \ - --hash=sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144 \ - --hash=sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453 \ - --hash=sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5 \ - --hash=sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61 \ - --hash=sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11 \ - --hash=sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a \ - --hash=sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54 \ - --hash=sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73 \ - --hash=sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc \ - --hash=sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347 \ - --hash=sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c \ - --hash=sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66 \ - --hash=sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c \ - --hash=sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93 \ - --hash=sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443 \ - --hash=sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc \ - --hash=sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1 \ - --hash=sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892 \ - --hash=sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8 \ - --hash=sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001 \ - --hash=sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa \ - --hash=sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90 \ - --hash=sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c \ - --hash=sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0 \ - --hash=sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692 \ - --hash=sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4 \ - --hash=sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5 \ - --hash=sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690 \ - --hash=sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83 \ - --hash=sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66 \ - --hash=sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f \ - --hash=sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f \ - --hash=sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4 \ - --hash=sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee \ - --hash=sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81 \ - --hash=sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95 \ - --hash=sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9 \ - --hash=sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff \ - --hash=sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e \ - --hash=sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5 \ - --hash=sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6 \ - --hash=sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7 \ - --hash=sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1 \ - --hash=sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394 \ - --hash=sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6 \ - --hash=sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742 \ - --hash=sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57 \ - --hash=sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b \ - --hash=sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7 \ - --hash=sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b \ - --hash=sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244 \ - --hash=sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af \ - --hash=sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185 \ - --hash=sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8 \ - --hash=sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5 - # via - # clip - # diffusers - # transformers -requests==2.25.1 \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e - # via - # basicsr - # clean-fid - # diffusers - # fsspec - # huggingface-hub - # requests-oauthlib - # streamlit - # tb-nightly - # tensorboard - # torchvision - # transformers - # wandb -requests-oauthlib==1.3.1 \ - --hash=sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5 \ - --hash=sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a - # via google-auth-oauthlib -resize-right==0.0.2 \ - --hash=sha256:78351ca3eda0872208fcbc90861b45de559f90fb4027ce41825fdeb9b995005c \ - --hash=sha256:7dc35b72ce4012b77f7cc9049835163793ab98a58ab8893610fb119fe59af520 - # via k-diffusion -rich==12.6.0 \ - --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ - --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 - # via streamlit -rsa==4.9 \ - --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ - --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 - # via google-auth -scikit-image==0.19.3 \ - --hash=sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099 \ - --hash=sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d \ - --hash=sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790 \ - --hash=sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450 \ - --hash=sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef \ - --hash=sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245 \ - --hash=sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919 \ - --hash=sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6 \ - --hash=sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8 \ - --hash=sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d \ - --hash=sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19 \ - --hash=sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2 \ - --hash=sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92 \ - --hash=sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced \ - --hash=sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e \ - --hash=sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732 \ - --hash=sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34 \ - --hash=sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83 \ - --hash=sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f \ - --hash=sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9 \ - --hash=sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe \ - --hash=sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7 \ - --hash=sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b \ - --hash=sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6 \ - --hash=sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc \ - --hash=sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5 - # via - # albumentations - # basicsr - # k-diffusion -scikit-learn==1.1.3 \ - --hash=sha256:23fb9e74b813cc2528b5167d82ed08950b11106ccf50297161875e45152fb311 \ - --hash=sha256:250da993701da88bf475e7c5746abf1285ea0ae47e4d0917cd13afd6600bb162 \ - --hash=sha256:28b2bd6a1419acd522ff45d282c8ba23dbccb5338802ab0ee12baa4ade0aba4c \ - --hash=sha256:2ee2c649f2231b68511aabb0dc827edd8936aad682acc6263c34aed11bc95dac \ - --hash=sha256:30e27721adc308e8fd9f419f43068e43490005f911edf4476a9e585059fa8a83 \ - --hash=sha256:38814f66285318f2e241305cca545eaa9b4126c65aa5dd78c69371f235f78e2b \ - --hash=sha256:4d3a19166d4e1cdfcab975c68f471e046ce01e74c42a9a33fa89a14c2fcedf60 \ - --hash=sha256:5699cded6c0685426433c7e5afe0fecad80ec831ec7fa264940e50c796775cc5 \ - --hash=sha256:6785b8a3093329bf90ac01801be5525551728ae73edb11baa175df660820add4 \ - --hash=sha256:6d1c1394e38a3319ace620381f6f23cc807d8780e9915c152449a86fc8f1db21 \ - --hash=sha256:701181792a28c82fecae12adb5d15d0ecf57bffab7cf4bdbb52c7b3fd428d540 \ - --hash=sha256:748f2bd632d6993e8918d43f1a26c380aeda4e122a88840d4c3a9af99d4239fe \ - --hash=sha256:8e9dd76c7274055d1acf4526b8efb16a3531c26dcda714a0c16da99bf9d41900 \ - --hash=sha256:bef51978a51ec19977700fe7b86aecea49c825884f3811756b74a3b152bb4e35 \ - --hash=sha256:cd55c6fbef7608dbce1f22baf289dfcc6eb323247daa3c3542f73d389c724786 \ - --hash=sha256:da5a2e95fef9805b1750e4abda4e834bf8835d26fc709a391543b53feee7bd0e \ - --hash=sha256:ee47f68d973cee7009f06edb956f2f5588a0f230f24a2a70175fd0ecf36e2653 \ - --hash=sha256:f4931f2a6c06e02c6c17a05f8ae397e2545965bc7a0a6cb38c8cd7d4fba8624d \ - --hash=sha256:f5644663987ee221f5d1f47a593271b966c271c236fe05634e6bdc06041b5a2b \ - --hash=sha256:f5d4231af7199531e77da1b78a4cc6b3d960a00b1ec672578ac818aae2b9c35d \ - --hash=sha256:fd3ee69d36d42a7dcbb17e355a5653af5fd241a7dfd9133080b3dde8d9e2aafb - # via qudida -scipy==1.9.3 \ - --hash=sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31 \ - --hash=sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108 \ - --hash=sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0 \ - --hash=sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b \ - --hash=sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e \ - --hash=sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e \ - --hash=sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5 \ - --hash=sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840 \ - --hash=sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58 \ - --hash=sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523 \ - --hash=sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd \ - --hash=sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab \ - --hash=sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c \ - --hash=sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb \ - --hash=sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096 \ - --hash=sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0 \ - --hash=sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc \ - --hash=sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9 \ - --hash=sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c \ - --hash=sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95 \ - --hash=sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027 - # via - # albumentations - # basicsr - # clean-fid - # clipseg - # facexlib - # filterpy - # gfpgan - # k-diffusion - # scikit-image - # scikit-learn - # torch-fidelity - # torchdiffeq -semver==2.13.0 \ - --hash=sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4 \ - --hash=sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f - # via streamlit -send2trash==1.8.0 \ - --hash=sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d \ - --hash=sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08 - # via -r installer/requirements.in -sentry-sdk==1.10.1 \ - --hash=sha256:06c0fa9ccfdc80d7e3b5d2021978d6eb9351fa49db9b5847cf4d1f2a473414ad \ - --hash=sha256:105faf7bd7b7fa25653404619ee261527266b14103fe1389e0ce077bd23a9691 - # via wandb -setproctitle==1.3.2 \ - --hash=sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b \ - --hash=sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9 \ - --hash=sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31 \ - --hash=sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83 \ - --hash=sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f \ - --hash=sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca \ - --hash=sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494 \ - --hash=sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe \ - --hash=sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172 \ - --hash=sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084 \ - --hash=sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4 \ - --hash=sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1 \ - --hash=sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c \ - --hash=sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973 \ - --hash=sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c \ - --hash=sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202 \ - --hash=sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5 \ - --hash=sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65 \ - --hash=sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18 \ - --hash=sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13 \ - --hash=sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005 \ - --hash=sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02 \ - --hash=sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7 \ - --hash=sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665 \ - --hash=sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f \ - --hash=sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1 \ - --hash=sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989 \ - --hash=sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827 \ - --hash=sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42 \ - --hash=sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927 \ - --hash=sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122 \ - --hash=sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9 \ - --hash=sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f \ - --hash=sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d \ - --hash=sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98 \ - --hash=sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796 \ - --hash=sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb \ - --hash=sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd \ - --hash=sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe \ - --hash=sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806 \ - --hash=sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484 \ - --hash=sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e \ - --hash=sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575 \ - --hash=sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76 \ - --hash=sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246 \ - --hash=sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb \ - --hash=sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099 \ - --hash=sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c \ - --hash=sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258 \ - --hash=sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865 \ - --hash=sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b \ - --hash=sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d \ - --hash=sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10 \ - --hash=sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f \ - --hash=sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94 \ - --hash=sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6 \ - --hash=sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963 \ - --hash=sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4 \ - --hash=sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8 \ - --hash=sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761 - # via wandb -shortuuid==1.0.9 \ - --hash=sha256:459f12fa1acc34ff213b1371467c0325169645a31ed989e268872339af7563d5 \ - --hash=sha256:b2bb9eb7773170e253bb7ba25971023acb473517a8b76803d9618668cb1dd46f - # via wandb -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # docker-pycreds - # eventlet - # flask-cors - # google-auth - # grpcio - # promise - # python-dateutil - # validators - # wandb -smmap==5.0.0 \ - --hash=sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94 \ - --hash=sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936 - # via gitdb -streamlit==1.14.0 \ - --hash=sha256:62556d873567e1b3427bcd118a57ee6946619f363bd6bba38df2d1f8225ecba0 \ - --hash=sha256:e078b8143d150ba721bdb9194218e311c5fe1d6d4156473a2dea6cc848a6c9fc - # via -r installer/requirements.in -taming-transformers-rom1504==0.0.6 \ - --hash=sha256:051b5804c58caa247bcd51d17ddb525b4d5f892a29d42dc460f40e3e9e34e5d8 \ - --hash=sha256:73fe5fc1108accee4236ee6976e0987ab236afad0af06cb9f037641a908d2c32 - # via -r installer/requirements.in -tb-nightly==2.11.0a20221106 \ - --hash=sha256:8940457ee42db92f01da8bcdbbea1a476735eda559dde5976f5728919960af4a - # via - # basicsr - # gfpgan -tensorboard==2.10.1 \ - --hash=sha256:fb9222c1750e2fa35ef170d998a1e229f626eeced3004494a8849c88c15d8c1c - # via - # pytorch-lightning - # test-tube -tensorboard-data-server==0.6.1 \ - --hash=sha256:809fe9887682d35c1f7d1f54f0f40f98bb1f771b14265b453ca051e2ce58fca7 \ - --hash=sha256:d8237580755e58eff68d1f3abefb5b1e39ae5c8b127cc40920f9c4fb33f4b98a \ - --hash=sha256:fa8cef9be4fcae2f2363c88176638baf2da19c5ec90addb49b1cde05c95c88ee - # via - # tb-nightly - # tensorboard -tensorboard-plugin-wit==1.8.1 \ - --hash=sha256:ff26bdd583d155aa951ee3b152b3d0cffae8005dc697f72b44a8e8c2a77a8cbe - # via - # tb-nightly - # tensorboard -test-tube==0.7.5 \ - --hash=sha256:1379c33eb8cde3e9b36610f87da0f16c2e06496b1cfebac473df4e7be2faa124 - # via -r installer/requirements.in -threadpoolctl==3.1.0 \ - --hash=sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b \ - --hash=sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380 - # via scikit-learn -tifffile==2022.10.10 \ - --hash=sha256:50b61ba943b866d191295bc38a00191c9fdab23ece063544c7f1a264e3f6aa8e \ - --hash=sha256:87f3aee8a0d06b74655269a105de75c1958a24653e1930d523eb516100043503 - # via scikit-image -tokenizers==0.13.1 \ - --hash=sha256:0a3412830ad66a643723d6b0fc3202e64e9e299bd9c9eda04b2914b5b1e0ddb0 \ - --hash=sha256:126bcb18a77cf65961ece04f54bd10ef3713412195e543d9d3eda2f0e4fd677c \ - --hash=sha256:16434c61c5eb72f6692b9bc52052b07ca92d3eba9dd72a1bc371890e1bdc3f07 \ - --hash=sha256:1d4acfdb6e7ef974677bb8445462db7fed240185fdc0f5b061db357d4ef8d85d \ - --hash=sha256:3333d1cee5c8f47c96362ea0abc1f81c77c9b92c6c3d11cbf1d01985f0d5cf1d \ - --hash=sha256:3acf3cae4c4739fc9ec49fa0e6cce224c1aa0fabc8f8d1358fd7de5c7d49cdca \ - --hash=sha256:3ba43b3f6f6b41c97c1d041785342cd72ba142999f6c4605d628e8e937398f20 \ - --hash=sha256:3c69a8389fd88bc32115e99db70f63bef577ba5c54f40a632580038a49612856 \ - --hash=sha256:3de653a551cc616a442a123da21706cb3a3163cf6919973f978f0921eee1bdf0 \ - --hash=sha256:4b3be8af87b357340b9b049d28067287b5e5e296e3120b6e4875d3b8469b67e6 \ - --hash=sha256:680bc0e357b7da6d0d01634bffbd002e866fdaccde303e1d1af58f32464cf308 \ - --hash=sha256:70de69681a264a5808d39f4bb6331be9a4dec51fd48cd1b959a94da76c4939cc \ - --hash=sha256:73198cda6e1d991c583ed798514863e16763aa600eb7aa6df7722373290575b2 \ - --hash=sha256:80864f456f715829f901ad5bb85af97e9ae52fc902270944804f6476ab8c6699 \ - --hash=sha256:80b9552295fdce0a2513dcb795a3f8591eca1a8dcf8afe0de3214209e6924ad1 \ - --hash=sha256:84fa41b58a8d3b7363ecdf3397d4b38f345fcf7d4dd14565b4360e7bffc9cae0 \ - --hash=sha256:890d2139100b5c8ac6d585438d5e145ede1d7b32b4090a6c078db6db0ef1daea \ - --hash=sha256:8b3f97041f7716998e474d3c7ffd19ac6941f117616696aef2b5ba927bf091e3 \ - --hash=sha256:910479e92d5fbdf91e8106b4c658fd43d418893d7cfd5fb11983c54a1ff53869 \ - --hash=sha256:96a1beef1e64d44597627f4e29d794047a66ad4d7474d93daf5a0ee27928e012 \ - --hash=sha256:98bef54cf51ac335fda1408112df7ff3e584107633bd9066616033e12b0bd519 \ - --hash=sha256:afcb1bd6d9ed59d5c8e633765589cab12f98aae09804f156b5965b4463b8b8e3 \ - --hash=sha256:b72dec85488af3e1e8d58fb4b86b5dbe5171c176002b5e098ea6d52a70004bb5 \ - --hash=sha256:c3109ba62bea56c68c7c2a976250b040afee61b5f86fc791f17afaa2a09fce94 \ - --hash=sha256:c73b9e6c107e980e65077b89c54311d8d645f6a9efdde82990777fa43c0a8cae \ - --hash=sha256:d8fca8b492a4697b0182e0c40b164cb0c44a9669d9c98033fec2f88174605eb0 \ - --hash=sha256:db6872294339bf35c158219fc65bad939ba87d14c936ae7a33a3ca2d1532c5b1 \ - --hash=sha256:e1a90bc97f53600f52e902f3ae097424de712d8ae0e42d957efc7ed908573a20 \ - --hash=sha256:f75f476fe183c03c515a0f0f5d195cb05d93fcdc76e31fe3c9753d01f3ee990b \ - --hash=sha256:fd17b14f84bec0b171869abd17ca0d9bfc564aa1e7f3451f44da526949a911c1 \ - --hash=sha256:fea71780b66f8c278ebae7221c8959404cf7343b8d2f4b7308aa668cf6f02364 - # via transformers -toml==0.10.2 \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f - # via streamlit -toolz==0.12.0 \ - --hash=sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f \ - --hash=sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194 - # via altair -torch==1.12.0 ; platform_system == "Darwin" \ - --hash=sha256:0399746f83b4541bcb5b219a18dbe8cade760aba1c660d2748a38c6dc338ebc7 \ - --hash=sha256:0986685f2ec8b7c4d3593e8cfe96be85d462943f1a8f54112fc48d4d9fbbe903 \ - --hash=sha256:13c7cca6b2ea3704d775444f02af53c5f072d145247e17b8cd7813ac57869f03 \ - --hash=sha256:201abf43a99bb4980cc827dd4b38ac28f35e4dddac7832718be3d5479cafd2c1 \ - --hash=sha256:2143d5fe192fd908b70b494349de5b1ac02854a8a902bd5f47d13d85b410e430 \ - --hash=sha256:2568f011dddeb5990d8698cc375d237f14568ffa8489854e3b94113b4b6b7c8b \ - --hash=sha256:3322d33a06e440d715bb214334bd41314c94632d9a2f07d22006bf21da3a2be4 \ - --hash=sha256:349ea3ba0c0e789e0507876c023181f13b35307aebc2e771efd0e045b8e03e84 \ - --hash=sha256:44a3804e9bb189574f5d02ccc2dc6e32e26a81b3e095463b7067b786048c6072 \ - --hash=sha256:5ed69d5af232c5c3287d44cef998880dadcc9721cd020e9ae02f42e56b79c2e4 \ - --hash=sha256:60d06ee2abfa85f10582d205404d52889d69bcbb71f7e211cfc37e3957ac19ca \ - --hash=sha256:63341f96840a223f277e498d2737b39da30d9f57c7a1ef88857b920096317739 \ - --hash=sha256:72207b8733523388c49d43ffcc4416d1d8cd64c40f7826332e714605ace9b1d2 \ - --hash=sha256:7ddb167827170c4e3ff6a27157414a00b9fef93dea175da04caf92a0619b7aee \ - --hash=sha256:844f1db41173b53fe40c44b3e04fcca23a6ce00ac328b7099f2800e611766845 \ - --hash=sha256:a1325c9c28823af497cbf443369bddac9ac59f67f1e600f8ab9b754958e55b76 \ - --hash=sha256:abbdc5483359b9495dc76e3bd7911ccd2ddc57706c117f8316832e31590af871 \ - --hash=sha256:c0313438bc36448ffd209f5fb4e5f325b3af158cdf61c8829b8ddaf128c57816 \ - --hash=sha256:e3e8348edca3e3cee5a67a2b452b85c57712efe1cc3ffdb87c128b3dde54534e \ - --hash=sha256:fb47291596677570246d723ee6abbcbac07eeba89d8f83de31e3954f21f44879 - # via - # -r installer/requirements.in - # accelerate - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # kornia - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # test-tube - # torch-fidelity - # torchdiffeq - # torchmetrics - # torchvision -torch-fidelity==0.3.0 \ - --hash=sha256:3d3e33db98919759cc4f3f24cb27e1e74bdc7c905d90a780630e4e1c18492b66 \ - --hash=sha256:d01284825595feb7dc3eae3dc9a0d8ced02be764813a3483f109bc142b52a1d3 - # via -r installer/requirements.in -torchdiffeq==0.2.3 \ - --hash=sha256:b5b01ec1294a2d8d5f77e567bf17c5de1237c0573cb94deefa88326f0e18c338 \ - --hash=sha256:fe75f434b9090ac0c27702e02bed21472b0f87035be6581f51edc5d4013ea31a - # via k-diffusion -torchmetrics==0.10.2 \ - --hash=sha256:43757d82266969906fc74b6e80766fcb2a0d52d6c3d09e3b7c98cf3b733fd20c \ - --hash=sha256:daa29d96bff5cff04d80eec5b9f5076993d6ac9c2d2163e88b6b31f8d38f7c25 - # via pytorch-lightning -torchvision==0.13.0 ; platform_system == "Darwin" \ - --hash=sha256:01e9e7b2e7724e66561e8d98f900985d80191e977c5c0b3f33ed31800ba0210c \ - --hash=sha256:0e28740bd5695076f7c449af650fc474d6566722d446461c2ceebf9c9599b37f \ - --hash=sha256:1b703701f0b99f307ad925b1abda2b3d5bdbf30643ff02102b6aeeb8840ae278 \ - --hash=sha256:1e2049f1207631d42d743205f663f1d2235796565be3f18b0339d479626faf30 \ - --hash=sha256:253eb0c67bf88cef4a79ec69058c3e94f9fde28b9e3699ad1afc0b3ed50f8075 \ - --hash=sha256:42d95ab197d090efc5669fec02fbc603d05c859e50ca2c60180d1a113aa9b3e2 \ - --hash=sha256:5c31e9b3004142dbfdf32adc4cf2d4fd709b820833e9786f839ae3a91ff65ef0 \ - --hash=sha256:61d5093a50b7923a4e5bf9e0271001c29e01abec2348b7dd93370a0a9d15836c \ - --hash=sha256:667cac55afb13cda7d362466e7eba3119e529b210e55507d231bead09aca5e1f \ - --hash=sha256:6c4c35428c758adc485ff8f239b5ed68c1b6c26efa261a52e431cab0f7f22aec \ - --hash=sha256:83a4d9d50787d1e886c94486b63b15978391f6cf1892fce6a93132c09b14e128 \ - --hash=sha256:a20662c11dc14fd4eff102ceb946a7ee80b9f98303bb52435cc903f2c4c1fe10 \ - --hash=sha256:acb72a40e5dc0cd454d28514dbdd589a5057afd9bb5c785b87a54718b999bfa1 \ - --hash=sha256:ad458146aca15f652f9b0c227bebd5403602c7341f15f68f20ec119fa8e8f4a5 \ - --hash=sha256:ada295dbfe55017b02acfab960a997387f5addbadd28ee5e575e24f692992ce4 \ - --hash=sha256:b620a43df4131ad09f5761c415a016a9ea95aaf8ec8c91d030fb59bad591094a \ - --hash=sha256:b7a2c9aebc7ef265777fe7e82577364288d98cf6b8cf0a63bb2621df78a7af1a \ - --hash=sha256:c2278a189663087bb8e65915062aa7a25b8f8e5a3cfaa5879fe277e23e4bbf40 \ - --hash=sha256:df16abf31e7a5fce8db1f781bf1e4f20c8bc730c7c3f657e946cc5820c04e465 - # via - # -r installer/requirements.in - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity -tornado==6.2 \ - --hash=sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca \ - --hash=sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72 \ - --hash=sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23 \ - --hash=sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8 \ - --hash=sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b \ - --hash=sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9 \ - --hash=sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13 \ - --hash=sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75 \ - --hash=sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac \ - --hash=sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e \ - --hash=sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b - # via streamlit -tqdm==4.64.1 \ - --hash=sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4 \ - --hash=sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1 - # via - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # huggingface-hub - # k-diffusion - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity - # transformers -transformers==4.24.0 \ - --hash=sha256:486f353a8e594002e48be0e2aba723d96eda839e63bfe274702a4b5eda85559b \ - --hash=sha256:b7ab50039ef9bf817eff14ab974f306fd20a72350bdc9df3a858fd009419322e - # via -r installer/requirements.in -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e - # via - # huggingface-hub - # pytorch-lightning - # qudida - # streamlit - # torch - # torchvision -tzdata==2022.6 \ - --hash=sha256:04a680bdc5b15750c39c12a448885a51134a27ec9af83667663f0b3a1bf3f342 \ - --hash=sha256:91f11db4503385928c15598c98573e3af07e7229181bee5375bd30f1695ddcae - # via pytz-deprecation-shim -tzlocal==4.2 \ - --hash=sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745 \ - --hash=sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7 - # via streamlit -urllib3==1.26.12 \ - --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ - --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 - # via - # requests - # sentry-sdk -validators==0.18.2 \ - --hash=sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd \ - --hash=sha256:37cd9a9213278538ad09b5b9f9134266e7c226ab1fede1d500e29e0a8fbb9ea6 - # via streamlit -wandb==0.13.5 \ - --hash=sha256:11f30a22e30abaa9c187e8b6aa4c12d76160b40bbe98a6f14b0dde9297bbfbe2 \ - --hash=sha256:60d5bcc524b8a314c8e072c03f7702dbd5406261b00a4ce75e7556b805fdc765 - # via k-diffusion -wcwidth==0.2.5 \ - --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ - --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 - # via ftfy -werkzeug==2.2.2 \ - --hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \ - --hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5 - # via - # flask - # tb-nightly - # tensorboard -wheel==0.38.2 \ - --hash=sha256:3d492ef22379a156ec923d2a77051cedfd4df4b667864e9e41ba83f0b70b7149 \ - --hash=sha256:7a5a3095dceca97a3cac869b8fef4e89b83fafde21b6688f47b6fda7600eb441 - # via - # tb-nightly - # tensorboard -whichcraft==0.6.1 \ - --hash=sha256:acdbb91b63d6a15efbd6430d1d7b2d36e44a71697e93e19b7ded477afd9fce87 \ - --hash=sha256:deda9266fbb22b8c64fd3ee45c050d61139cd87419765f588e37c8d23e236dd9 - # via flaskwebgui -yapf==0.32.0 \ - --hash=sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32 \ - --hash=sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b - # via - # basicsr - # gfpgan -yarl==1.8.1 \ - --hash=sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb \ - --hash=sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3 \ - --hash=sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035 \ - --hash=sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453 \ - --hash=sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d \ - --hash=sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a \ - --hash=sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231 \ - --hash=sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f \ - --hash=sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae \ - --hash=sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b \ - --hash=sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3 \ - --hash=sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507 \ - --hash=sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd \ - --hash=sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae \ - --hash=sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe \ - --hash=sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c \ - --hash=sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4 \ - --hash=sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64 \ - --hash=sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357 \ - --hash=sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54 \ - --hash=sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461 \ - --hash=sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4 \ - --hash=sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497 \ - --hash=sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0 \ - --hash=sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1 \ - --hash=sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957 \ - --hash=sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350 \ - --hash=sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780 \ - --hash=sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843 \ - --hash=sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548 \ - --hash=sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6 \ - --hash=sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40 \ - --hash=sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee \ - --hash=sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b \ - --hash=sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6 \ - --hash=sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0 \ - --hash=sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e \ - --hash=sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880 \ - --hash=sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc \ - --hash=sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e \ - --hash=sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead \ - --hash=sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28 \ - --hash=sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf \ - --hash=sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd \ - --hash=sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae \ - --hash=sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0 \ - --hash=sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0 \ - --hash=sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae \ - --hash=sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda \ - --hash=sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546 \ - --hash=sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802 \ - --hash=sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be \ - --hash=sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07 \ - --hash=sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936 \ - --hash=sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272 \ - --hash=sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc \ - --hash=sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a \ - --hash=sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28 \ - --hash=sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b - # via aiohttp -zipp==3.10.0 \ - --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ - --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -setuptools==65.5.1 \ - --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ - --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f - # via - # numba - # tb-nightly - # tensorboard - # wandb diff --git a/binary_installer/py3.10-linux-x86_64-cuda-reqs.txt b/binary_installer/py3.10-linux-x86_64-cuda-reqs.txt deleted file mode 100644 index ce528843e5..0000000000 --- a/binary_installer/py3.10-linux-x86_64-cuda-reqs.txt +++ /dev/null @@ -1,2103 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --generate-hashes --output-file=binary_installer/py3.10-linux-x86_64-cuda-reqs.txt binary_installer/requirements.in -# ---extra-index-url https://download.pytorch.org/whl/torch_stable.html ---extra-index-url https://download.pytorch.org/whl/cu116 ---trusted-host https - -absl-py==1.3.0 \ - --hash=sha256:34995df9bd7a09b3b8749e230408f5a2a2dd7a68a0d33c12a3d0cb15a041a507 \ - --hash=sha256:463c38a08d2e4cef6c498b76ba5bd4858e4c6ef51da1a5a1f27139a022e20248 - # via - # tb-nightly - # tensorboard -accelerate==0.14.0 \ - --hash=sha256:31c5bcc40564ef849b5bc1c4424a43ccaf9e26413b7df89c2e36bf81f070fd44 \ - --hash=sha256:b15d562c0889d0cf441b01faa025dfc29b163d061b6cc7d489c2c83b0a55ffab - # via - # -r binary_installer/requirements.in - # k-diffusion -addict==2.4.0 \ - --hash=sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc \ - --hash=sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494 - # via basicsr -aiohttp==3.8.3 \ - --hash=sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8 \ - --hash=sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142 \ - --hash=sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18 \ - --hash=sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34 \ - --hash=sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a \ - --hash=sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033 \ - --hash=sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06 \ - --hash=sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4 \ - --hash=sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d \ - --hash=sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b \ - --hash=sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc \ - --hash=sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091 \ - --hash=sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d \ - --hash=sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85 \ - --hash=sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb \ - --hash=sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937 \ - --hash=sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf \ - --hash=sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1 \ - --hash=sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b \ - --hash=sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d \ - --hash=sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269 \ - --hash=sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da \ - --hash=sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346 \ - --hash=sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494 \ - --hash=sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697 \ - --hash=sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4 \ - --hash=sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585 \ - --hash=sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c \ - --hash=sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da \ - --hash=sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad \ - --hash=sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2 \ - --hash=sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6 \ - --hash=sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c \ - --hash=sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849 \ - --hash=sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa \ - --hash=sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b \ - --hash=sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb \ - --hash=sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7 \ - --hash=sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715 \ - --hash=sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76 \ - --hash=sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d \ - --hash=sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276 \ - --hash=sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6 \ - --hash=sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37 \ - --hash=sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb \ - --hash=sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d \ - --hash=sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c \ - --hash=sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446 \ - --hash=sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008 \ - --hash=sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342 \ - --hash=sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d \ - --hash=sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7 \ - --hash=sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061 \ - --hash=sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba \ - --hash=sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7 \ - --hash=sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290 \ - --hash=sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0 \ - --hash=sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d \ - --hash=sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8 \ - --hash=sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f \ - --hash=sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48 \ - --hash=sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502 \ - --hash=sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62 \ - --hash=sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9 \ - --hash=sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403 \ - --hash=sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77 \ - --hash=sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476 \ - --hash=sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e \ - --hash=sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96 \ - --hash=sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5 \ - --hash=sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784 \ - --hash=sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091 \ - --hash=sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b \ - --hash=sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97 \ - --hash=sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a \ - --hash=sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2 \ - --hash=sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9 \ - --hash=sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d \ - --hash=sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73 \ - --hash=sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017 \ - --hash=sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363 \ - --hash=sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c \ - --hash=sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d \ - --hash=sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618 \ - --hash=sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491 \ - --hash=sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b \ - --hash=sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca - # via fsspec -aiosignal==1.2.0 \ - --hash=sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a \ - --hash=sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2 - # via aiohttp -albumentations==1.3.0 \ - --hash=sha256:294165d87d03bc8323e484927f0a5c1a3c64b0e7b9c32a979582a6c93c363bdf \ - --hash=sha256:be1af36832c8893314f2a5550e8ac19801e04770734c1b70fa3c996b41f37bed - # via -r binary_installer/requirements.in -altair==4.2.0 \ - --hash=sha256:0c724848ae53410c13fa28be2b3b9a9dcb7b5caa1a70f7f217bd663bb419935a \ - --hash=sha256:d87d9372e63b48cd96b2a6415f0cf9457f50162ab79dc7a31cd7e024dd840026 - # via streamlit -antlr4-python3-runtime==4.9.3 \ - --hash=sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b - # via omegaconf -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c - # via aiohttp -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c - # via - # aiohttp - # jsonschema -basicsr==1.4.2 \ - --hash=sha256:b89b595a87ef964cda9913b4d99380ddb6554c965577c0c10cb7b78e31301e87 - # via - # gfpgan - # realesrgan -bidict==0.22.0 \ - --hash=sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0 \ - --hash=sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8 - # via python-socketio -blinker==1.5 \ - --hash=sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36 \ - --hash=sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462 - # via streamlit -boltons==21.0.0 \ - --hash=sha256:65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13 \ - --hash=sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b - # via torchsde -cachetools==5.2.0 \ - --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ - --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db - # via - # google-auth - # streamlit -certifi==2022.9.24 \ - --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ - --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 - # via - # requests - # sentry-sdk -chardet==4.0.0 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 - # via requests -charset-normalizer==2.1.1 \ - --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ - --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f - # via aiohttp -clean-fid==0.1.34 \ - --hash=sha256:2997f85a67a28c95adaae7899a33fc10537164fef4cdd424e3257bffad79a901 - # via k-diffusion -click==8.1.3 \ - --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ - --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 - # via - # flask - # streamlit - # wandb -clip @ https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip \ - --hash=sha256:b5842c25da441d6c581b53a5c60e0c2127ebafe0f746f8e15561a006c6c3be6a - # via - # -r binary_installer/requirements.in - # clipseg -clipseg @ https://github.com/invoke-ai/clipseg/archive/1f754751c85d7d4255fa681f4491ff5711c1c288.zip \ - --hash=sha256:14f43ed42f90be3fe57f06de483cb8be0f67f87a6f62a011339d45a39f4b4189 - # via -r binary_installer/requirements.in -commonmark==0.9.1 \ - --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ - --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 - # via rich -contourpy==1.0.6 \ - --hash=sha256:0236875c5a0784215b49d00ebbe80c5b6b5d5244b3655a36dda88105334dea17 \ - --hash=sha256:03d1b9c6b44a9e30d554654c72be89af94fab7510b4b9f62356c64c81cec8b7d \ - --hash=sha256:0537cc1195245bbe24f2913d1f9211b8f04eb203de9044630abd3664c6cc339c \ - --hash=sha256:06ca79e1efbbe2df795822df2fa173d1a2b38b6e0f047a0ec7903fbca1d1847e \ - --hash=sha256:08e8d09d96219ace6cb596506fb9b64ea5f270b2fb9121158b976d88871fcfd1 \ - --hash=sha256:0b1e66346acfb17694d46175a0cea7d9036f12ed0c31dfe86f0f405eedde2bdd \ - --hash=sha256:0b97454ed5b1368b66ed414c754cba15b9750ce69938fc6153679787402e4cdf \ - --hash=sha256:0e4854cc02006ad6684ce092bdadab6f0912d131f91c2450ce6dbdea78ee3c0b \ - --hash=sha256:12a7dc8439544ed05c6553bf026d5e8fa7fad48d63958a95d61698df0e00092b \ - --hash=sha256:1b1ee48a130da4dd0eb8055bbab34abf3f6262957832fd575e0cab4979a15a41 \ - --hash=sha256:1c0e1308307a75e07d1f1b5f0f56b5af84538a5e9027109a7bcf6cb47c434e72 \ - --hash=sha256:1dedf4c64185a216c35eb488e6f433297c660321275734401760dafaeb0ad5c2 \ - --hash=sha256:208bc904889c910d95aafcf7be9e677726df9ef71e216780170dbb7e37d118fa \ - --hash=sha256:211dfe2bd43bf5791d23afbe23a7952e8ac8b67591d24be3638cabb648b3a6eb \ - --hash=sha256:341330ed19074f956cb20877ad8d2ae50e458884bfa6a6df3ae28487cc76c768 \ - --hash=sha256:344cb3badf6fc7316ad51835f56ac387bdf86c8e1b670904f18f437d70da4183 \ - --hash=sha256:358f6364e4873f4d73360b35da30066f40387dd3c427a3e5432c6b28dd24a8fa \ - --hash=sha256:371f6570a81dfdddbb837ba432293a63b4babb942a9eb7aaa699997adfb53278 \ - --hash=sha256:375d81366afd547b8558c4720337218345148bc2fcffa3a9870cab82b29667f2 \ - --hash=sha256:3a1917d3941dd58732c449c810fa7ce46cc305ce9325a11261d740118b85e6f3 \ - --hash=sha256:4081918147fc4c29fad328d5066cfc751da100a1098398742f9f364be63803fc \ - --hash=sha256:444fb776f58f4906d8d354eb6f6ce59d0a60f7b6a720da6c1ccb839db7c80eb9 \ - --hash=sha256:46deb310a276cc5c1fd27958e358cce68b1e8a515fa5a574c670a504c3a3fe30 \ - --hash=sha256:494efed2c761f0f37262815f9e3c4bb9917c5c69806abdee1d1cb6611a7174a0 \ - --hash=sha256:50627bf76abb6ba291ad08db583161939c2c5fab38c38181b7833423ab9c7de3 \ - --hash=sha256:5641927cc5ae66155d0c80195dc35726eae060e7defc18b7ab27600f39dd1fe7 \ - --hash=sha256:5b117d29433fc8393b18a696d794961464e37afb34a6eeb8b2c37b5f4128a83e \ - --hash=sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6 \ - --hash=sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142 \ - --hash=sha256:6f56515e7c6fae4529b731f6c117752247bef9cdad2b12fc5ddf8ca6a50965a5 \ - --hash=sha256:730c27978a0003b47b359935478b7d63fd8386dbb2dcd36c1e8de88cbfc1e9de \ - --hash=sha256:75a2e638042118118ab39d337da4c7908c1af74a8464cad59f19fbc5bbafec9b \ - --hash=sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb \ - --hash=sha256:7ee394502026d68652c2824348a40bf50f31351a668977b51437131a90d777ea \ - --hash=sha256:8468b40528fa1e15181cccec4198623b55dcd58306f8815a793803f51f6c474a \ - --hash=sha256:84c593aeff7a0171f639da92cb86d24954bbb61f8a1b530f74eb750a14685832 \ - --hash=sha256:913bac9d064cff033cf3719e855d4f1db9f1c179e0ecf3ba9fdef21c21c6a16a \ - --hash=sha256:9447c45df407d3ecb717d837af3b70cfef432138530712263730783b3d016512 \ - --hash=sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675 \ - --hash=sha256:9bc407a6af672da20da74823443707e38ece8b93a04009dca25856c2d9adadb1 \ - --hash=sha256:9e8e686a6db92a46111a1ee0ee6f7fbfae4048f0019de207149f43ac1812cf95 \ - --hash=sha256:9fc4e7973ed0e1fe689435842a6e6b330eb7ccc696080dda9a97b1a1b78e41db \ - --hash=sha256:a457ee72d9032e86730f62c5eeddf402e732fdf5ca8b13b41772aa8ae13a4563 \ - --hash=sha256:a628bba09ba72e472bf7b31018b6281fd4cc903f0888049a3724afba13b6e0b8 \ - --hash=sha256:a79d239fc22c3b8d9d3de492aa0c245533f4f4c7608e5749af866949c0f1b1b9 \ - --hash=sha256:aa4674cf3fa2bd9c322982644967f01eed0c91bb890f624e0e0daf7a5c3383e9 \ - --hash=sha256:acd2bd02f1a7adff3a1f33e431eb96ab6d7987b039d2946a9b39fe6fb16a1036 \ - --hash=sha256:b3b1bd7577c530eaf9d2bc52d1a93fef50ac516a8b1062c3d1b9bcec9ebe329b \ - --hash=sha256:b48d94386f1994db7c70c76b5808c12e23ed7a4ee13693c2fc5ab109d60243c0 \ - --hash=sha256:b64f747e92af7da3b85631a55d68c45a2d728b4036b03cdaba4bd94bcc85bd6f \ - --hash=sha256:b98c820608e2dca6442e786817f646d11057c09a23b68d2b3737e6dcb6e4a49b \ - --hash=sha256:c1baa49ab9fedbf19d40d93163b7d3e735d9cd8d5efe4cce9907902a6dad391f \ - --hash=sha256:c38c6536c2d71ca2f7e418acaf5bca30a3af7f2a2fa106083c7d738337848dbe \ - --hash=sha256:c78bfbc1a7bff053baf7e508449d2765964d67735c909b583204e3240a2aca45 \ - --hash=sha256:cd2bc0c8f2e8de7dd89a7f1c10b8844e291bca17d359373203ef2e6100819edd \ - --hash=sha256:d2eff2af97ea0b61381828b1ad6cd249bbd41d280e53aea5cccd7b2b31b8225c \ - --hash=sha256:d8834c14b8c3dd849005e06703469db9bf96ba2d66a3f88ecc539c9a8982e0ee \ - --hash=sha256:d912f0154a20a80ea449daada904a7eb6941c83281a9fab95de50529bfc3a1da \ - --hash=sha256:da1ef35fd79be2926ba80fbb36327463e3656c02526e9b5b4c2b366588b74d9a \ - --hash=sha256:dbe6fe7a1166b1ddd7b6d887ea6fa8389d3f28b5ed3f73a8f40ece1fc5a3d340 \ - --hash=sha256:dcd556c8fc37a342dd636d7eef150b1399f823a4462f8c968e11e1ebeabee769 \ - --hash=sha256:e13b31d1b4b68db60b3b29f8e337908f328c7f05b9add4b1b5c74e0691180109 \ - --hash=sha256:e1739496c2f0108013629aa095cc32a8c6363444361960c07493818d0dea2da4 \ - --hash=sha256:e43255a83835a129ef98f75d13d643844d8c646b258bebd11e4a0975203e018f \ - --hash=sha256:e626cefff8491bce356221c22af5a3ea528b0b41fbabc719c00ae233819ea0bf \ - --hash=sha256:eadad75bf91897f922e0fb3dca1b322a58b1726a953f98c2e5f0606bd8408621 \ - --hash=sha256:f33da6b5d19ad1bb5e7ad38bb8ba5c426d2178928bc2b2c44e8823ea0ecb6ff3 \ - --hash=sha256:f4052a8a4926d4468416fc7d4b2a7b2a3e35f25b39f4061a7e2a3a2748c4fc48 \ - --hash=sha256:f6ca38dd8d988eca8f07305125dec6f54ac1c518f1aaddcc14d08c01aebb6efc - # via matplotlib -cycler==0.11.0 \ - --hash=sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3 \ - --hash=sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f - # via matplotlib -decorator==5.1.1 \ - --hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \ - --hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 - # via validators -diffusers==0.7.2 \ - --hash=sha256:4a5f8b3a5fbd936bba7d459611cb35ec62875030367be32b232f9e19543e25a9 \ - --hash=sha256:fb814ffd150cc6f470380b8c6a521181a77beb2f44134d2aad2e4cd8aa2ced0e - # via -r binary_installer/requirements.in -dnspython==2.2.1 \ - --hash=sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e \ - --hash=sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f - # via eventlet -docker-pycreds==0.4.0 \ - --hash=sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4 \ - --hash=sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49 - # via wandb -einops==0.5.0 \ - --hash=sha256:055de7eeb3cb9e9710ef3085a811090c6b52e809b7044e8785824ed185f486d1 \ - --hash=sha256:8b7a83cffc1ea88e306df099b7cbb9c3ba5003bd84d05ae44be5655864abb8d3 - # via k-diffusion -entrypoints==0.4 \ - --hash=sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4 \ - --hash=sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f - # via altair -eventlet==0.33.1 \ - --hash=sha256:a085922698e5029f820cf311a648ac324d73cec0e4792877609d978a4b5bbf31 \ - --hash=sha256:afbe17f06a58491e9aebd7a4a03e70b0b63fd4cf76d8307bae07f280479b1515 - # via -r binary_installer/requirements.in -facexlib==0.2.5 \ - --hash=sha256:31e20cc4ed5d63562d380e4564bae14ac0d5d1899a079bad87621e13564567e4 \ - --hash=sha256:cc7ceb56c5424319c47223cf75eef6828c34c66082707c6eb35b95d39779f02d - # via - # gfpgan - # realesrgan -filelock==3.8.0 \ - --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ - --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 - # via - # diffusers - # huggingface-hub - # transformers -filterpy==1.4.5 \ - --hash=sha256:4f2a4d39e4ea601b9ab42b2db08b5918a9538c168cff1c6895ae26646f3d73b1 - # via facexlib -flask==2.2.2 \ - --hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \ - --hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526 - # via - # flask-cors - # flask-socketio -flask-cors==3.0.10 \ - --hash=sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438 \ - --hash=sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de - # via -r binary_installer/requirements.in -flask-socketio==5.3.1 \ - --hash=sha256:fd0ed0fc1341671d92d5f5b2f5503916deb7aa7e2940e6636cfa2c087c828bf9 \ - --hash=sha256:ff0c721f20bff1e2cfba77948727a8db48f187e89a72fe50c34478ce6efb3353 - # via -r binary_installer/requirements.in -flaskwebgui==0.3.7 \ - --hash=sha256:4a69955308eaa8bb256ba04a994dc8f58a48dcd6f9599694ab1bcd9f43d88a5d \ - --hash=sha256:535974ce2672dcc74787c254de24cceed4101be75d96952dae82014dd57f061e - # via -r binary_installer/requirements.in -fonttools==4.38.0 \ - --hash=sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1 \ - --hash=sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb - # via matplotlib -frozenlist==1.3.1 \ - --hash=sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e \ - --hash=sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04 \ - --hash=sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944 \ - --hash=sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845 \ - --hash=sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f \ - --hash=sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f \ - --hash=sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb \ - --hash=sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2 \ - --hash=sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3 \ - --hash=sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f \ - --hash=sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a \ - --hash=sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131 \ - --hash=sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1 \ - --hash=sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa \ - --hash=sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8 \ - --hash=sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b \ - --hash=sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2 \ - --hash=sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e \ - --hash=sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b \ - --hash=sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b \ - --hash=sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10 \ - --hash=sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170 \ - --hash=sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8 \ - --hash=sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b \ - --hash=sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989 \ - --hash=sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd \ - --hash=sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03 \ - --hash=sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519 \ - --hash=sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189 \ - --hash=sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b \ - --hash=sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca \ - --hash=sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff \ - --hash=sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96 \ - --hash=sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d \ - --hash=sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5 \ - --hash=sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2 \ - --hash=sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204 \ - --hash=sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a \ - --hash=sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8 \ - --hash=sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141 \ - --hash=sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792 \ - --hash=sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9 \ - --hash=sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c \ - --hash=sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c \ - --hash=sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221 \ - --hash=sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d \ - --hash=sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc \ - --hash=sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f \ - --hash=sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9 \ - --hash=sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef \ - --hash=sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3 \ - --hash=sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab \ - --hash=sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b \ - --hash=sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db \ - --hash=sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988 \ - --hash=sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6 \ - --hash=sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc \ - --hash=sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83 \ - --hash=sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be - # via - # aiohttp - # aiosignal -fsspec[http]==2022.10.0 \ - --hash=sha256:6b7c6ab3b476cdf17efcfeccde7fca28ef5a48f73a71010aaceec5fc15bf9ebf \ - --hash=sha256:cb6092474e90487a51de768170f3afa50ca8982c26150a59072b16433879ff1d - # via pytorch-lightning -ftfy==6.1.1 \ - --hash=sha256:0ffd33fce16b54cccaec78d6ec73d95ad370e5df5a25255c8966a6147bd667ca \ - --hash=sha256:bfc2019f84fcd851419152320a6375604a0f1459c281b5b199b2cd0d2e727f8f - # via clip -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d - # via - # basicsr - # test-tube -getpass-asterisk==1.0.1 \ - --hash=sha256:20d45cafda0066d761961e0919728526baf7bb5151fbf48a7d5ea4034127d857 \ - --hash=sha256:7cc357a924cf62fa4e15b73cb4e5e30685c9084e464ffdc3fd9000a2b54ea9e9 - # via -r binary_installer/requirements.in -gfpgan @ https://github.com/invoke-ai/GFPGAN/archive/c796277a1cf77954e5fc0b288d7062d162894248.zip ; platform_system == "Linux" or platform_system == "Darwin" \ - --hash=sha256:4155907b8b7db3686324554df7007eedd245cdf8656c21da9d9a3f44bef2fcaa - # via - # -r binary_installer/requirements.in - # realesrgan -gitdb==4.0.9 \ - --hash=sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd \ - --hash=sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa - # via gitpython -gitpython==3.1.29 \ - --hash=sha256:41eea0deec2deea139b459ac03656f0dd28fc4a3387240ec1d3c259a2c47850f \ - --hash=sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd - # via - # streamlit - # wandb -google-auth==2.14.0 \ - --hash=sha256:1ad5b0e6eba5f69645971abb3d2c197537d5914070a8c6d30299dfdb07c5c700 \ - --hash=sha256:cf24817855d874ede2efd071aa22125445f555de1685b739a9782fcf408c2a3d - # via - # google-auth-oauthlib - # tb-nightly - # tensorboard -google-auth-oauthlib==0.4.6 \ - --hash=sha256:3f2a6e802eebbb6fb736a370fbf3b055edcb6b52878bf2f26330b5e041316c73 \ - --hash=sha256:a90a072f6993f2c327067bf65270046384cda5a8ecb20b94ea9a687f1f233a7a - # via - # tb-nightly - # tensorboard -greenlet==2.0.0.post0 \ - --hash=sha256:00ebdaf0fa51c284fd2172837d751731a15971e0c20d1a9163cfbdf620ce8b49 \ - --hash=sha256:029ca674b3a7e8427db8f5c65d5ed4e24a7417af2a415a5958598aefd71980c4 \ - --hash=sha256:02bdb1e373b275bd705c43b249426e776c4f8a8ff2afaf8ec5ea0dde487d8a14 \ - --hash=sha256:08dc04f49ed1ea5e6772bb5e8cf2a77d1b1744566f4eca471a55b35af1278b31 \ - --hash=sha256:08f44e938d142271b954405afb6570e0be48a9f556b6bf4d42d2e3ae6a251fad \ - --hash=sha256:0a5c03e2a68ec2ff1cba74ceaed899ec8cd353285f4f985c30c8cfbef9d3a3be \ - --hash=sha256:0fee3240093b745efc857392f09379514ad84db4ca324514594bbdf6380016c8 \ - --hash=sha256:118e708dd7bc88beaeeaa5a8601a7743b8835b7bbaf7c8f23ffa78f8bc8faf28 \ - --hash=sha256:13d492a807a5c7334b5931e9b6d9b181991ccc6a40555a7b177f189feff59b4b \ - --hash=sha256:1cac9e9895aeff26434325404558783ee54f4ff3aec8daa56b8706796f7b01a0 \ - --hash=sha256:2146d15429b4eeb412428737594acb5660a5bc0fdd1488d8a2a74a5ee32391fa \ - --hash=sha256:21ee1ae26d072b195edea764218623f6c15eba4ae06816908f33c82e0af018d3 \ - --hash=sha256:22eca421e3f2f3c18f4f54c0ff525aa9d397c6f116fce9ebd37b420174dbc027 \ - --hash=sha256:2bab49783858cf724fff6868395cbeb81d1188cba23616b53e79de0beda29f42 \ - --hash=sha256:2fbdec204ca40b3d0c0992a19c1ba627441c17983ac4ffc45baec7f5f53e20ca \ - --hash=sha256:30ce47525f9a1515566429ac7de6b1ae76d32c3ccede256e3517a1a6419cf659 \ - --hash=sha256:335dcf676d5e4122e4006c16ae11eda2467af5461b949c265ce120b6b959ffe2 \ - --hash=sha256:3407b843b05da71fef0f1dd666059c08ad0e0f4afc3b9c93c998a2e53fac95e5 \ - --hash=sha256:35827f98fd0d768862b8f15777e6dbb03fe6ac6e7bd1bee3f3ded4536f350347 \ - --hash=sha256:3a22e5988f9d66b3e9ae9583bf9d8ef792b09f23afeb78707e6a4f47ab57cc5e \ - --hash=sha256:3c3327da2bdab61078e42e695307465c425671a5a9251e6c29ee130d51943f28 \ - --hash=sha256:3ca723dfc2789c1fb991809822811896b198ecf0909dbccea4a07170d18c3e1b \ - --hash=sha256:46156ae88ee71c37b6c4f7af63fff5d3ab8f45ef72e1a660bcf6386c1647f106 \ - --hash=sha256:4bbe2d074292e3646704371eb640ee52c386d633ed72ff223dadcd3fe8ecd8f9 \ - --hash=sha256:4c4310f0e42154995d92810f27b44ab7116a4a696feb0ff141ae2de59196efd7 \ - --hash=sha256:4cfa629de5b2dea27c81b334c4536463e9a49ac0877e2008a276d58d4c72868a \ - --hash=sha256:4e144ab0de56b4d2a2cf0d2fb9d568b59fce49aab3e129badf17c12b0252047d \ - --hash=sha256:4ea67f303cec384b148774667c7e3cf02311e7026fc02bdcdcd206dfe4ea4fc9 \ - --hash=sha256:538c9e8f65a32413ace426f8117ef019021adf8175f7c491fed65f5fe2083e0c \ - --hash=sha256:56565ac9ab4ff3dd473bfe959e0bf2a5062aabb89b7c94cabb417beb162c9fff \ - --hash=sha256:5e22485256bb1c60bbcc6f8509b1a11042358a2462d5ecdb9a82dc472d2fdd60 \ - --hash=sha256:602a69c24f1a9755dd1760b3b31bdfc495c4613260c876a01b7e6d5eb9bcae1b \ - --hash=sha256:6393ec3cecda53b20241e88bc33d87cbd8126cc10870fc69fa16ca2e20a5ac1b \ - --hash=sha256:6442bbfb047dc1e47658954b72e1589f2bc4e12e67d51bbad0059a626180daa1 \ - --hash=sha256:666d2a0b269a68cd4fe0976544ab97970c5334d35d0e47ae9be1723f734d8204 \ - --hash=sha256:697cfbfc19815c40213badcfe5f076418e0f9100cd25a66f513f32c1026b8bf4 \ - --hash=sha256:6a1a6745c5dce202aa3f29a1736c53cf2179e9c3b280dc62cea9cb8c69977c83 \ - --hash=sha256:6fc73fc8dd81d9efa842a55033b6b4cb233b134a0270e127c6874d053ef2049b \ - --hash=sha256:7e9e0d4c5c618b0442396715ffe6c2f84a60d593dad7e0184388aed36d568a65 \ - --hash=sha256:81fdcf7c0c2df46a99ca421a552c4370117851c5e4dbd6cb53d569b896d62322 \ - --hash=sha256:8b26932be686f3582df039d79fe96f7ca13d63b39468162f816f9ff29584b9a4 \ - --hash=sha256:8b7e5191b974fb66fcbac1818ba494d3512da9cf6eaef7acd952f9862eaaa20c \ - --hash=sha256:8c80e9c41a83d8c90399af8c7dcdeae0c03c48b40b9d0ab84457533d5d7882bf \ - --hash=sha256:9f2f110b9cc325f6543e0e3f4ab8008c272a59052f9464047c29d4be4511ce05 \ - --hash=sha256:a339e510a079dc8372e39ce1c7629414db51966235c9670c58d529def79243a2 \ - --hash=sha256:ad9abc3e4d2370cecb524421cc5c8a664006aa11d5c1cb3c9250e3bf65ab546e \ - --hash=sha256:b043782c8f6cccc8fae3a16db397eca1d36a41b0706cbf6f514aea1e1a260bab \ - --hash=sha256:b31de27313abbb567c528ed123380fcf18a5dfd03134570dfd12227e21ac1184 \ - --hash=sha256:b75e5644cc353328cd57ec8dafaaf5f81b2c3ecf7c4b278b907e99ad53ba7839 \ - --hash=sha256:b8cfc8fc944bd7b704691bc28225a2635e377e92dc413459845868d3f7724982 \ - --hash=sha256:c2055c52260808d87622293b57df1c68aeb12ddd8a0cfc0223fb57a5f629e202 \ - --hash=sha256:c416106b3b8e905b6ab0e84ec90047a6401021aa023f9aa93978e57cd8f8189f \ - --hash=sha256:d0e210e17a6181a3fd3f8dce957043a4e74177ffa9f295514984b2b633940dce \ - --hash=sha256:d9453135e48cd631e3e9f06d9da9100d17c9f662e4a6d8b552c29be6c834a6b9 \ - --hash=sha256:dd0198006278291d9469309d655093df1f5e5107c0261e242b5f390baee32199 \ - --hash=sha256:e1781bda1e787d3ad33788cc3be47f6e47a9581676d02670c15ee36c9460adfe \ - --hash=sha256:e56a5a9f303e3ac011ba445a6d84f05d08666bf8db094afafcec5228622c30f5 \ - --hash=sha256:e93ae35f0fd3caf75e58c76a1cab71e6ece169aaa1b281782ef9efde0a6b83f2 \ - --hash=sha256:eb36b6570646227a63eda03916f1cc6f3744ee96d28f7a0a5629c59267a8055f \ - --hash=sha256:f8c425a130e04d5404edaf6f5906e5ab12f3aa1168a1828aba6dfadac5910469 - # via eventlet -grpcio==1.50.0 \ - --hash=sha256:05f7c248e440f538aaad13eee78ef35f0541e73498dd6f832fe284542ac4b298 \ - --hash=sha256:080b66253f29e1646ac53ef288c12944b131a2829488ac3bac8f52abb4413c0d \ - --hash=sha256:12b479839a5e753580b5e6053571de14006157f2ef9b71f38c56dc9b23b95ad6 \ - --hash=sha256:156f8009e36780fab48c979c5605eda646065d4695deea4cfcbcfdd06627ddb6 \ - --hash=sha256:15f9e6d7f564e8f0776770e6ef32dac172c6f9960c478616c366862933fa08b4 \ - --hash=sha256:177afaa7dba3ab5bfc211a71b90da1b887d441df33732e94e26860b3321434d9 \ - --hash=sha256:1a4cd8cb09d1bc70b3ea37802be484c5ae5a576108bad14728f2516279165dd7 \ - --hash=sha256:1d8d02dbb616c0a9260ce587eb751c9c7dc689bc39efa6a88cc4fa3e9c138a7b \ - --hash=sha256:2b71916fa8f9eb2abd93151fafe12e18cebb302686b924bd4ec39266211da525 \ - --hash=sha256:2d9fd6e38b16c4d286a01e1776fdf6c7a4123d99ae8d6b3f0b4a03a34bf6ce45 \ - --hash=sha256:3b611b3de3dfd2c47549ca01abfa9bbb95937eb0ea546ea1d762a335739887be \ - --hash=sha256:3e4244c09cc1b65c286d709658c061f12c61c814be0b7030a2d9966ff02611e0 \ - --hash=sha256:40838061e24f960b853d7bce85086c8e1b81c6342b1f4c47ff0edd44bbae2722 \ - --hash=sha256:4b123fbb7a777a2fedec684ca0b723d85e1d2379b6032a9a9b7851829ed3ca9a \ - --hash=sha256:531f8b46f3d3db91d9ef285191825d108090856b3bc86a75b7c3930f16ce432f \ - --hash=sha256:67dd41a31f6fc5c7db097a5c14a3fa588af54736ffc174af4411d34c4f306f68 \ - --hash=sha256:7489dbb901f4fdf7aec8d3753eadd40839c9085967737606d2c35b43074eea24 \ - --hash=sha256:8d4c8e73bf20fb53fe5a7318e768b9734cf122fe671fcce75654b98ba12dfb75 \ - --hash=sha256:8e69aa4e9b7f065f01d3fdcecbe0397895a772d99954bb82eefbb1682d274518 \ - --hash=sha256:8e8999a097ad89b30d584c034929f7c0be280cd7851ac23e9067111167dcbf55 \ - --hash=sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974 \ - --hash=sha256:92d7635d1059d40d2ec29c8bf5ec58900120b3ce5150ef7414119430a4b2dd5c \ - --hash=sha256:931e746d0f75b2a5cff0a1197d21827a3a2f400c06bace036762110f19d3d507 \ - --hash=sha256:95ce51f7a09491fb3da8cf3935005bff19983b77c4e9437ef77235d787b06842 \ - --hash=sha256:9eea18a878cffc804506d39c6682d71f6b42ec1c151d21865a95fae743fda500 \ - --hash=sha256:a23d47f2fc7111869f0ff547f771733661ff2818562b04b9ed674fa208e261f4 \ - --hash=sha256:a4c23e54f58e016761b576976da6a34d876420b993f45f66a2bfb00363ecc1f9 \ - --hash=sha256:a50a1be449b9e238b9bd43d3857d40edf65df9416dea988929891d92a9f8a778 \ - --hash=sha256:ab5d0e3590f0a16cb88de4a3fa78d10eb66a84ca80901eb2c17c1d2c308c230f \ - --hash=sha256:ae23daa7eda93c1c49a9ecc316e027ceb99adbad750fbd3a56fa9e4a2ffd5ae0 \ - --hash=sha256:af98d49e56605a2912cf330b4627e5286243242706c3a9fa0bcec6e6f68646fc \ - --hash=sha256:b2f77a90ba7b85bfb31329f8eab9d9540da2cf8a302128fb1241d7ea239a5469 \ - --hash=sha256:baab51dcc4f2aecabf4ed1e2f57bceab240987c8b03533f1cef90890e6502067 \ - --hash=sha256:ca8a2254ab88482936ce941485c1c20cdeaef0efa71a61dbad171ab6758ec998 \ - --hash=sha256:cb11464f480e6103c59d558a3875bd84eed6723f0921290325ebe97262ae1347 \ - --hash=sha256:ce8513aee0af9c159319692bfbf488b718d1793d764798c3d5cff827a09e25ef \ - --hash=sha256:cf151f97f5f381163912e8952eb5b3afe89dec9ed723d1561d59cabf1e219a35 \ - --hash=sha256:d144ad10eeca4c1d1ce930faa105899f86f5d99cecfe0d7224f3c4c76265c15e \ - --hash=sha256:d534d169673dd5e6e12fb57cc67664c2641361e1a0885545495e65a7b761b0f4 \ - --hash=sha256:d75061367a69808ab2e84c960e9dce54749bcc1e44ad3f85deee3a6c75b4ede9 \ - --hash=sha256:d84d04dec64cc4ed726d07c5d17b73c343c8ddcd6b59c7199c801d6bbb9d9ed1 \ - --hash=sha256:de411d2b030134b642c092e986d21aefb9d26a28bf5a18c47dd08ded411a3bc5 \ - --hash=sha256:e07fe0d7ae395897981d16be61f0db9791f482f03fee7d1851fe20ddb4f69c03 \ - --hash=sha256:ea8ccf95e4c7e20419b7827aa5b6da6f02720270686ac63bd3493a651830235c \ - --hash=sha256:f7025930039a011ed7d7e7ef95a1cb5f516e23c5a6ecc7947259b67bea8e06ca - # via - # tb-nightly - # tensorboard -huggingface-hub==0.10.1 \ - --hash=sha256:5c188d5b16bec4b78449f8681f9975ff9d321c16046cc29bcf0d7e464ff29276 \ - --hash=sha256:dc3b0e9a663fe6cad6a8522055c02a9d8673dbd527223288e2442bc028c253db - # via - # diffusers - # transformers -idna==2.10 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 - # via - # requests - # yarl -imageio==2.22.3 \ - --hash=sha256:63f007b7f2a082306e36922b3fd529a7aa305d2b78f46195bab8e22bbfe866e9 \ - --hash=sha256:a4b88f9f3d428b8c0ceeb7e297df8c346a642bb7e3111743eb85717d60b26f6f - # via - # scikit-image - # test-tube -imageio-ffmpeg==0.4.7 \ - --hash=sha256:27b48c32becae1658aa81c3a6b922538e4099edf5fbcbdb4ff5dbc84b8ffd3d3 \ - --hash=sha256:6514f1380daf42815bc8c83aad63f33e0b8b47133421ddafe7b410cd8dfbbea5 \ - --hash=sha256:6aba52ddf0a64442ffcb8d30ac6afb668186acec99ecbc7ae5bd171c4f500bbc \ - --hash=sha256:7a08838f97f363e37ca41821b864fd3fdc99ab1fe2421040c78eb5f56a9e723e \ - --hash=sha256:8e724d12dfe83e2a6eb39619e820243ca96c81c47c2648e66e05f7ee24e14312 \ - --hash=sha256:fc60686ef03c2d0f842901b206223c30051a6a120384458761390104470846fd - # via -r binary_installer/requirements.in -importlib-metadata==5.0.0 \ - --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ - --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 - # via - # diffusers - # flask - # markdown - # streamlit -itsdangerous==2.1.2 \ - --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ - --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a - # via flask -jinja2==3.1.2 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - # via - # altair - # flask - # pydeck -joblib==1.2.0 \ - --hash=sha256:091138ed78f800342968c523bdde947e7a305b8594b910a0fea2ab83c3c6d385 \ - --hash=sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018 - # via scikit-learn -jsonmerge==1.9.0 \ - --hash=sha256:a2d1f80021c5c1d70a49e31f862b5f068f9db066080d8561e80654de74a3584d - # via k-diffusion -jsonschema==4.17.0 \ - --hash=sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d \ - --hash=sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248 - # via - # altair - # jsonmerge -k-diffusion @ https://github.com/Birch-san/k-diffusion/archive/363386981fee88620709cf8f6f2eea167bd6cd74.zip \ - --hash=sha256:8eac5cdc08736e6d61908a1b2948f2b2f62691b01dc1aab978bddb3451af0d66 - # via -r binary_installer/requirements.in -kiwisolver==1.4.4 \ - --hash=sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b \ - --hash=sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166 \ - --hash=sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c \ - --hash=sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c \ - --hash=sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0 \ - --hash=sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4 \ - --hash=sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9 \ - --hash=sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286 \ - --hash=sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767 \ - --hash=sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c \ - --hash=sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6 \ - --hash=sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b \ - --hash=sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004 \ - --hash=sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf \ - --hash=sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494 \ - --hash=sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac \ - --hash=sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626 \ - --hash=sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766 \ - --hash=sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514 \ - --hash=sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6 \ - --hash=sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f \ - --hash=sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d \ - --hash=sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191 \ - --hash=sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d \ - --hash=sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51 \ - --hash=sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f \ - --hash=sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8 \ - --hash=sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454 \ - --hash=sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb \ - --hash=sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da \ - --hash=sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8 \ - --hash=sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de \ - --hash=sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a \ - --hash=sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9 \ - --hash=sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008 \ - --hash=sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3 \ - --hash=sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32 \ - --hash=sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938 \ - --hash=sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1 \ - --hash=sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9 \ - --hash=sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d \ - --hash=sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824 \ - --hash=sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b \ - --hash=sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd \ - --hash=sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2 \ - --hash=sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5 \ - --hash=sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69 \ - --hash=sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3 \ - --hash=sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae \ - --hash=sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597 \ - --hash=sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e \ - --hash=sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955 \ - --hash=sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca \ - --hash=sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a \ - --hash=sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea \ - --hash=sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede \ - --hash=sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4 \ - --hash=sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6 \ - --hash=sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686 \ - --hash=sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408 \ - --hash=sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871 \ - --hash=sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29 \ - --hash=sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750 \ - --hash=sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897 \ - --hash=sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0 \ - --hash=sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2 \ - --hash=sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09 \ - --hash=sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c - # via matplotlib -kornia==0.6.8 \ - --hash=sha256:0985e02453c0ab4f030e8d22a3a7554dab312ffa8f8a54ec872190e6f0b58c56 \ - --hash=sha256:0d6d69330b4fd24da742337b8134da0ce01b4d7da66770db5498d58e8b4a0832 - # via k-diffusion -llvmlite==0.39.1 \ - --hash=sha256:03aee0ccd81735696474dc4f8b6be60774892a2929d6c05d093d17392c237f32 \ - --hash=sha256:1578f5000fdce513712e99543c50e93758a954297575610f48cb1fd71b27c08a \ - --hash=sha256:16f56eb1eec3cda3a5c526bc3f63594fc24e0c8d219375afeb336f289764c6c7 \ - --hash=sha256:1ec3d70b3e507515936e475d9811305f52d049281eaa6c8273448a61c9b5b7e2 \ - --hash=sha256:22d36591cd5d02038912321d9ab8e4668e53ae2211da5523f454e992b5e13c36 \ - --hash=sha256:3803f11ad5f6f6c3d2b545a303d68d9fabb1d50e06a8d6418e6fcd2d0df00959 \ - --hash=sha256:39dc2160aed36e989610fc403487f11b8764b6650017ff367e45384dff88ffbf \ - --hash=sha256:3fc14e757bc07a919221f0cbaacb512704ce5774d7fcada793f1996d6bc75f2a \ - --hash=sha256:4c6ebace910410daf0bebda09c1859504fc2f33d122e9a971c4c349c89cca630 \ - --hash=sha256:50aea09a2b933dab7c9df92361b1844ad3145bfb8dd2deb9cd8b8917d59306fb \ - --hash=sha256:60f8dd1e76f47b3dbdee4b38d9189f3e020d22a173c00f930b52131001d801f9 \ - --hash=sha256:62c0ea22e0b9dffb020601bb65cb11dd967a095a488be73f07d8867f4e327ca5 \ - --hash=sha256:6546bed4e02a1c3d53a22a0bced254b3b6894693318b16c16c8e43e29d6befb6 \ - --hash=sha256:6717c7a6e93c9d2c3d07c07113ec80ae24af45cde536b34363d4bcd9188091d9 \ - --hash=sha256:7ebf1eb9badc2a397d4f6a6c8717447c81ac011db00064a00408bc83c923c0e4 \ - --hash=sha256:9ffc84ade195abd4abcf0bd3b827b9140ae9ef90999429b9ea84d5df69c9058c \ - --hash=sha256:a3f331a323d0f0ada6b10d60182ef06c20a2f01be21699999d204c5750ffd0b4 \ - --hash=sha256:b1a0bbdb274fb683f993198775b957d29a6f07b45d184c571ef2a721ce4388cf \ - --hash=sha256:b43abd7c82e805261c425d50335be9a6c4f84264e34d6d6e475207300005d572 \ - --hash=sha256:c0f158e4708dda6367d21cf15afc58de4ebce979c7a1aa2f6b977aae737e2a54 \ - --hash=sha256:d0bfd18c324549c0fec2c5dc610fd024689de6f27c6cc67e4e24a07541d6e49b \ - --hash=sha256:ddab526c5a2c4ccb8c9ec4821fcea7606933dc53f510e2a6eebb45a418d3488a \ - --hash=sha256:e172c73fccf7d6db4bd6f7de963dedded900d1a5c6778733241d878ba613980e \ - --hash=sha256:e2c00ff204afa721b0bb9835b5bf1ba7fba210eefcec5552a9e05a63219ba0dc \ - --hash=sha256:e31f4b799d530255aaf0566e3da2df5bfc35d3cd9d6d5a3dcc251663656c27b1 \ - --hash=sha256:e4f212c018db951da3e1dc25c2651abc688221934739721f2dad5ff1dd5f90e7 \ - --hash=sha256:fa9b26939ae553bf30a9f5c4c754db0fb2d2677327f2511e674aa2f5df941789 \ - --hash=sha256:fb62fc7016b592435d3e3a8f680e3ea8897c3c9e62e6e6cc58011e7a4801439e - # via numba -lmdb==1.3.0 \ - --hash=sha256:008243762decf8f6c90430a9bced56290ebbcdb5e877d90e42343bb97033e494 \ - --hash=sha256:08f4b5129f4683802569b02581142e415c8dcc0ff07605983ec1b07804cecbad \ - --hash=sha256:17215a42a4b9814c383deabecb160581e4fb75d00198eef0e3cea54f230ffbea \ - --hash=sha256:18c69fabdaf04efaf246587739cc1062b3e57c6ef0743f5c418df89e5e7e7b9b \ - --hash=sha256:2cfa4aa9c67f8aee89b23005e98d1f3f32490b6b905fd1cb604b207cbd5755ab \ - --hash=sha256:2df38115dd9428a54d59ae7c712a4c7cce0d6b1d66056de4b1a8c38718066106 \ - --hash=sha256:394df860c3f93cfd92b6f4caba785f38208cc9614c18b3803f83a2cc1695042f \ - --hash=sha256:41318717ab5d15ad2d6d263d34fbf614a045210f64b25e59ce734bb2105e421f \ - --hash=sha256:4172fba19417d7b29409beca7d73c067b54e5d8ab1fb9b51d7b4c1445d20a167 \ - --hash=sha256:5a14aca2651c3af6f0d0a6b9168200eea0c8f2d27c40b01a442f33329a6e8dff \ - --hash=sha256:5ddd590e1c7fcb395931aa3782fb89b9db4550ab2d81d006ecd239e0d462bc41 \ - --hash=sha256:60a11efc21aaf009d06518996360eed346f6000bfc9de05114374230879f992e \ - --hash=sha256:6260a526e4ad85b1f374a5ba9475bf369fb07e7728ea6ec57226b02c40d1976b \ - --hash=sha256:62ab28e3593bdc318ea2f2fa1574e5fca3b6d1f264686d773ba54a637d4f563b \ - --hash=sha256:63cb73fe7ce9eb93d992d632c85a0476b4332670d9e6a2802b5062f603b7809f \ - --hash=sha256:65334eafa5d430b18d81ebd5362559a41483c362e1931f6e1b15bab2ecb7d75d \ - --hash=sha256:7da05d70fcc6561ac6b09e9fb1bf64b7ca294652c64c8a2889273970cee796b9 \ - --hash=sha256:abbc439cd9fe60ffd6197009087ea885ac150017dc85384093b1d376f83f0ec4 \ - --hash=sha256:c6adbd6f7f9048e97f31a069e652eb51020a81e80a0ce92dbb9810d21da2409a \ - --hash=sha256:d6a816954d212f40fd15007cd81ab7a6bebb77436d949a6a9ae04af57fc127f3 \ - --hash=sha256:d9103aa4908f0bca43c5911ca067d4e3d01f682dff0c0381a1239bd2bd757984 \ - --hash=sha256:df2724bad7820114a205472994091097d0fa65a3e5fff5a8e688d123fb8c6326 \ - --hash=sha256:e568ae0887ae196340947d9800136e90feaed6b86a261ef01f01b2ba65fc8106 \ - --hash=sha256:e6a704b3baced9182836c7f77b769f23856f3a8f62d0282b1bc1feaf81a86712 \ - --hash=sha256:eefb392f6b5cd43aada49258c5a79be11cb2c8cd3fc3e2d9319a1e0b9f906458 \ - --hash=sha256:f291e3f561f58dddf63a92a5a6a4b8af3a0920b6705d35e2f80e52e86ee238a2 \ - --hash=sha256:fa6439356e591d3249ab0e1778a6f8d8408e993f66dc911914c78208f5310309 - # via - # basicsr - # gfpgan -markdown==3.4.1 \ - --hash=sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186 \ - --hash=sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff - # via - # tb-nightly - # tensorboard -markupsafe==2.1.1 \ - --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ - --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ - --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ - --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ - --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ - --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ - --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ - --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ - --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ - --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ - --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ - --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ - --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ - --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ - --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ - --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ - --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ - --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ - --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ - --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ - --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ - --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ - --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ - --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ - --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ - --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ - --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ - --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ - --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ - --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ - --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ - --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ - --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ - --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ - --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ - --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ - --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ - --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ - --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ - --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 - # via - # jinja2 - # werkzeug -matplotlib==3.6.2 \ - --hash=sha256:0844523dfaaff566e39dbfa74e6f6dc42e92f7a365ce80929c5030b84caa563a \ - --hash=sha256:0eda9d1b43f265da91fb9ae10d6922b5a986e2234470a524e6b18f14095b20d2 \ - --hash=sha256:168093410b99f647ba61361b208f7b0d64dde1172b5b1796d765cd243cadb501 \ - --hash=sha256:1836f366272b1557a613f8265db220eb8dd883202bbbabe01bad5a4eadfd0c95 \ - --hash=sha256:19d61ee6414c44a04addbe33005ab1f87539d9f395e25afcbe9a3c50ce77c65c \ - --hash=sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267 \ - --hash=sha256:32d29c8c26362169c80c5718ce367e8c64f4dd068a424e7110df1dd2ed7bd428 \ - --hash=sha256:380d48c15ec41102a2b70858ab1dedfa33eb77b2c0982cb65a200ae67a48e9cb \ - --hash=sha256:3964934731fd7a289a91d315919cf757f293969a4244941ab10513d2351b4e83 \ - --hash=sha256:3cef89888a466228fc4e4b2954e740ce8e9afde7c4315fdd18caa1b8de58ca17 \ - --hash=sha256:4426c74761790bff46e3d906c14c7aab727543293eed5a924300a952e1a3a3c1 \ - --hash=sha256:5024b8ed83d7f8809982d095d8ab0b179bebc07616a9713f86d30cf4944acb73 \ - --hash=sha256:52c2bdd7cd0bf9d5ccdf9c1816568fd4ccd51a4d82419cc5480f548981b47dd0 \ - --hash=sha256:54fa9fe27f5466b86126ff38123261188bed568c1019e4716af01f97a12fe812 \ - --hash=sha256:5ba73aa3aca35d2981e0b31230d58abb7b5d7ca104e543ae49709208d8ce706a \ - --hash=sha256:5e16dcaecffd55b955aa5e2b8a804379789c15987e8ebd2f32f01398a81e975b \ - --hash=sha256:5ecfc6559132116dedfc482d0ad9df8a89dc5909eebffd22f3deb684132d002f \ - --hash=sha256:74153008bd24366cf099d1f1e83808d179d618c4e32edb0d489d526523a94d9f \ - --hash=sha256:78ec3c3412cf277e6252764ee4acbdbec6920cc87ad65862272aaa0e24381eee \ - --hash=sha256:795ad83940732b45d39b82571f87af0081c120feff2b12e748d96bb191169e33 \ - --hash=sha256:7f716b6af94dc1b6b97c46401774472f0867e44595990fe80a8ba390f7a0a028 \ - --hash=sha256:83dc89c5fd728fdb03b76f122f43b4dcee8c61f1489e232d9ad0f58020523e1c \ - --hash=sha256:8a0ae37576ed444fe853709bdceb2be4c7df6f7acae17b8378765bd28e61b3ae \ - --hash=sha256:8a8dbe2cb7f33ff54b16bb5c500673502a35f18ac1ed48625e997d40c922f9cc \ - --hash=sha256:8a9d899953c722b9afd7e88dbefd8fb276c686c3116a43c577cfabf636180558 \ - --hash=sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5 \ - --hash=sha256:9347cc6822f38db2b1d1ce992f375289670e595a2d1c15961aacbe0977407dfc \ - --hash=sha256:9f335e5625feb90e323d7e3868ec337f7b9ad88b5d633f876e3b778813021dab \ - --hash=sha256:b03fd10a1709d0101c054883b550f7c4c5e974f751e2680318759af005964990 \ - --hash=sha256:b0ca2c60d3966dfd6608f5f8c49b8a0fcf76de6654f2eda55fc6ef038d5a6f27 \ - --hash=sha256:b2604c6450f9dd2c42e223b1f5dca9643a23cfecc9fde4a94bb38e0d2693b136 \ - --hash=sha256:ca0e7a658fbafcddcaefaa07ba8dae9384be2343468a8e011061791588d839fa \ - --hash=sha256:d0e9ac04065a814d4cf2c6791a2ad563f739ae3ae830d716d54245c2b96fead6 \ - --hash=sha256:d50e8c1e571ee39b5dfbc295c11ad65988879f68009dd281a6e1edbc2ff6c18c \ - --hash=sha256:d840adcad7354be6f2ec28d0706528b0026e4c3934cc6566b84eac18633eab1b \ - --hash=sha256:e0bbee6c2a5bf2a0017a9b5e397babb88f230e6f07c3cdff4a4c4bc75ed7c617 \ - --hash=sha256:e5afe0a7ea0e3a7a257907060bee6724a6002b7eec55d0db16fd32409795f3e1 \ - --hash=sha256:e68be81cd8c22b029924b6d0ee814c337c0e706b8d88495a617319e5dd5441c3 \ - --hash=sha256:ec9be0f4826cdb3a3a517509dcc5f87f370251b76362051ab59e42b6b765f8c4 \ - --hash=sha256:f04f97797df35e442ed09f529ad1235d1f1c0f30878e2fe09a2676b71a8801e0 \ - --hash=sha256:f41e57ad63d336fe50d3a67bb8eaa26c09f6dda6a59f76777a99b8ccd8e26aec - # via - # clipseg - # filterpy -multidict==6.0.2 \ - --hash=sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60 \ - --hash=sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c \ - --hash=sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672 \ - --hash=sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51 \ - --hash=sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032 \ - --hash=sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2 \ - --hash=sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b \ - --hash=sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80 \ - --hash=sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88 \ - --hash=sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a \ - --hash=sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d \ - --hash=sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389 \ - --hash=sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c \ - --hash=sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9 \ - --hash=sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c \ - --hash=sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516 \ - --hash=sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b \ - --hash=sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43 \ - --hash=sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee \ - --hash=sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227 \ - --hash=sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d \ - --hash=sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae \ - --hash=sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7 \ - --hash=sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4 \ - --hash=sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9 \ - --hash=sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f \ - --hash=sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013 \ - --hash=sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9 \ - --hash=sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e \ - --hash=sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693 \ - --hash=sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a \ - --hash=sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15 \ - --hash=sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb \ - --hash=sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96 \ - --hash=sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87 \ - --hash=sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376 \ - --hash=sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658 \ - --hash=sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0 \ - --hash=sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071 \ - --hash=sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360 \ - --hash=sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc \ - --hash=sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3 \ - --hash=sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba \ - --hash=sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8 \ - --hash=sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9 \ - --hash=sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2 \ - --hash=sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3 \ - --hash=sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68 \ - --hash=sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8 \ - --hash=sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d \ - --hash=sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49 \ - --hash=sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608 \ - --hash=sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57 \ - --hash=sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86 \ - --hash=sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20 \ - --hash=sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293 \ - --hash=sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849 \ - --hash=sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937 \ - --hash=sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d - # via - # aiohttp - # yarl -networkx==2.8.8 \ - --hash=sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e \ - --hash=sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524 - # via scikit-image -numba==0.56.4 \ - --hash=sha256:0240f9026b015e336069329839208ebd70ec34ae5bfbf402e4fcc8e06197528e \ - --hash=sha256:03634579d10a6129181129de293dd6b5eaabee86881369d24d63f8fe352dd6cb \ - --hash=sha256:03fe94cd31e96185cce2fae005334a8cc712fc2ba7756e52dff8c9400718173f \ - --hash=sha256:0611e6d3eebe4cb903f1a836ffdb2bda8d18482bcd0a0dcc56e79e2aa3fefef5 \ - --hash=sha256:0da583c532cd72feefd8e551435747e0e0fbb3c0530357e6845fcc11e38d6aea \ - --hash=sha256:14dbbabf6ffcd96ee2ac827389afa59a70ffa9f089576500434c34abf9b054a4 \ - --hash=sha256:32d9fef412c81483d7efe0ceb6cf4d3310fde8b624a9cecca00f790573ac96ee \ - --hash=sha256:3a993349b90569518739009d8f4b523dfedd7e0049e6838c0e17435c3e70dcc4 \ - --hash=sha256:3cb1a07a082a61df80a468f232e452d818f5ae254b40c26390054e4e868556e0 \ - --hash=sha256:42f9e1be942b215df7e6cc9948cf9c15bb8170acc8286c063a9e57994ef82fd1 \ - --hash=sha256:4373da9757049db7c90591e9ec55a2e97b2b36ba7ae3bf9c956a513374077470 \ - --hash=sha256:4e08e203b163ace08bad500b0c16f6092b1eb34fd1fce4feaf31a67a3a5ecf3b \ - --hash=sha256:553da2ce74e8862e18a72a209ed3b6d2924403bdd0fb341fa891c6455545ba7c \ - --hash=sha256:720886b852a2d62619ae3900fe71f1852c62db4f287d0c275a60219e1643fc04 \ - --hash=sha256:85dbaed7a05ff96492b69a8900c5ba605551afb9b27774f7f10511095451137c \ - --hash=sha256:8a95ca9cc77ea4571081f6594e08bd272b66060634b8324e99cd1843020364f9 \ - --hash=sha256:91f021145a8081f881996818474ef737800bcc613ffb1e618a655725a0f9e246 \ - --hash=sha256:9f62672145f8669ec08762895fe85f4cf0ead08ce3164667f2b94b2f62ab23c3 \ - --hash=sha256:a12ef323c0f2101529d455cfde7f4135eaa147bad17afe10b48634f796d96abd \ - --hash=sha256:c602d015478b7958408d788ba00a50272649c5186ea8baa6cf71d4a1c761bba1 \ - --hash=sha256:c75e8a5f810ce80a0cfad6e74ee94f9fde9b40c81312949bf356b7304ef20740 \ - --hash=sha256:d0ae9270a7a5cc0ede63cd234b4ff1ce166c7a749b91dbbf45e0000c56d3eade \ - --hash=sha256:d69ad934e13c15684e7887100a8f5f0f61d7a8e57e0fd29d9993210089a5b531 \ - --hash=sha256:dbcc847bac2d225265d054993a7f910fda66e73d6662fe7156452cac0325b073 \ - --hash=sha256:e64d338b504c9394a4a34942df4627e1e6cb07396ee3b49fe7b8d6420aa5104f \ - --hash=sha256:f4cfc3a19d1e26448032049c79fc60331b104f694cf570a9e94f4e2c9d0932bb \ - --hash=sha256:fbfb45e7b297749029cb28694abf437a78695a100e7c2033983d69f0ba2698d4 \ - --hash=sha256:fcdf84ba3ed8124eb7234adfbb8792f311991cbf8aed1cad4b1b1a7ee08380c1 - # via facexlib -numpy==1.23.4 \ - --hash=sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8 \ - --hash=sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735 \ - --hash=sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd \ - --hash=sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810 \ - --hash=sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db \ - --hash=sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962 \ - --hash=sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79 \ - --hash=sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911 \ - --hash=sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d \ - --hash=sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488 \ - --hash=sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5 \ - --hash=sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0 \ - --hash=sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f \ - --hash=sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f \ - --hash=sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2 \ - --hash=sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0 \ - --hash=sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68 \ - --hash=sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3 \ - --hash=sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6 \ - --hash=sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71 \ - --hash=sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894 \ - --hash=sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f \ - --hash=sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329 \ - --hash=sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba \ - --hash=sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c \ - --hash=sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e \ - --hash=sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef \ - --hash=sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7 - # via - # accelerate - # albumentations - # altair - # basicsr - # clean-fid - # clipseg - # contourpy - # diffusers - # facexlib - # filterpy - # gfpgan - # imageio - # matplotlib - # numba - # opencv-python - # opencv-python-headless - # pandas - # pyarrow - # pydeck - # pypatchmatch - # pytorch-lightning - # pywavelets - # qudida - # realesrgan - # scikit-image - # scikit-learn - # scipy - # streamlit - # taming-transformers-rom1504 - # tb-nightly - # tensorboard - # test-tube - # tifffile - # torch-fidelity - # torchmetrics - # torchsde - # torchvision - # transformers -oauthlib==3.2.2 \ - --hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \ - --hash=sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918 - # via requests-oauthlib -omegaconf==2.2.3 \ - --hash=sha256:59ff9fba864ffbb5fb710b64e8a9ba37c68fa339a2e2bb4f1b648d6901552523 \ - --hash=sha256:d6f2cbf79a992899eb76c6cb1aedfcf0fe7456a8654382edd5ee0c1b199c0657 - # via taming-transformers-rom1504 -opencv-python==4.6.0.66 \ - --hash=sha256:0dc82a3d8630c099d2f3ac1b1aabee164e8188db54a786abb7a4e27eba309440 \ - --hash=sha256:5af8ba35a4fcb8913ffb86e92403e9a656a4bff4a645d196987468f0f8947875 \ - --hash=sha256:6e32af22e3202748bd233ed8f538741876191863882eba44e332d1a34993165b \ - --hash=sha256:c5bfae41ad4031e66bb10ec4a0a2ffd3e514d092652781e8b1ac98d1b59f1158 \ - --hash=sha256:dbdc84a9b4ea2cbae33861652d25093944b9959279200b7ae0badd32439f74de \ - --hash=sha256:e6e448b62afc95c5b58f97e87ef84699e6607fe5c58730a03301c52496005cae \ - --hash=sha256:f482e78de6e7b0b060ff994ffd859bddc3f7f382bb2019ef157b0ea8ca8712f5 - # via - # basicsr - # clipseg - # facexlib - # gfpgan - # realesrgan -opencv-python-headless==4.6.0.66 \ - --hash=sha256:21e70f8b0c04098cdf466d27184fe6c3820aaef944a22548db95099959c95889 \ - --hash=sha256:2c032c373e447c3fc2a670bca20e2918a1205a6e72854df60425fd3f82c78c32 \ - --hash=sha256:3bacd806cce1f1988e58f3d6f761538e0215d6621d316de94c009dc0acbd6ad3 \ - --hash=sha256:d5291d7e10aa2c19cab6fd86f0d61af8617290ecd2d7ffcb051e446868d04cc5 \ - --hash=sha256:e6c069bc963d7e8fcec21b3e33e594d35948badd63eccb2e80f88b0a12102c03 \ - --hash=sha256:eec6281054346103d6af93f173b7c6a206beb2663d3adc04aa3ddc66e85093df \ - --hash=sha256:ffbf26fcd697af996408440a93bc69c49c05a36845771f984156dfbeaa95d497 - # via - # albumentations - # qudida -packaging==21.3 \ - --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ - --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 - # via - # accelerate - # huggingface-hub - # kornia - # matplotlib - # pytorch-lightning - # scikit-image - # streamlit - # torchmetrics - # transformers -pandas==1.5.1 \ - --hash=sha256:04e51b01d5192499390c0015630975f57836cc95c7411415b499b599b05c0c96 \ - --hash=sha256:05c527c64ee02a47a24031c880ee0ded05af0623163494173204c5b72ddce658 \ - --hash=sha256:0a78e05ec09731c5b3bd7a9805927ea631fe6f6cb06f0e7c63191a9a778d52b4 \ - --hash=sha256:17da7035d9e6f9ea9cdc3a513161f8739b8f8489d31dc932bc5a29a27243f93d \ - --hash=sha256:249cec5f2a5b22096440bd85c33106b6102e0672204abd2d5c014106459804ee \ - --hash=sha256:2c25e5c16ee5c0feb6cf9d982b869eec94a22ddfda9aa2fbed00842cbb697624 \ - --hash=sha256:32e3d9f65606b3f6e76555bfd1d0b68d94aff0929d82010b791b6254bf5a4b96 \ - --hash=sha256:36aa1f8f680d7584e9b572c3203b20d22d697c31b71189322f16811d4ecfecd3 \ - --hash=sha256:5b0c970e2215572197b42f1cff58a908d734503ea54b326412c70d4692256391 \ - --hash=sha256:5cee0c74e93ed4f9d39007e439debcaadc519d7ea5c0afc3d590a3a7b2edf060 \ - --hash=sha256:669c8605dba6c798c1863157aefde959c1796671ffb342b80fcb80a4c0bc4c26 \ - --hash=sha256:66a1ad667b56e679e06ba73bb88c7309b3f48a4c279bd3afea29f65a766e9036 \ - --hash=sha256:683779e5728ac9138406c59a11e09cd98c7d2c12f0a5fc2b9c5eecdbb4a00075 \ - --hash=sha256:6bb391659a747cf4f181a227c3e64b6d197100d53da98dcd766cc158bdd9ec68 \ - --hash=sha256:81f0674fa50b38b6793cd84fae5d67f58f74c2d974d2cb4e476d26eee33343d0 \ - --hash=sha256:927e59c694e039c75d7023465d311277a1fc29ed7236b5746e9dddf180393113 \ - --hash=sha256:932d2d7d3cab44cfa275601c982f30c2d874722ef6396bb539e41e4dc4618ed4 \ - --hash=sha256:a52419d9ba5906db516109660b114faf791136c94c1a636ed6b29cbfff9187ee \ - --hash=sha256:b156a971bc451c68c9e1f97567c94fd44155f073e3bceb1b0d195fd98ed12048 \ - --hash=sha256:bcf1a82b770b8f8c1e495b19a20d8296f875a796c4fe6e91da5ef107f18c5ecb \ - --hash=sha256:cb2a9cf1150302d69bb99861c5cddc9c25aceacb0a4ef5299785d0f5389a3209 \ - --hash=sha256:d8c709f4700573deb2036d240d140934df7e852520f4a584b2a8d5443b71f54d \ - --hash=sha256:db45b94885000981522fb92349e6b76f5aee0924cc5315881239c7859883117d \ - --hash=sha256:ddf46b940ef815af4e542697eaf071f0531449407a7607dd731bf23d156e20a7 \ - --hash=sha256:e675f8fe9aa6c418dc8d3aac0087b5294c1a4527f1eacf9fe5ea671685285454 \ - --hash=sha256:eb7e8cf2cf11a2580088009b43de84cabbf6f5dae94ceb489f28dba01a17cb77 \ - --hash=sha256:f340331a3f411910adfb4bbe46c2ed5872d9e473a783d7f14ecf49bc0869c594 - # via - # altair - # streamlit - # test-tube -pathtools==0.1.2 \ - --hash=sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0 - # via wandb -picklescan==0.0.5 \ - --hash=sha256:368cf1b9a075bc1b6460ad82b694f260532b836c82f99d13846cd36e1bbe7f9a \ - --hash=sha256:57153eca04d5df5009f2cdd595aef261b8a6f27e03046a1c84f672aa6869c592 - # via -r binary_installer/requirements.in -pillow==9.3.0 \ - --hash=sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040 \ - --hash=sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8 \ - --hash=sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65 \ - --hash=sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2 \ - --hash=sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627 \ - --hash=sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07 \ - --hash=sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef \ - --hash=sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535 \ - --hash=sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c \ - --hash=sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc \ - --hash=sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3 \ - --hash=sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1 \ - --hash=sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c \ - --hash=sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa \ - --hash=sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32 \ - --hash=sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502 \ - --hash=sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4 \ - --hash=sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f \ - --hash=sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812 \ - --hash=sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636 \ - --hash=sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20 \ - --hash=sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c \ - --hash=sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91 \ - --hash=sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe \ - --hash=sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b \ - --hash=sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad \ - --hash=sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9 \ - --hash=sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72 \ - --hash=sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4 \ - --hash=sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de \ - --hash=sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29 \ - --hash=sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee \ - --hash=sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c \ - --hash=sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7 \ - --hash=sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11 \ - --hash=sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c \ - --hash=sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c \ - --hash=sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448 \ - --hash=sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b \ - --hash=sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20 \ - --hash=sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228 \ - --hash=sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd \ - --hash=sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699 \ - --hash=sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b \ - --hash=sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2 \ - --hash=sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4 \ - --hash=sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c \ - --hash=sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f \ - --hash=sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2 \ - --hash=sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c \ - --hash=sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3 \ - --hash=sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193 \ - --hash=sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48 \ - --hash=sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02 \ - --hash=sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8 \ - --hash=sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e \ - --hash=sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f \ - --hash=sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b \ - --hash=sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74 \ - --hash=sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb \ - --hash=sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0 - # via - # basicsr - # clean-fid - # diffusers - # facexlib - # imageio - # k-diffusion - # matplotlib - # pypatchmatch - # realesrgan - # scikit-image - # streamlit - # torch-fidelity - # torchvision -promise==2.3 \ - --hash=sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0 - # via wandb -protobuf==3.19.6 \ - --hash=sha256:010be24d5a44be7b0613750ab40bc8b8cedc796db468eae6c779b395f50d1fa1 \ - --hash=sha256:0469bc66160180165e4e29de7f445e57a34ab68f49357392c5b2f54c656ab25e \ - --hash=sha256:0c0714b025ec057b5a7600cb66ce7c693815f897cfda6d6efb58201c472e3437 \ - --hash=sha256:11478547958c2dfea921920617eb457bc26867b0d1aa065ab05f35080c5d9eb6 \ - --hash=sha256:14082457dc02be946f60b15aad35e9f5c69e738f80ebbc0900a19bc83734a5a4 \ - --hash=sha256:2b2d2913bcda0e0ec9a784d194bc490f5dc3d9d71d322d070b11a0ade32ff6ba \ - --hash=sha256:30a15015d86b9c3b8d6bf78d5b8c7749f2512c29f168ca259c9d7727604d0e39 \ - --hash=sha256:30f5370d50295b246eaa0296533403961f7e64b03ea12265d6dfce3a391d8992 \ - --hash=sha256:347b393d4dd06fb93a77620781e11c058b3b0a5289262f094379ada2920a3730 \ - --hash=sha256:4bc98de3cdccfb5cd769620d5785b92c662b6bfad03a202b83799b6ed3fa1fa7 \ - --hash=sha256:5057c64052a1f1dd7d4450e9aac25af6bf36cfbfb3a1cd89d16393a036c49157 \ - --hash=sha256:559670e006e3173308c9254d63facb2c03865818f22204037ab76f7a0ff70b5f \ - --hash=sha256:5a0d7539a1b1fb7e76bf5faa0b44b30f812758e989e59c40f77a7dab320e79b9 \ - --hash=sha256:5f5540d57a43042389e87661c6eaa50f47c19c6176e8cf1c4f287aeefeccb5c4 \ - --hash=sha256:7a552af4dc34793803f4e735aabe97ffc45962dfd3a237bdde242bff5a3de684 \ - --hash=sha256:84a04134866861b11556a82dd91ea6daf1f4925746b992f277b84013a7cc1229 \ - --hash=sha256:878b4cd080a21ddda6ac6d1e163403ec6eea2e206cf225982ae04567d39be7b0 \ - --hash=sha256:90b0d02163c4e67279ddb6dc25e063db0130fc299aefabb5d481053509fae5c8 \ - --hash=sha256:91d5f1e139ff92c37e0ff07f391101df77e55ebb97f46bbc1535298d72019462 \ - --hash=sha256:a8ce5ae0de28b51dff886fb922012dad885e66176663950cb2344c0439ecb473 \ - --hash=sha256:aa3b82ca1f24ab5326dcf4ea00fcbda703e986b22f3d27541654f749564d778b \ - --hash=sha256:bb6776bd18f01ffe9920e78e03a8676530a5d6c5911934c6a1ac6eb78973ecb6 \ - --hash=sha256:bbf5cea5048272e1c60d235c7bd12ce1b14b8a16e76917f371c718bd3005f045 \ - --hash=sha256:c0ccd3f940fe7f3b35a261b1dd1b4fc850c8fde9f74207015431f174be5976b3 \ - --hash=sha256:d0b635cefebd7a8a0f92020562dead912f81f401af7e71f16bf9506ff3bdbb38 - # via - # streamlit - # tb-nightly - # tensorboard - # wandb -psutil==5.9.3 \ - --hash=sha256:07d880053c6461c9b89cd5d4808f3b8336665fa3acdefd6777662c5ed73a851a \ - --hash=sha256:12500d761ac091f2426567f19f95fd3f15a197d96befb44a5c1e3cbe6db5752c \ - --hash=sha256:1b540599481c73408f6b392cdffef5b01e8ff7a2ac8caae0a91b8222e88e8f1e \ - --hash=sha256:35feafe232d1aaf35d51bd42790cbccb882456f9f18cdc411532902370d660df \ - --hash=sha256:3a7826e68b0cf4ce2c1ee385d64eab7d70e3133171376cac53d7c1790357ec8f \ - --hash=sha256:46907fa62acaac364fff0b8a9da7b360265d217e4fdeaca0a2397a6883dffba2 \ - --hash=sha256:4bd4854f0c83aa84a5a40d3b5d0eb1f3c128f4146371e03baed4589fe4f3c931 \ - --hash=sha256:538fcf6ae856b5e12d13d7da25ad67f02113c96f5989e6ad44422cb5994ca7fc \ - --hash=sha256:547ebb02031fdada635452250ff39942db8310b5c4a8102dfe9384ee5791e650 \ - --hash=sha256:5e8b50241dd3c2ed498507f87a6602825073c07f3b7e9560c58411c14fe1e1c9 \ - --hash=sha256:5fa88e3d5d0b480602553d362c4b33a63e0c40bfea7312a7bf78799e01e0810b \ - --hash=sha256:68fa227c32240c52982cb931801c5707a7f96dd8927f9102d6c7771ea1ff5698 \ - --hash=sha256:6ced1ad823ecfa7d3ce26fe8aa4996e2e53fb49b7fed8ad81c80958501ec0619 \ - --hash=sha256:71b1206e7909792d16933a0d2c1c7f04ae196186c51ba8567abae1d041f06dcb \ - --hash=sha256:767ef4fa33acda16703725c0473a91e1832d296c37c63896c7153ba81698f1ab \ - --hash=sha256:7ccfcdfea4fc4b0a02ca2c31de7fcd186beb9cff8207800e14ab66f79c773af6 \ - --hash=sha256:7e4939ff75149b67aef77980409f156f0082fa36accc475d45c705bb00c6c16a \ - --hash=sha256:828c9dc9478b34ab96be75c81942d8df0c2bb49edbb481f597314d92b6441d89 \ - --hash=sha256:8a4e07611997acf178ad13b842377e3d8e9d0a5bac43ece9bfc22a96735d9a4f \ - --hash=sha256:941a6c2c591da455d760121b44097781bc970be40e0e43081b9139da485ad5b7 \ - --hash=sha256:9a4af6ed1094f867834f5f07acd1250605a0874169a5fcadbcec864aec2496a6 \ - --hash=sha256:9ec296f565191f89c48f33d9544d8d82b0d2af7dd7d2d4e6319f27a818f8d1cc \ - --hash=sha256:9ec95df684583b5596c82bb380c53a603bb051cf019d5c849c47e117c5064395 \ - --hash=sha256:a04a1836894c8279e5e0a0127c0db8e198ca133d28be8a2a72b4db16f6cf99c1 \ - --hash=sha256:a3d81165b8474087bb90ec4f333a638ccfd1d69d34a9b4a1a7eaac06648f9fbe \ - --hash=sha256:b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71 \ - --hash=sha256:ba38cf9984d5462b506e239cf4bc24e84ead4b1d71a3be35e66dad0d13ded7c1 \ - --hash=sha256:beb57d8a1ca0ae0eb3d08ccaceb77e1a6d93606f0e1754f0d60a6ebd5c288837 \ - --hash=sha256:d266cd05bd4a95ca1c2b9b5aac50d249cf7c94a542f47e0b22928ddf8b80d1ef \ - --hash=sha256:d8c3cc6bb76492133474e130a12351a325336c01c96a24aae731abf5a47fe088 \ - --hash=sha256:db8e62016add2235cc87fb7ea000ede9e4ca0aa1f221b40cef049d02d5d2593d \ - --hash=sha256:e7507f6c7b0262d3e7b0eeda15045bf5881f4ada70473b87bc7b7c93b992a7d7 \ - --hash=sha256:ed15edb14f52925869250b1375f0ff58ca5c4fa8adefe4883cfb0737d32f5c02 \ - --hash=sha256:f57d63a2b5beaf797b87024d018772439f9d3103a395627b77d17a8d72009543 \ - --hash=sha256:fa5e32c7d9b60b2528108ade2929b115167fe98d59f89555574715054f50fa31 \ - --hash=sha256:fe79b4ad4836e3da6c4650cb85a663b3a51aef22e1a829c384e18fae87e5e727 - # via - # accelerate - # wandb -pyarrow==10.0.0 \ - --hash=sha256:10e031794d019425d34406edffe7e32157359e9455f9edb97a1732f8dabf802f \ - --hash=sha256:25f51dca780fc22cfd7ac30f6bdfe70eb99145aee9acfda987f2c49955d66ed9 \ - --hash=sha256:2d326a9d47ac237d81b8c4337e9d30a0b361835b536fc7ea53991455ce761fbd \ - --hash=sha256:3d2694f08c8d4482d14e3798ff036dbd81ae6b1c47948f52515e1aa90fbec3f0 \ - --hash=sha256:4051664d354b14939b5da35cfa77821ade594bc1cf56dd2032b3068c96697d74 \ - --hash=sha256:511735040b83f2993f78d7fb615e7b88253d75f41500e87e587c40156ff88120 \ - --hash=sha256:65d4a312f3ced318423704355acaccc7f7bdfe242472e59bdd54aa0f8837adf8 \ - --hash=sha256:68ccb82c04c0f7abf7a95541d5e9d9d94290fc66a2d36d3f6ea0777f40c15654 \ - --hash=sha256:69b8a1fd99201178799b02f18498633847109b701856ec762f314352a431b7d0 \ - --hash=sha256:758284e1ebd3f2a9abb30544bfec28d151a398bb7c0f2578cbca5ee5b000364a \ - --hash=sha256:7be7f42f713068293308c989a4a3a2de03b70199bdbe753901c6595ff8640c64 \ - --hash=sha256:7ce026274cd5d9934cd3694e89edecde4e036018bbc6cb735fd33b9e967e7d47 \ - --hash=sha256:7e6b837cc44cd62a0e280c8fc4de94ebce503d6d1190e6e94157ab49a8bea67b \ - --hash=sha256:b153b05765393557716e3729cf988442b3ae4f5567364ded40d58c07feed27c2 \ - --hash=sha256:b3e3148468d3eed3779d68241f1d13ed4ee7cca4c6dbc7c07e5062b93ad4da33 \ - --hash=sha256:b45f969ed924282e9d4ede38f3430630d809c36dbff65452cabce03141943d28 \ - --hash=sha256:b9f63ceb8346aac0bcb487fafe9faca642ad448ca649fcf66a027c6e120cbc12 \ - --hash=sha256:c79300e1a3e23f2bf4defcf0d70ff5ea25ef6ebf6f121d8670ee14bb662bb7ca \ - --hash=sha256:d45a59e2f47826544c0ca70bc0f7ed8ffa5ad23f93b0458230c7e983bcad1acf \ - --hash=sha256:e4c6da9f9e1ff96781ee1478f7cc0860e66c23584887b8e297c4b9905c3c9066 \ - --hash=sha256:f329951d56b3b943c353f7b27c894e02367a7efbb9fef7979c6b24e02dbfcf55 \ - --hash=sha256:f76157d9579571c865860e5fd004537c03e21139db76692d96fd8a186adab1f2 - # via streamlit -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 - # via google-auth -pydeck==0.8.0 \ - --hash=sha256:07edde833f7cfcef6749124351195aa7dcd24663d4909fd7898dbd0b6fbc01ec \ - --hash=sha256:a8fa7757c6f24bba033af39db3147cb020eef44012ba7e60d954de187f9ed4d5 - # via streamlit -pydeprecate==0.3.2 \ - --hash=sha256:d481116cc5d7f6c473e7c4be820efdd9b90a16b594b350276e9e66a6cb5bdd29 \ - --hash=sha256:ed86b68ed837e6465245904a3de2f59bf9eef78ac7a2502ee280533d04802457 - # via pytorch-lightning -pygments==2.13.0 \ - --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ - --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 - # via rich -pympler==1.0.1 \ - --hash=sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa \ - --hash=sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d - # via streamlit -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc - # via - # matplotlib - # packaging -pypatchmatch @ https://github.com/invoke-ai/PyPatchMatch/archive/129863937a8ab37f6bbcec327c994c0f932abdbc.zip \ - --hash=sha256:4ad6ec95379e7d122d494ff76633cc7cf9b71330d5efda147fceba81e3dc6cd2 - # via -r binary_installer/requirements.in -pyreadline3==3.4.1 \ - --hash=sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae \ - --hash=sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb - # via -r binary_installer/requirements.in -pyrsistent==0.19.2 \ - --hash=sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed \ - --hash=sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb \ - --hash=sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a \ - --hash=sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95 \ - --hash=sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712 \ - --hash=sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73 \ - --hash=sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41 \ - --hash=sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b \ - --hash=sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78 \ - --hash=sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab \ - --hash=sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308 \ - --hash=sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425 \ - --hash=sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2 \ - --hash=sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e \ - --hash=sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6 \ - --hash=sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2 \ - --hash=sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a \ - --hash=sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291 \ - --hash=sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584 \ - --hash=sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a \ - --hash=sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0 \ - --hash=sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770 - # via jsonschema -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 - # via - # matplotlib - # pandas - # streamlit -python-engineio==4.3.4 \ - --hash=sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c \ - --hash=sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae - # via python-socketio -python-socketio==5.7.2 \ - --hash=sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097 \ - --hash=sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3 - # via flask-socketio -pytorch-lightning==1.7.7 \ - --hash=sha256:27c2dd01a18db2415168e3fa3775ccb5a1fa1e2961a50439ad9365507fe9d4ae \ - --hash=sha256:4438b8284d7f7fdb06cf3566a7b5b6f102ac8971cf7bb6d3f1b1de64628241f3 - # via taming-transformers-rom1504 -pytz==2022.6 \ - --hash=sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427 \ - --hash=sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2 - # via pandas -pytz-deprecation-shim==0.1.0.post0 \ - --hash=sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6 \ - --hash=sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d - # via tzlocal -pywavelets==1.4.1 \ - --hash=sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b \ - --hash=sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4 \ - --hash=sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4 \ - --hash=sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6 \ - --hash=sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de \ - --hash=sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c \ - --hash=sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa \ - --hash=sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93 \ - --hash=sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875 \ - --hash=sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd \ - --hash=sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356 \ - --hash=sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1 \ - --hash=sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c \ - --hash=sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2 \ - --hash=sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784 \ - --hash=sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4 \ - --hash=sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b \ - --hash=sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc \ - --hash=sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966 \ - --hash=sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e \ - --hash=sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426 \ - --hash=sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c \ - --hash=sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202 \ - --hash=sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc \ - --hash=sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd - # via scikit-image -pyyaml==6.0 \ - --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \ - --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \ - --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \ - --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \ - --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \ - --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \ - --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \ - --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \ - --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \ - --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \ - --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \ - --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \ - --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \ - --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \ - --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \ - --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \ - --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \ - --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \ - --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \ - --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \ - --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \ - --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \ - --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \ - --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \ - --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \ - --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \ - --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \ - --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \ - --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \ - --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \ - --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \ - --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \ - --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \ - --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \ - --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \ - --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \ - --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \ - --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \ - --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \ - --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5 - # via - # accelerate - # albumentations - # basicsr - # gfpgan - # huggingface-hub - # omegaconf - # pytorch-lightning - # transformers - # wandb -qudida==0.0.4 \ - --hash=sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6 \ - --hash=sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8 - # via albumentations -realesrgan==0.3.0 \ - --hash=sha256:0d36da96ab9f447071606e91f502ccdfb08f80cc82ee4f8caf720c7745ccec7e \ - --hash=sha256:59336c16c30dd5130eff350dd27424acb9b7281d18a6810130e265606c9a6088 - # via -r binary_installer/requirements.in -regex==2022.10.31 \ - --hash=sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad \ - --hash=sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4 \ - --hash=sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd \ - --hash=sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc \ - --hash=sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d \ - --hash=sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066 \ - --hash=sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec \ - --hash=sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9 \ - --hash=sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e \ - --hash=sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8 \ - --hash=sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e \ - --hash=sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783 \ - --hash=sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6 \ - --hash=sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1 \ - --hash=sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c \ - --hash=sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4 \ - --hash=sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1 \ - --hash=sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1 \ - --hash=sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7 \ - --hash=sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8 \ - --hash=sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe \ - --hash=sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d \ - --hash=sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b \ - --hash=sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8 \ - --hash=sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c \ - --hash=sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af \ - --hash=sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49 \ - --hash=sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714 \ - --hash=sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542 \ - --hash=sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318 \ - --hash=sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e \ - --hash=sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5 \ - --hash=sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc \ - --hash=sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144 \ - --hash=sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453 \ - --hash=sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5 \ - --hash=sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61 \ - --hash=sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11 \ - --hash=sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a \ - --hash=sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54 \ - --hash=sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73 \ - --hash=sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc \ - --hash=sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347 \ - --hash=sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c \ - --hash=sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66 \ - --hash=sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c \ - --hash=sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93 \ - --hash=sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443 \ - --hash=sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc \ - --hash=sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1 \ - --hash=sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892 \ - --hash=sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8 \ - --hash=sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001 \ - --hash=sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa \ - --hash=sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90 \ - --hash=sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c \ - --hash=sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0 \ - --hash=sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692 \ - --hash=sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4 \ - --hash=sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5 \ - --hash=sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690 \ - --hash=sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83 \ - --hash=sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66 \ - --hash=sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f \ - --hash=sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f \ - --hash=sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4 \ - --hash=sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee \ - --hash=sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81 \ - --hash=sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95 \ - --hash=sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9 \ - --hash=sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff \ - --hash=sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e \ - --hash=sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5 \ - --hash=sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6 \ - --hash=sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7 \ - --hash=sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1 \ - --hash=sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394 \ - --hash=sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6 \ - --hash=sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742 \ - --hash=sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57 \ - --hash=sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b \ - --hash=sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7 \ - --hash=sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b \ - --hash=sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244 \ - --hash=sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af \ - --hash=sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185 \ - --hash=sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8 \ - --hash=sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5 - # via - # clip - # diffusers - # transformers -requests==2.25.1 \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e - # via - # basicsr - # clean-fid - # diffusers - # fsspec - # huggingface-hub - # requests-oauthlib - # streamlit - # tb-nightly - # tensorboard - # torchvision - # transformers - # wandb -requests-oauthlib==1.3.1 \ - --hash=sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5 \ - --hash=sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a - # via google-auth-oauthlib -resize-right==0.0.2 \ - --hash=sha256:78351ca3eda0872208fcbc90861b45de559f90fb4027ce41825fdeb9b995005c \ - --hash=sha256:7dc35b72ce4012b77f7cc9049835163793ab98a58ab8893610fb119fe59af520 - # via k-diffusion -rich==12.6.0 \ - --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ - --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 - # via streamlit -rsa==4.9 \ - --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ - --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 - # via google-auth -scikit-image==0.19.3 \ - --hash=sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099 \ - --hash=sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d \ - --hash=sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790 \ - --hash=sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450 \ - --hash=sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef \ - --hash=sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245 \ - --hash=sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919 \ - --hash=sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6 \ - --hash=sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8 \ - --hash=sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d \ - --hash=sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19 \ - --hash=sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2 \ - --hash=sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92 \ - --hash=sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced \ - --hash=sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e \ - --hash=sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732 \ - --hash=sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34 \ - --hash=sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83 \ - --hash=sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f \ - --hash=sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9 \ - --hash=sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe \ - --hash=sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7 \ - --hash=sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b \ - --hash=sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6 \ - --hash=sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc \ - --hash=sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5 - # via - # albumentations - # basicsr - # k-diffusion -scikit-learn==1.1.3 \ - --hash=sha256:23fb9e74b813cc2528b5167d82ed08950b11106ccf50297161875e45152fb311 \ - --hash=sha256:250da993701da88bf475e7c5746abf1285ea0ae47e4d0917cd13afd6600bb162 \ - --hash=sha256:28b2bd6a1419acd522ff45d282c8ba23dbccb5338802ab0ee12baa4ade0aba4c \ - --hash=sha256:2ee2c649f2231b68511aabb0dc827edd8936aad682acc6263c34aed11bc95dac \ - --hash=sha256:30e27721adc308e8fd9f419f43068e43490005f911edf4476a9e585059fa8a83 \ - --hash=sha256:38814f66285318f2e241305cca545eaa9b4126c65aa5dd78c69371f235f78e2b \ - --hash=sha256:4d3a19166d4e1cdfcab975c68f471e046ce01e74c42a9a33fa89a14c2fcedf60 \ - --hash=sha256:5699cded6c0685426433c7e5afe0fecad80ec831ec7fa264940e50c796775cc5 \ - --hash=sha256:6785b8a3093329bf90ac01801be5525551728ae73edb11baa175df660820add4 \ - --hash=sha256:6d1c1394e38a3319ace620381f6f23cc807d8780e9915c152449a86fc8f1db21 \ - --hash=sha256:701181792a28c82fecae12adb5d15d0ecf57bffab7cf4bdbb52c7b3fd428d540 \ - --hash=sha256:748f2bd632d6993e8918d43f1a26c380aeda4e122a88840d4c3a9af99d4239fe \ - --hash=sha256:8e9dd76c7274055d1acf4526b8efb16a3531c26dcda714a0c16da99bf9d41900 \ - --hash=sha256:bef51978a51ec19977700fe7b86aecea49c825884f3811756b74a3b152bb4e35 \ - --hash=sha256:cd55c6fbef7608dbce1f22baf289dfcc6eb323247daa3c3542f73d389c724786 \ - --hash=sha256:da5a2e95fef9805b1750e4abda4e834bf8835d26fc709a391543b53feee7bd0e \ - --hash=sha256:ee47f68d973cee7009f06edb956f2f5588a0f230f24a2a70175fd0ecf36e2653 \ - --hash=sha256:f4931f2a6c06e02c6c17a05f8ae397e2545965bc7a0a6cb38c8cd7d4fba8624d \ - --hash=sha256:f5644663987ee221f5d1f47a593271b966c271c236fe05634e6bdc06041b5a2b \ - --hash=sha256:f5d4231af7199531e77da1b78a4cc6b3d960a00b1ec672578ac818aae2b9c35d \ - --hash=sha256:fd3ee69d36d42a7dcbb17e355a5653af5fd241a7dfd9133080b3dde8d9e2aafb - # via qudida -scipy==1.9.3 \ - --hash=sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31 \ - --hash=sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108 \ - --hash=sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0 \ - --hash=sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b \ - --hash=sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e \ - --hash=sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e \ - --hash=sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5 \ - --hash=sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840 \ - --hash=sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58 \ - --hash=sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523 \ - --hash=sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd \ - --hash=sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab \ - --hash=sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c \ - --hash=sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb \ - --hash=sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096 \ - --hash=sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0 \ - --hash=sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc \ - --hash=sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9 \ - --hash=sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c \ - --hash=sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95 \ - --hash=sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027 - # via - # albumentations - # basicsr - # clean-fid - # clipseg - # facexlib - # filterpy - # gfpgan - # k-diffusion - # scikit-image - # scikit-learn - # torch-fidelity - # torchdiffeq - # torchsde -semver==2.13.0 \ - --hash=sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4 \ - --hash=sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f - # via streamlit -send2trash==1.8.0 \ - --hash=sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d \ - --hash=sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08 - # via -r binary_installer/requirements.in -sentry-sdk==1.10.1 \ - --hash=sha256:06c0fa9ccfdc80d7e3b5d2021978d6eb9351fa49db9b5847cf4d1f2a473414ad \ - --hash=sha256:105faf7bd7b7fa25653404619ee261527266b14103fe1389e0ce077bd23a9691 - # via wandb -setproctitle==1.3.2 \ - --hash=sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b \ - --hash=sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9 \ - --hash=sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31 \ - --hash=sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83 \ - --hash=sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f \ - --hash=sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca \ - --hash=sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494 \ - --hash=sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe \ - --hash=sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172 \ - --hash=sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084 \ - --hash=sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4 \ - --hash=sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1 \ - --hash=sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c \ - --hash=sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973 \ - --hash=sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c \ - --hash=sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202 \ - --hash=sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5 \ - --hash=sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65 \ - --hash=sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18 \ - --hash=sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13 \ - --hash=sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005 \ - --hash=sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02 \ - --hash=sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7 \ - --hash=sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665 \ - --hash=sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f \ - --hash=sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1 \ - --hash=sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989 \ - --hash=sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827 \ - --hash=sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42 \ - --hash=sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927 \ - --hash=sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122 \ - --hash=sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9 \ - --hash=sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f \ - --hash=sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d \ - --hash=sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98 \ - --hash=sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796 \ - --hash=sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb \ - --hash=sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd \ - --hash=sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe \ - --hash=sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806 \ - --hash=sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484 \ - --hash=sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e \ - --hash=sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575 \ - --hash=sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76 \ - --hash=sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246 \ - --hash=sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb \ - --hash=sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099 \ - --hash=sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c \ - --hash=sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258 \ - --hash=sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865 \ - --hash=sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b \ - --hash=sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d \ - --hash=sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10 \ - --hash=sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f \ - --hash=sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94 \ - --hash=sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6 \ - --hash=sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963 \ - --hash=sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4 \ - --hash=sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8 \ - --hash=sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761 - # via wandb -shortuuid==1.0.9 \ - --hash=sha256:459f12fa1acc34ff213b1371467c0325169645a31ed989e268872339af7563d5 \ - --hash=sha256:b2bb9eb7773170e253bb7ba25971023acb473517a8b76803d9618668cb1dd46f - # via wandb -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # docker-pycreds - # eventlet - # flask-cors - # google-auth - # grpcio - # promise - # python-dateutil - # validators - # wandb -smmap==5.0.0 \ - --hash=sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94 \ - --hash=sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936 - # via gitdb -streamlit==1.14.0 \ - --hash=sha256:62556d873567e1b3427bcd118a57ee6946619f363bd6bba38df2d1f8225ecba0 \ - --hash=sha256:e078b8143d150ba721bdb9194218e311c5fe1d6d4156473a2dea6cc848a6c9fc - # via -r binary_installer/requirements.in -taming-transformers-rom1504==0.0.6 \ - --hash=sha256:051b5804c58caa247bcd51d17ddb525b4d5f892a29d42dc460f40e3e9e34e5d8 \ - --hash=sha256:73fe5fc1108accee4236ee6976e0987ab236afad0af06cb9f037641a908d2c32 - # via -r binary_installer/requirements.in -tb-nightly==2.11.0a20221106 \ - --hash=sha256:8940457ee42db92f01da8bcdbbea1a476735eda559dde5976f5728919960af4a - # via - # basicsr - # gfpgan -tensorboard==2.10.1 \ - --hash=sha256:fb9222c1750e2fa35ef170d998a1e229f626eeced3004494a8849c88c15d8c1c - # via - # pytorch-lightning - # test-tube -tensorboard-data-server==0.6.1 \ - --hash=sha256:809fe9887682d35c1f7d1f54f0f40f98bb1f771b14265b453ca051e2ce58fca7 \ - --hash=sha256:d8237580755e58eff68d1f3abefb5b1e39ae5c8b127cc40920f9c4fb33f4b98a \ - --hash=sha256:fa8cef9be4fcae2f2363c88176638baf2da19c5ec90addb49b1cde05c95c88ee - # via - # tb-nightly - # tensorboard -tensorboard-plugin-wit==1.8.1 \ - --hash=sha256:ff26bdd583d155aa951ee3b152b3d0cffae8005dc697f72b44a8e8c2a77a8cbe - # via - # tb-nightly - # tensorboard -test-tube==0.7.5 \ - --hash=sha256:1379c33eb8cde3e9b36610f87da0f16c2e06496b1cfebac473df4e7be2faa124 - # via -r binary_installer/requirements.in -threadpoolctl==3.1.0 \ - --hash=sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b \ - --hash=sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380 - # via scikit-learn -tifffile==2022.10.10 \ - --hash=sha256:50b61ba943b866d191295bc38a00191c9fdab23ece063544c7f1a264e3f6aa8e \ - --hash=sha256:87f3aee8a0d06b74655269a105de75c1958a24653e1930d523eb516100043503 - # via scikit-image -tokenizers==0.13.1 \ - --hash=sha256:0a3412830ad66a643723d6b0fc3202e64e9e299bd9c9eda04b2914b5b1e0ddb0 \ - --hash=sha256:126bcb18a77cf65961ece04f54bd10ef3713412195e543d9d3eda2f0e4fd677c \ - --hash=sha256:16434c61c5eb72f6692b9bc52052b07ca92d3eba9dd72a1bc371890e1bdc3f07 \ - --hash=sha256:1d4acfdb6e7ef974677bb8445462db7fed240185fdc0f5b061db357d4ef8d85d \ - --hash=sha256:3333d1cee5c8f47c96362ea0abc1f81c77c9b92c6c3d11cbf1d01985f0d5cf1d \ - --hash=sha256:3acf3cae4c4739fc9ec49fa0e6cce224c1aa0fabc8f8d1358fd7de5c7d49cdca \ - --hash=sha256:3ba43b3f6f6b41c97c1d041785342cd72ba142999f6c4605d628e8e937398f20 \ - --hash=sha256:3c69a8389fd88bc32115e99db70f63bef577ba5c54f40a632580038a49612856 \ - --hash=sha256:3de653a551cc616a442a123da21706cb3a3163cf6919973f978f0921eee1bdf0 \ - --hash=sha256:4b3be8af87b357340b9b049d28067287b5e5e296e3120b6e4875d3b8469b67e6 \ - --hash=sha256:680bc0e357b7da6d0d01634bffbd002e866fdaccde303e1d1af58f32464cf308 \ - --hash=sha256:70de69681a264a5808d39f4bb6331be9a4dec51fd48cd1b959a94da76c4939cc \ - --hash=sha256:73198cda6e1d991c583ed798514863e16763aa600eb7aa6df7722373290575b2 \ - --hash=sha256:80864f456f715829f901ad5bb85af97e9ae52fc902270944804f6476ab8c6699 \ - --hash=sha256:80b9552295fdce0a2513dcb795a3f8591eca1a8dcf8afe0de3214209e6924ad1 \ - --hash=sha256:84fa41b58a8d3b7363ecdf3397d4b38f345fcf7d4dd14565b4360e7bffc9cae0 \ - --hash=sha256:890d2139100b5c8ac6d585438d5e145ede1d7b32b4090a6c078db6db0ef1daea \ - --hash=sha256:8b3f97041f7716998e474d3c7ffd19ac6941f117616696aef2b5ba927bf091e3 \ - --hash=sha256:910479e92d5fbdf91e8106b4c658fd43d418893d7cfd5fb11983c54a1ff53869 \ - --hash=sha256:96a1beef1e64d44597627f4e29d794047a66ad4d7474d93daf5a0ee27928e012 \ - --hash=sha256:98bef54cf51ac335fda1408112df7ff3e584107633bd9066616033e12b0bd519 \ - --hash=sha256:afcb1bd6d9ed59d5c8e633765589cab12f98aae09804f156b5965b4463b8b8e3 \ - --hash=sha256:b72dec85488af3e1e8d58fb4b86b5dbe5171c176002b5e098ea6d52a70004bb5 \ - --hash=sha256:c3109ba62bea56c68c7c2a976250b040afee61b5f86fc791f17afaa2a09fce94 \ - --hash=sha256:c73b9e6c107e980e65077b89c54311d8d645f6a9efdde82990777fa43c0a8cae \ - --hash=sha256:d8fca8b492a4697b0182e0c40b164cb0c44a9669d9c98033fec2f88174605eb0 \ - --hash=sha256:db6872294339bf35c158219fc65bad939ba87d14c936ae7a33a3ca2d1532c5b1 \ - --hash=sha256:e1a90bc97f53600f52e902f3ae097424de712d8ae0e42d957efc7ed908573a20 \ - --hash=sha256:f75f476fe183c03c515a0f0f5d195cb05d93fcdc76e31fe3c9753d01f3ee990b \ - --hash=sha256:fd17b14f84bec0b171869abd17ca0d9bfc564aa1e7f3451f44da526949a911c1 \ - --hash=sha256:fea71780b66f8c278ebae7221c8959404cf7343b8d2f4b7308aa668cf6f02364 - # via transformers -toml==0.10.2 \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f - # via streamlit -toolz==0.12.0 \ - --hash=sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f \ - --hash=sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194 - # via altair -torch==1.12.0+cu116 ; platform_system == "Linux" or platform_system == "Windows" \ - --hash=sha256:1d9557d1e871794a31a71c40dec8589d6c3347f3f2953a8dd74cfd58e1ecb52e \ - --hash=sha256:72538e4505087668a4642f861578dfed470fae5da20b1758b0f34e4a070d6b21 \ - --hash=sha256:74f5b137190a6face6859d630f129289e7fae6a4d9a747430b3b5d5c6297a3ae \ - --hash=sha256:7665e906995328746c6f70016ee90cafe50cbf434b6ef576e1de2678929ee63e \ - --hash=sha256:7ee1899e9afe5f5e35ba46bc70e17735d2c02cedede1fa69a288cc680b5ab3db \ - --hash=sha256:97d63afcb6358071737f8325aa933e9db2f30cd2f068591d27d4ea72f3cabad2 \ - --hash=sha256:aa43d7b54b86f723f17c5c44df1078c59a6149fc4d42fbef08aafab9d61451c9 \ - --hash=sha256:f772be831447dd01ebd26cbedf619e668d1b269d69bf6b4ff46b1378362bff26 - # via - # -r binary_installer/requirements.in - # accelerate - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # kornia - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # test-tube - # torch-fidelity - # torchdiffeq - # torchmetrics - # torchsde - # torchvision -torch-fidelity==0.3.0 \ - --hash=sha256:3d3e33db98919759cc4f3f24cb27e1e74bdc7c905d90a780630e4e1c18492b66 \ - --hash=sha256:d01284825595feb7dc3eae3dc9a0d8ced02be764813a3483f109bc142b52a1d3 - # via -r binary_installer/requirements.in -torchdiffeq==0.2.3 \ - --hash=sha256:b5b01ec1294a2d8d5f77e567bf17c5de1237c0573cb94deefa88326f0e18c338 \ - --hash=sha256:fe75f434b9090ac0c27702e02bed21472b0f87035be6581f51edc5d4013ea31a - # via k-diffusion -torchmetrics==0.10.2 \ - --hash=sha256:43757d82266969906fc74b6e80766fcb2a0d52d6c3d09e3b7c98cf3b733fd20c \ - --hash=sha256:daa29d96bff5cff04d80eec5b9f5076993d6ac9c2d2163e88b6b31f8d38f7c25 - # via pytorch-lightning -torchsde==0.2.5 \ - --hash=sha256:222be9e15610d37a4b5a71cfa0c442178f9fd9ca02f6522a3e11c370b3d0906b \ - --hash=sha256:4c34373a94a357bdf60bbfee00c850f3563d634491555820b900c9a4f7eff300 - # via k-diffusion -torchvision==0.13.0+cu116 ; platform_system == "Linux" or platform_system == "Windows" \ - --hash=sha256:1696feadf1921c8fa1549bad774221293298288ebedaa14e44bc3e57e964a369 \ - --hash=sha256:572544b108eaf12638f3dca0f496a453c4b8d8256bcc8333d5355df641c0380c \ - --hash=sha256:76dbe71be271e2f246d556a8201c6f73a431851045d866c51bd945521817b892 \ - --hash=sha256:90b9461f57e1219ca900bfd9e85548b840ec56d57ec331b7a7eb871113b34c0a \ - --hash=sha256:941a8c958f2fe9184ce522567f4a471b52dd306891870e979fe6569062432258 \ - --hash=sha256:9ce27c87a8581d00dcef416ec75f8eca9c225d8c36b81150a1f2a60eb70155dc \ - --hash=sha256:cb6bf0117b8f4b601baeae54e8a6bb5c4942b054835ba997f438ddcb7adcfb90 \ - --hash=sha256:d1a3c124645e3460b3e50b54eb89a2575a5036bfa618f15dc4f5d635c716069d - # via - # -r binary_installer/requirements.in - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity -tornado==6.2 \ - --hash=sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca \ - --hash=sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72 \ - --hash=sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23 \ - --hash=sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8 \ - --hash=sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b \ - --hash=sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9 \ - --hash=sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13 \ - --hash=sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75 \ - --hash=sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac \ - --hash=sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e \ - --hash=sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b - # via streamlit -tqdm==4.64.1 \ - --hash=sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4 \ - --hash=sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1 - # via - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # huggingface-hub - # k-diffusion - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity - # transformers -trampoline==0.1.2 \ - --hash=sha256:36cc9a4ff9811843d177fc0e0740efbd7da39eadfe6e50c9e2937cbc06d899d9 - # via torchsde -transformers==4.24.0 \ - --hash=sha256:486f353a8e594002e48be0e2aba723d96eda839e63bfe274702a4b5eda85559b \ - --hash=sha256:b7ab50039ef9bf817eff14ab974f306fd20a72350bdc9df3a858fd009419322e - # via -r binary_installer/requirements.in -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e - # via - # huggingface-hub - # pytorch-lightning - # qudida - # streamlit - # torch - # torchvision -tzdata==2022.6 \ - --hash=sha256:04a680bdc5b15750c39c12a448885a51134a27ec9af83667663f0b3a1bf3f342 \ - --hash=sha256:91f11db4503385928c15598c98573e3af07e7229181bee5375bd30f1695ddcae - # via pytz-deprecation-shim -tzlocal==4.2 \ - --hash=sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745 \ - --hash=sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7 - # via streamlit -urllib3==1.26.12 \ - --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ - --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 - # via - # requests - # sentry-sdk -validators==0.18.2 \ - --hash=sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd \ - --hash=sha256:37cd9a9213278538ad09b5b9f9134266e7c226ab1fede1d500e29e0a8fbb9ea6 - # via streamlit -wandb==0.13.5 \ - --hash=sha256:11f30a22e30abaa9c187e8b6aa4c12d76160b40bbe98a6f14b0dde9297bbfbe2 \ - --hash=sha256:60d5bcc524b8a314c8e072c03f7702dbd5406261b00a4ce75e7556b805fdc765 - # via k-diffusion -watchdog==2.1.9 \ - --hash=sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412 \ - --hash=sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654 \ - --hash=sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306 \ - --hash=sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33 \ - --hash=sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd \ - --hash=sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7 \ - --hash=sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892 \ - --hash=sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609 \ - --hash=sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6 \ - --hash=sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1 \ - --hash=sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591 \ - --hash=sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d \ - --hash=sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d \ - --hash=sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c \ - --hash=sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3 \ - --hash=sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39 \ - --hash=sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213 \ - --hash=sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330 \ - --hash=sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428 \ - --hash=sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1 \ - --hash=sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846 \ - --hash=sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153 \ - --hash=sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3 \ - --hash=sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9 \ - --hash=sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658 - # via streamlit -wcwidth==0.2.5 \ - --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ - --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 - # via ftfy -werkzeug==2.2.2 \ - --hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \ - --hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5 - # via - # flask - # tb-nightly - # tensorboard -wheel==0.38.2 \ - --hash=sha256:3d492ef22379a156ec923d2a77051cedfd4df4b667864e9e41ba83f0b70b7149 \ - --hash=sha256:7a5a3095dceca97a3cac869b8fef4e89b83fafde21b6688f47b6fda7600eb441 - # via - # tb-nightly - # tensorboard -whichcraft==0.6.1 \ - --hash=sha256:acdbb91b63d6a15efbd6430d1d7b2d36e44a71697e93e19b7ded477afd9fce87 \ - --hash=sha256:deda9266fbb22b8c64fd3ee45c050d61139cd87419765f588e37c8d23e236dd9 - # via flaskwebgui -yapf==0.32.0 \ - --hash=sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32 \ - --hash=sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b - # via - # basicsr - # gfpgan -yarl==1.8.1 \ - --hash=sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb \ - --hash=sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3 \ - --hash=sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035 \ - --hash=sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453 \ - --hash=sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d \ - --hash=sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a \ - --hash=sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231 \ - --hash=sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f \ - --hash=sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae \ - --hash=sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b \ - --hash=sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3 \ - --hash=sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507 \ - --hash=sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd \ - --hash=sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae \ - --hash=sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe \ - --hash=sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c \ - --hash=sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4 \ - --hash=sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64 \ - --hash=sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357 \ - --hash=sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54 \ - --hash=sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461 \ - --hash=sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4 \ - --hash=sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497 \ - --hash=sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0 \ - --hash=sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1 \ - --hash=sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957 \ - --hash=sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350 \ - --hash=sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780 \ - --hash=sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843 \ - --hash=sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548 \ - --hash=sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6 \ - --hash=sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40 \ - --hash=sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee \ - --hash=sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b \ - --hash=sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6 \ - --hash=sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0 \ - --hash=sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e \ - --hash=sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880 \ - --hash=sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc \ - --hash=sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e \ - --hash=sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead \ - --hash=sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28 \ - --hash=sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf \ - --hash=sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd \ - --hash=sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae \ - --hash=sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0 \ - --hash=sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0 \ - --hash=sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae \ - --hash=sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda \ - --hash=sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546 \ - --hash=sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802 \ - --hash=sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be \ - --hash=sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07 \ - --hash=sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936 \ - --hash=sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272 \ - --hash=sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc \ - --hash=sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a \ - --hash=sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28 \ - --hash=sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b - # via aiohttp -zipp==3.10.0 \ - --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ - --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -setuptools==65.5.1 \ - --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ - --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f - # via - # numba - # tb-nightly - # tensorboard - # wandb diff --git a/binary_installer/py3.10-windows-x86_64-cuda-reqs.txt b/binary_installer/py3.10-windows-x86_64-cuda-reqs.txt deleted file mode 100644 index f7e9f6866f..0000000000 --- a/binary_installer/py3.10-windows-x86_64-cuda-reqs.txt +++ /dev/null @@ -1,2109 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: -# -# pip-compile --allow-unsafe --generate-hashes --output-file=installer/py3.10-windows-x86_64-cuda-reqs.txt installer/requirements.in -# ---extra-index-url https://download.pytorch.org/whl/torch_stable.html ---extra-index-url https://download.pytorch.org/whl/cu116 ---trusted-host https - -absl-py==1.3.0 \ - --hash=sha256:34995df9bd7a09b3b8749e230408f5a2a2dd7a68a0d33c12a3d0cb15a041a507 \ - --hash=sha256:463c38a08d2e4cef6c498b76ba5bd4858e4c6ef51da1a5a1f27139a022e20248 - # via - # tb-nightly - # tensorboard -accelerate==0.14.0 \ - --hash=sha256:31c5bcc40564ef849b5bc1c4424a43ccaf9e26413b7df89c2e36bf81f070fd44 \ - --hash=sha256:b15d562c0889d0cf441b01faa025dfc29b163d061b6cc7d489c2c83b0a55ffab - # via - # -r installer/requirements.in - # k-diffusion -addict==2.4.0 \ - --hash=sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc \ - --hash=sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494 - # via basicsr -aiohttp==3.8.3 \ - --hash=sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8 \ - --hash=sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142 \ - --hash=sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18 \ - --hash=sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34 \ - --hash=sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a \ - --hash=sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033 \ - --hash=sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06 \ - --hash=sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4 \ - --hash=sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d \ - --hash=sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b \ - --hash=sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc \ - --hash=sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091 \ - --hash=sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d \ - --hash=sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85 \ - --hash=sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb \ - --hash=sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937 \ - --hash=sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf \ - --hash=sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1 \ - --hash=sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b \ - --hash=sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d \ - --hash=sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269 \ - --hash=sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da \ - --hash=sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346 \ - --hash=sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494 \ - --hash=sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697 \ - --hash=sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4 \ - --hash=sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585 \ - --hash=sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c \ - --hash=sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da \ - --hash=sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad \ - --hash=sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2 \ - --hash=sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6 \ - --hash=sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c \ - --hash=sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849 \ - --hash=sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa \ - --hash=sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b \ - --hash=sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb \ - --hash=sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7 \ - --hash=sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715 \ - --hash=sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76 \ - --hash=sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d \ - --hash=sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276 \ - --hash=sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6 \ - --hash=sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37 \ - --hash=sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb \ - --hash=sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d \ - --hash=sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c \ - --hash=sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446 \ - --hash=sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008 \ - --hash=sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342 \ - --hash=sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d \ - --hash=sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7 \ - --hash=sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061 \ - --hash=sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba \ - --hash=sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7 \ - --hash=sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290 \ - --hash=sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0 \ - --hash=sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d \ - --hash=sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8 \ - --hash=sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f \ - --hash=sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48 \ - --hash=sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502 \ - --hash=sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62 \ - --hash=sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9 \ - --hash=sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403 \ - --hash=sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77 \ - --hash=sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476 \ - --hash=sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e \ - --hash=sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96 \ - --hash=sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5 \ - --hash=sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784 \ - --hash=sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091 \ - --hash=sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b \ - --hash=sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97 \ - --hash=sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a \ - --hash=sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2 \ - --hash=sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9 \ - --hash=sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d \ - --hash=sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73 \ - --hash=sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017 \ - --hash=sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363 \ - --hash=sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c \ - --hash=sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d \ - --hash=sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618 \ - --hash=sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491 \ - --hash=sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b \ - --hash=sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca - # via fsspec -aiosignal==1.2.0 \ - --hash=sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a \ - --hash=sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2 - # via aiohttp -albumentations==1.3.0 \ - --hash=sha256:294165d87d03bc8323e484927f0a5c1a3c64b0e7b9c32a979582a6c93c363bdf \ - --hash=sha256:be1af36832c8893314f2a5550e8ac19801e04770734c1b70fa3c996b41f37bed - # via -r installer/requirements.in -altair==4.2.0 \ - --hash=sha256:0c724848ae53410c13fa28be2b3b9a9dcb7b5caa1a70f7f217bd663bb419935a \ - --hash=sha256:d87d9372e63b48cd96b2a6415f0cf9457f50162ab79dc7a31cd7e024dd840026 - # via streamlit -antlr4-python3-runtime==4.9.3 \ - --hash=sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b - # via omegaconf -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c - # via aiohttp -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c - # via - # aiohttp - # jsonschema -basicsr==1.4.2 \ - --hash=sha256:b89b595a87ef964cda9913b4d99380ddb6554c965577c0c10cb7b78e31301e87 - # via - # gfpgan - # realesrgan -bidict==0.22.0 \ - --hash=sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0 \ - --hash=sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8 - # via python-socketio -blinker==1.5 \ - --hash=sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36 \ - --hash=sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462 - # via streamlit -boltons==21.0.0 \ - --hash=sha256:65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13 \ - --hash=sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b - # via torchsde -cachetools==5.2.0 \ - --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ - --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db - # via - # google-auth - # streamlit -certifi==2022.9.24 \ - --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ - --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 - # via - # requests - # sentry-sdk -chardet==4.0.0 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 - # via requests -charset-normalizer==2.1.1 \ - --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ - --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f - # via aiohttp -clean-fid==0.1.34 \ - --hash=sha256:2997f85a67a28c95adaae7899a33fc10537164fef4cdd424e3257bffad79a901 - # via k-diffusion -click==8.1.3 \ - --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ - --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 - # via - # flask - # streamlit - # wandb -clip @ https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip \ - --hash=sha256:b5842c25da441d6c581b53a5c60e0c2127ebafe0f746f8e15561a006c6c3be6a - # via - # -r installer/requirements.in - # clipseg -clipseg @ https://github.com/invoke-ai/clipseg/archive/1f754751c85d7d4255fa681f4491ff5711c1c288.zip \ - --hash=sha256:14f43ed42f90be3fe57f06de483cb8be0f67f87a6f62a011339d45a39f4b4189 - # via -r installer/requirements.in -colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via - # click - # tqdm -commonmark==0.9.1 \ - --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ - --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 - # via rich -contourpy==1.0.6 \ - --hash=sha256:0236875c5a0784215b49d00ebbe80c5b6b5d5244b3655a36dda88105334dea17 \ - --hash=sha256:03d1b9c6b44a9e30d554654c72be89af94fab7510b4b9f62356c64c81cec8b7d \ - --hash=sha256:0537cc1195245bbe24f2913d1f9211b8f04eb203de9044630abd3664c6cc339c \ - --hash=sha256:06ca79e1efbbe2df795822df2fa173d1a2b38b6e0f047a0ec7903fbca1d1847e \ - --hash=sha256:08e8d09d96219ace6cb596506fb9b64ea5f270b2fb9121158b976d88871fcfd1 \ - --hash=sha256:0b1e66346acfb17694d46175a0cea7d9036f12ed0c31dfe86f0f405eedde2bdd \ - --hash=sha256:0b97454ed5b1368b66ed414c754cba15b9750ce69938fc6153679787402e4cdf \ - --hash=sha256:0e4854cc02006ad6684ce092bdadab6f0912d131f91c2450ce6dbdea78ee3c0b \ - --hash=sha256:12a7dc8439544ed05c6553bf026d5e8fa7fad48d63958a95d61698df0e00092b \ - --hash=sha256:1b1ee48a130da4dd0eb8055bbab34abf3f6262957832fd575e0cab4979a15a41 \ - --hash=sha256:1c0e1308307a75e07d1f1b5f0f56b5af84538a5e9027109a7bcf6cb47c434e72 \ - --hash=sha256:1dedf4c64185a216c35eb488e6f433297c660321275734401760dafaeb0ad5c2 \ - --hash=sha256:208bc904889c910d95aafcf7be9e677726df9ef71e216780170dbb7e37d118fa \ - --hash=sha256:211dfe2bd43bf5791d23afbe23a7952e8ac8b67591d24be3638cabb648b3a6eb \ - --hash=sha256:341330ed19074f956cb20877ad8d2ae50e458884bfa6a6df3ae28487cc76c768 \ - --hash=sha256:344cb3badf6fc7316ad51835f56ac387bdf86c8e1b670904f18f437d70da4183 \ - --hash=sha256:358f6364e4873f4d73360b35da30066f40387dd3c427a3e5432c6b28dd24a8fa \ - --hash=sha256:371f6570a81dfdddbb837ba432293a63b4babb942a9eb7aaa699997adfb53278 \ - --hash=sha256:375d81366afd547b8558c4720337218345148bc2fcffa3a9870cab82b29667f2 \ - --hash=sha256:3a1917d3941dd58732c449c810fa7ce46cc305ce9325a11261d740118b85e6f3 \ - --hash=sha256:4081918147fc4c29fad328d5066cfc751da100a1098398742f9f364be63803fc \ - --hash=sha256:444fb776f58f4906d8d354eb6f6ce59d0a60f7b6a720da6c1ccb839db7c80eb9 \ - --hash=sha256:46deb310a276cc5c1fd27958e358cce68b1e8a515fa5a574c670a504c3a3fe30 \ - --hash=sha256:494efed2c761f0f37262815f9e3c4bb9917c5c69806abdee1d1cb6611a7174a0 \ - --hash=sha256:50627bf76abb6ba291ad08db583161939c2c5fab38c38181b7833423ab9c7de3 \ - --hash=sha256:5641927cc5ae66155d0c80195dc35726eae060e7defc18b7ab27600f39dd1fe7 \ - --hash=sha256:5b117d29433fc8393b18a696d794961464e37afb34a6eeb8b2c37b5f4128a83e \ - --hash=sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6 \ - --hash=sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142 \ - --hash=sha256:6f56515e7c6fae4529b731f6c117752247bef9cdad2b12fc5ddf8ca6a50965a5 \ - --hash=sha256:730c27978a0003b47b359935478b7d63fd8386dbb2dcd36c1e8de88cbfc1e9de \ - --hash=sha256:75a2e638042118118ab39d337da4c7908c1af74a8464cad59f19fbc5bbafec9b \ - --hash=sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb \ - --hash=sha256:7ee394502026d68652c2824348a40bf50f31351a668977b51437131a90d777ea \ - --hash=sha256:8468b40528fa1e15181cccec4198623b55dcd58306f8815a793803f51f6c474a \ - --hash=sha256:84c593aeff7a0171f639da92cb86d24954bbb61f8a1b530f74eb750a14685832 \ - --hash=sha256:913bac9d064cff033cf3719e855d4f1db9f1c179e0ecf3ba9fdef21c21c6a16a \ - --hash=sha256:9447c45df407d3ecb717d837af3b70cfef432138530712263730783b3d016512 \ - --hash=sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675 \ - --hash=sha256:9bc407a6af672da20da74823443707e38ece8b93a04009dca25856c2d9adadb1 \ - --hash=sha256:9e8e686a6db92a46111a1ee0ee6f7fbfae4048f0019de207149f43ac1812cf95 \ - --hash=sha256:9fc4e7973ed0e1fe689435842a6e6b330eb7ccc696080dda9a97b1a1b78e41db \ - --hash=sha256:a457ee72d9032e86730f62c5eeddf402e732fdf5ca8b13b41772aa8ae13a4563 \ - --hash=sha256:a628bba09ba72e472bf7b31018b6281fd4cc903f0888049a3724afba13b6e0b8 \ - --hash=sha256:a79d239fc22c3b8d9d3de492aa0c245533f4f4c7608e5749af866949c0f1b1b9 \ - --hash=sha256:aa4674cf3fa2bd9c322982644967f01eed0c91bb890f624e0e0daf7a5c3383e9 \ - --hash=sha256:acd2bd02f1a7adff3a1f33e431eb96ab6d7987b039d2946a9b39fe6fb16a1036 \ - --hash=sha256:b3b1bd7577c530eaf9d2bc52d1a93fef50ac516a8b1062c3d1b9bcec9ebe329b \ - --hash=sha256:b48d94386f1994db7c70c76b5808c12e23ed7a4ee13693c2fc5ab109d60243c0 \ - --hash=sha256:b64f747e92af7da3b85631a55d68c45a2d728b4036b03cdaba4bd94bcc85bd6f \ - --hash=sha256:b98c820608e2dca6442e786817f646d11057c09a23b68d2b3737e6dcb6e4a49b \ - --hash=sha256:c1baa49ab9fedbf19d40d93163b7d3e735d9cd8d5efe4cce9907902a6dad391f \ - --hash=sha256:c38c6536c2d71ca2f7e418acaf5bca30a3af7f2a2fa106083c7d738337848dbe \ - --hash=sha256:c78bfbc1a7bff053baf7e508449d2765964d67735c909b583204e3240a2aca45 \ - --hash=sha256:cd2bc0c8f2e8de7dd89a7f1c10b8844e291bca17d359373203ef2e6100819edd \ - --hash=sha256:d2eff2af97ea0b61381828b1ad6cd249bbd41d280e53aea5cccd7b2b31b8225c \ - --hash=sha256:d8834c14b8c3dd849005e06703469db9bf96ba2d66a3f88ecc539c9a8982e0ee \ - --hash=sha256:d912f0154a20a80ea449daada904a7eb6941c83281a9fab95de50529bfc3a1da \ - --hash=sha256:da1ef35fd79be2926ba80fbb36327463e3656c02526e9b5b4c2b366588b74d9a \ - --hash=sha256:dbe6fe7a1166b1ddd7b6d887ea6fa8389d3f28b5ed3f73a8f40ece1fc5a3d340 \ - --hash=sha256:dcd556c8fc37a342dd636d7eef150b1399f823a4462f8c968e11e1ebeabee769 \ - --hash=sha256:e13b31d1b4b68db60b3b29f8e337908f328c7f05b9add4b1b5c74e0691180109 \ - --hash=sha256:e1739496c2f0108013629aa095cc32a8c6363444361960c07493818d0dea2da4 \ - --hash=sha256:e43255a83835a129ef98f75d13d643844d8c646b258bebd11e4a0975203e018f \ - --hash=sha256:e626cefff8491bce356221c22af5a3ea528b0b41fbabc719c00ae233819ea0bf \ - --hash=sha256:eadad75bf91897f922e0fb3dca1b322a58b1726a953f98c2e5f0606bd8408621 \ - --hash=sha256:f33da6b5d19ad1bb5e7ad38bb8ba5c426d2178928bc2b2c44e8823ea0ecb6ff3 \ - --hash=sha256:f4052a8a4926d4468416fc7d4b2a7b2a3e35f25b39f4061a7e2a3a2748c4fc48 \ - --hash=sha256:f6ca38dd8d988eca8f07305125dec6f54ac1c518f1aaddcc14d08c01aebb6efc - # via matplotlib -cycler==0.11.0 \ - --hash=sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3 \ - --hash=sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f - # via matplotlib -decorator==5.1.1 \ - --hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \ - --hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 - # via validators -diffusers==0.7.2 \ - --hash=sha256:4a5f8b3a5fbd936bba7d459611cb35ec62875030367be32b232f9e19543e25a9 \ - --hash=sha256:fb814ffd150cc6f470380b8c6a521181a77beb2f44134d2aad2e4cd8aa2ced0e - # via -r installer/requirements.in -dnspython==2.2.1 \ - --hash=sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e \ - --hash=sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f - # via eventlet -docker-pycreds==0.4.0 \ - --hash=sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4 \ - --hash=sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49 - # via wandb -einops==0.5.0 \ - --hash=sha256:055de7eeb3cb9e9710ef3085a811090c6b52e809b7044e8785824ed185f486d1 \ - --hash=sha256:8b7a83cffc1ea88e306df099b7cbb9c3ba5003bd84d05ae44be5655864abb8d3 - # via k-diffusion -entrypoints==0.4 \ - --hash=sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4 \ - --hash=sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f - # via altair -eventlet==0.33.1 \ - --hash=sha256:a085922698e5029f820cf311a648ac324d73cec0e4792877609d978a4b5bbf31 \ - --hash=sha256:afbe17f06a58491e9aebd7a4a03e70b0b63fd4cf76d8307bae07f280479b1515 - # via -r installer/requirements.in -facexlib==0.2.5 \ - --hash=sha256:31e20cc4ed5d63562d380e4564bae14ac0d5d1899a079bad87621e13564567e4 \ - --hash=sha256:cc7ceb56c5424319c47223cf75eef6828c34c66082707c6eb35b95d39779f02d - # via - # gfpgan - # realesrgan -filelock==3.8.0 \ - --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ - --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 - # via - # diffusers - # huggingface-hub - # transformers -filterpy==1.4.5 \ - --hash=sha256:4f2a4d39e4ea601b9ab42b2db08b5918a9538c168cff1c6895ae26646f3d73b1 - # via facexlib -flask==2.2.2 \ - --hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \ - --hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526 - # via - # flask-cors - # flask-socketio -flask-cors==3.0.10 \ - --hash=sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438 \ - --hash=sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de - # via -r installer/requirements.in -flask-socketio==5.3.1 \ - --hash=sha256:fd0ed0fc1341671d92d5f5b2f5503916deb7aa7e2940e6636cfa2c087c828bf9 \ - --hash=sha256:ff0c721f20bff1e2cfba77948727a8db48f187e89a72fe50c34478ce6efb3353 - # via -r installer/requirements.in -flaskwebgui==0.3.7 \ - --hash=sha256:4a69955308eaa8bb256ba04a994dc8f58a48dcd6f9599694ab1bcd9f43d88a5d \ - --hash=sha256:535974ce2672dcc74787c254de24cceed4101be75d96952dae82014dd57f061e - # via -r installer/requirements.in -fonttools==4.38.0 \ - --hash=sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1 \ - --hash=sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb - # via matplotlib -frozenlist==1.3.1 \ - --hash=sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e \ - --hash=sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04 \ - --hash=sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944 \ - --hash=sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845 \ - --hash=sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f \ - --hash=sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f \ - --hash=sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb \ - --hash=sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2 \ - --hash=sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3 \ - --hash=sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f \ - --hash=sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a \ - --hash=sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131 \ - --hash=sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1 \ - --hash=sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa \ - --hash=sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8 \ - --hash=sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b \ - --hash=sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2 \ - --hash=sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e \ - --hash=sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b \ - --hash=sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b \ - --hash=sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10 \ - --hash=sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170 \ - --hash=sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8 \ - --hash=sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b \ - --hash=sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989 \ - --hash=sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd \ - --hash=sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03 \ - --hash=sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519 \ - --hash=sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189 \ - --hash=sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b \ - --hash=sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca \ - --hash=sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff \ - --hash=sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96 \ - --hash=sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d \ - --hash=sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5 \ - --hash=sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2 \ - --hash=sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204 \ - --hash=sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a \ - --hash=sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8 \ - --hash=sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141 \ - --hash=sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792 \ - --hash=sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9 \ - --hash=sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c \ - --hash=sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c \ - --hash=sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221 \ - --hash=sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d \ - --hash=sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc \ - --hash=sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f \ - --hash=sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9 \ - --hash=sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef \ - --hash=sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3 \ - --hash=sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab \ - --hash=sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b \ - --hash=sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db \ - --hash=sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988 \ - --hash=sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6 \ - --hash=sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc \ - --hash=sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83 \ - --hash=sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be - # via - # aiohttp - # aiosignal -fsspec[http]==2022.10.0 \ - --hash=sha256:6b7c6ab3b476cdf17efcfeccde7fca28ef5a48f73a71010aaceec5fc15bf9ebf \ - --hash=sha256:cb6092474e90487a51de768170f3afa50ca8982c26150a59072b16433879ff1d - # via pytorch-lightning -ftfy==6.1.1 \ - --hash=sha256:0ffd33fce16b54cccaec78d6ec73d95ad370e5df5a25255c8966a6147bd667ca \ - --hash=sha256:bfc2019f84fcd851419152320a6375604a0f1459c281b5b199b2cd0d2e727f8f - # via clip -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d - # via - # basicsr - # test-tube -getpass-asterisk==1.0.1 \ - --hash=sha256:20d45cafda0066d761961e0919728526baf7bb5151fbf48a7d5ea4034127d857 \ - --hash=sha256:7cc357a924cf62fa4e15b73cb4e5e30685c9084e464ffdc3fd9000a2b54ea9e9 - # via -r installer/requirements.in -gfpgan @ https://github.com/TencentARC/GFPGAN/archive/2eac2033893ca7f427f4035d80fe95b92649ac56.zip \ - --hash=sha256:79e6d71c8f1df7c7ccb0ac6b9a2ccb615ad5cde818c8b6f285a8711c05aebf85 - # via - # -r installer/requirements.in - # realesrgan -gitdb==4.0.9 \ - --hash=sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd \ - --hash=sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa - # via gitpython -gitpython==3.1.29 \ - --hash=sha256:41eea0deec2deea139b459ac03656f0dd28fc4a3387240ec1d3c259a2c47850f \ - --hash=sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd - # via - # streamlit - # wandb -google-auth==2.14.0 \ - --hash=sha256:1ad5b0e6eba5f69645971abb3d2c197537d5914070a8c6d30299dfdb07c5c700 \ - --hash=sha256:cf24817855d874ede2efd071aa22125445f555de1685b739a9782fcf408c2a3d - # via - # google-auth-oauthlib - # tb-nightly - # tensorboard -google-auth-oauthlib==0.4.6 \ - --hash=sha256:3f2a6e802eebbb6fb736a370fbf3b055edcb6b52878bf2f26330b5e041316c73 \ - --hash=sha256:a90a072f6993f2c327067bf65270046384cda5a8ecb20b94ea9a687f1f233a7a - # via - # tb-nightly - # tensorboard -greenlet==2.0.0.post0 \ - --hash=sha256:00ebdaf0fa51c284fd2172837d751731a15971e0c20d1a9163cfbdf620ce8b49 \ - --hash=sha256:029ca674b3a7e8427db8f5c65d5ed4e24a7417af2a415a5958598aefd71980c4 \ - --hash=sha256:02bdb1e373b275bd705c43b249426e776c4f8a8ff2afaf8ec5ea0dde487d8a14 \ - --hash=sha256:08dc04f49ed1ea5e6772bb5e8cf2a77d1b1744566f4eca471a55b35af1278b31 \ - --hash=sha256:08f44e938d142271b954405afb6570e0be48a9f556b6bf4d42d2e3ae6a251fad \ - --hash=sha256:0a5c03e2a68ec2ff1cba74ceaed899ec8cd353285f4f985c30c8cfbef9d3a3be \ - --hash=sha256:0fee3240093b745efc857392f09379514ad84db4ca324514594bbdf6380016c8 \ - --hash=sha256:118e708dd7bc88beaeeaa5a8601a7743b8835b7bbaf7c8f23ffa78f8bc8faf28 \ - --hash=sha256:13d492a807a5c7334b5931e9b6d9b181991ccc6a40555a7b177f189feff59b4b \ - --hash=sha256:1cac9e9895aeff26434325404558783ee54f4ff3aec8daa56b8706796f7b01a0 \ - --hash=sha256:2146d15429b4eeb412428737594acb5660a5bc0fdd1488d8a2a74a5ee32391fa \ - --hash=sha256:21ee1ae26d072b195edea764218623f6c15eba4ae06816908f33c82e0af018d3 \ - --hash=sha256:22eca421e3f2f3c18f4f54c0ff525aa9d397c6f116fce9ebd37b420174dbc027 \ - --hash=sha256:2bab49783858cf724fff6868395cbeb81d1188cba23616b53e79de0beda29f42 \ - --hash=sha256:2fbdec204ca40b3d0c0992a19c1ba627441c17983ac4ffc45baec7f5f53e20ca \ - --hash=sha256:30ce47525f9a1515566429ac7de6b1ae76d32c3ccede256e3517a1a6419cf659 \ - --hash=sha256:335dcf676d5e4122e4006c16ae11eda2467af5461b949c265ce120b6b959ffe2 \ - --hash=sha256:3407b843b05da71fef0f1dd666059c08ad0e0f4afc3b9c93c998a2e53fac95e5 \ - --hash=sha256:35827f98fd0d768862b8f15777e6dbb03fe6ac6e7bd1bee3f3ded4536f350347 \ - --hash=sha256:3a22e5988f9d66b3e9ae9583bf9d8ef792b09f23afeb78707e6a4f47ab57cc5e \ - --hash=sha256:3c3327da2bdab61078e42e695307465c425671a5a9251e6c29ee130d51943f28 \ - --hash=sha256:3ca723dfc2789c1fb991809822811896b198ecf0909dbccea4a07170d18c3e1b \ - --hash=sha256:46156ae88ee71c37b6c4f7af63fff5d3ab8f45ef72e1a660bcf6386c1647f106 \ - --hash=sha256:4bbe2d074292e3646704371eb640ee52c386d633ed72ff223dadcd3fe8ecd8f9 \ - --hash=sha256:4c4310f0e42154995d92810f27b44ab7116a4a696feb0ff141ae2de59196efd7 \ - --hash=sha256:4cfa629de5b2dea27c81b334c4536463e9a49ac0877e2008a276d58d4c72868a \ - --hash=sha256:4e144ab0de56b4d2a2cf0d2fb9d568b59fce49aab3e129badf17c12b0252047d \ - --hash=sha256:4ea67f303cec384b148774667c7e3cf02311e7026fc02bdcdcd206dfe4ea4fc9 \ - --hash=sha256:538c9e8f65a32413ace426f8117ef019021adf8175f7c491fed65f5fe2083e0c \ - --hash=sha256:56565ac9ab4ff3dd473bfe959e0bf2a5062aabb89b7c94cabb417beb162c9fff \ - --hash=sha256:5e22485256bb1c60bbcc6f8509b1a11042358a2462d5ecdb9a82dc472d2fdd60 \ - --hash=sha256:602a69c24f1a9755dd1760b3b31bdfc495c4613260c876a01b7e6d5eb9bcae1b \ - --hash=sha256:6393ec3cecda53b20241e88bc33d87cbd8126cc10870fc69fa16ca2e20a5ac1b \ - --hash=sha256:6442bbfb047dc1e47658954b72e1589f2bc4e12e67d51bbad0059a626180daa1 \ - --hash=sha256:666d2a0b269a68cd4fe0976544ab97970c5334d35d0e47ae9be1723f734d8204 \ - --hash=sha256:697cfbfc19815c40213badcfe5f076418e0f9100cd25a66f513f32c1026b8bf4 \ - --hash=sha256:6a1a6745c5dce202aa3f29a1736c53cf2179e9c3b280dc62cea9cb8c69977c83 \ - --hash=sha256:6fc73fc8dd81d9efa842a55033b6b4cb233b134a0270e127c6874d053ef2049b \ - --hash=sha256:7e9e0d4c5c618b0442396715ffe6c2f84a60d593dad7e0184388aed36d568a65 \ - --hash=sha256:81fdcf7c0c2df46a99ca421a552c4370117851c5e4dbd6cb53d569b896d62322 \ - --hash=sha256:8b26932be686f3582df039d79fe96f7ca13d63b39468162f816f9ff29584b9a4 \ - --hash=sha256:8b7e5191b974fb66fcbac1818ba494d3512da9cf6eaef7acd952f9862eaaa20c \ - --hash=sha256:8c80e9c41a83d8c90399af8c7dcdeae0c03c48b40b9d0ab84457533d5d7882bf \ - --hash=sha256:9f2f110b9cc325f6543e0e3f4ab8008c272a59052f9464047c29d4be4511ce05 \ - --hash=sha256:a339e510a079dc8372e39ce1c7629414db51966235c9670c58d529def79243a2 \ - --hash=sha256:ad9abc3e4d2370cecb524421cc5c8a664006aa11d5c1cb3c9250e3bf65ab546e \ - --hash=sha256:b043782c8f6cccc8fae3a16db397eca1d36a41b0706cbf6f514aea1e1a260bab \ - --hash=sha256:b31de27313abbb567c528ed123380fcf18a5dfd03134570dfd12227e21ac1184 \ - --hash=sha256:b75e5644cc353328cd57ec8dafaaf5f81b2c3ecf7c4b278b907e99ad53ba7839 \ - --hash=sha256:b8cfc8fc944bd7b704691bc28225a2635e377e92dc413459845868d3f7724982 \ - --hash=sha256:c2055c52260808d87622293b57df1c68aeb12ddd8a0cfc0223fb57a5f629e202 \ - --hash=sha256:c416106b3b8e905b6ab0e84ec90047a6401021aa023f9aa93978e57cd8f8189f \ - --hash=sha256:d0e210e17a6181a3fd3f8dce957043a4e74177ffa9f295514984b2b633940dce \ - --hash=sha256:d9453135e48cd631e3e9f06d9da9100d17c9f662e4a6d8b552c29be6c834a6b9 \ - --hash=sha256:dd0198006278291d9469309d655093df1f5e5107c0261e242b5f390baee32199 \ - --hash=sha256:e1781bda1e787d3ad33788cc3be47f6e47a9581676d02670c15ee36c9460adfe \ - --hash=sha256:e56a5a9f303e3ac011ba445a6d84f05d08666bf8db094afafcec5228622c30f5 \ - --hash=sha256:e93ae35f0fd3caf75e58c76a1cab71e6ece169aaa1b281782ef9efde0a6b83f2 \ - --hash=sha256:eb36b6570646227a63eda03916f1cc6f3744ee96d28f7a0a5629c59267a8055f \ - --hash=sha256:f8c425a130e04d5404edaf6f5906e5ab12f3aa1168a1828aba6dfadac5910469 - # via eventlet -grpcio==1.50.0 \ - --hash=sha256:05f7c248e440f538aaad13eee78ef35f0541e73498dd6f832fe284542ac4b298 \ - --hash=sha256:080b66253f29e1646ac53ef288c12944b131a2829488ac3bac8f52abb4413c0d \ - --hash=sha256:12b479839a5e753580b5e6053571de14006157f2ef9b71f38c56dc9b23b95ad6 \ - --hash=sha256:156f8009e36780fab48c979c5605eda646065d4695deea4cfcbcfdd06627ddb6 \ - --hash=sha256:15f9e6d7f564e8f0776770e6ef32dac172c6f9960c478616c366862933fa08b4 \ - --hash=sha256:177afaa7dba3ab5bfc211a71b90da1b887d441df33732e94e26860b3321434d9 \ - --hash=sha256:1a4cd8cb09d1bc70b3ea37802be484c5ae5a576108bad14728f2516279165dd7 \ - --hash=sha256:1d8d02dbb616c0a9260ce587eb751c9c7dc689bc39efa6a88cc4fa3e9c138a7b \ - --hash=sha256:2b71916fa8f9eb2abd93151fafe12e18cebb302686b924bd4ec39266211da525 \ - --hash=sha256:2d9fd6e38b16c4d286a01e1776fdf6c7a4123d99ae8d6b3f0b4a03a34bf6ce45 \ - --hash=sha256:3b611b3de3dfd2c47549ca01abfa9bbb95937eb0ea546ea1d762a335739887be \ - --hash=sha256:3e4244c09cc1b65c286d709658c061f12c61c814be0b7030a2d9966ff02611e0 \ - --hash=sha256:40838061e24f960b853d7bce85086c8e1b81c6342b1f4c47ff0edd44bbae2722 \ - --hash=sha256:4b123fbb7a777a2fedec684ca0b723d85e1d2379b6032a9a9b7851829ed3ca9a \ - --hash=sha256:531f8b46f3d3db91d9ef285191825d108090856b3bc86a75b7c3930f16ce432f \ - --hash=sha256:67dd41a31f6fc5c7db097a5c14a3fa588af54736ffc174af4411d34c4f306f68 \ - --hash=sha256:7489dbb901f4fdf7aec8d3753eadd40839c9085967737606d2c35b43074eea24 \ - --hash=sha256:8d4c8e73bf20fb53fe5a7318e768b9734cf122fe671fcce75654b98ba12dfb75 \ - --hash=sha256:8e69aa4e9b7f065f01d3fdcecbe0397895a772d99954bb82eefbb1682d274518 \ - --hash=sha256:8e8999a097ad89b30d584c034929f7c0be280cd7851ac23e9067111167dcbf55 \ - --hash=sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974 \ - --hash=sha256:92d7635d1059d40d2ec29c8bf5ec58900120b3ce5150ef7414119430a4b2dd5c \ - --hash=sha256:931e746d0f75b2a5cff0a1197d21827a3a2f400c06bace036762110f19d3d507 \ - --hash=sha256:95ce51f7a09491fb3da8cf3935005bff19983b77c4e9437ef77235d787b06842 \ - --hash=sha256:9eea18a878cffc804506d39c6682d71f6b42ec1c151d21865a95fae743fda500 \ - --hash=sha256:a23d47f2fc7111869f0ff547f771733661ff2818562b04b9ed674fa208e261f4 \ - --hash=sha256:a4c23e54f58e016761b576976da6a34d876420b993f45f66a2bfb00363ecc1f9 \ - --hash=sha256:a50a1be449b9e238b9bd43d3857d40edf65df9416dea988929891d92a9f8a778 \ - --hash=sha256:ab5d0e3590f0a16cb88de4a3fa78d10eb66a84ca80901eb2c17c1d2c308c230f \ - --hash=sha256:ae23daa7eda93c1c49a9ecc316e027ceb99adbad750fbd3a56fa9e4a2ffd5ae0 \ - --hash=sha256:af98d49e56605a2912cf330b4627e5286243242706c3a9fa0bcec6e6f68646fc \ - --hash=sha256:b2f77a90ba7b85bfb31329f8eab9d9540da2cf8a302128fb1241d7ea239a5469 \ - --hash=sha256:baab51dcc4f2aecabf4ed1e2f57bceab240987c8b03533f1cef90890e6502067 \ - --hash=sha256:ca8a2254ab88482936ce941485c1c20cdeaef0efa71a61dbad171ab6758ec998 \ - --hash=sha256:cb11464f480e6103c59d558a3875bd84eed6723f0921290325ebe97262ae1347 \ - --hash=sha256:ce8513aee0af9c159319692bfbf488b718d1793d764798c3d5cff827a09e25ef \ - --hash=sha256:cf151f97f5f381163912e8952eb5b3afe89dec9ed723d1561d59cabf1e219a35 \ - --hash=sha256:d144ad10eeca4c1d1ce930faa105899f86f5d99cecfe0d7224f3c4c76265c15e \ - --hash=sha256:d534d169673dd5e6e12fb57cc67664c2641361e1a0885545495e65a7b761b0f4 \ - --hash=sha256:d75061367a69808ab2e84c960e9dce54749bcc1e44ad3f85deee3a6c75b4ede9 \ - --hash=sha256:d84d04dec64cc4ed726d07c5d17b73c343c8ddcd6b59c7199c801d6bbb9d9ed1 \ - --hash=sha256:de411d2b030134b642c092e986d21aefb9d26a28bf5a18c47dd08ded411a3bc5 \ - --hash=sha256:e07fe0d7ae395897981d16be61f0db9791f482f03fee7d1851fe20ddb4f69c03 \ - --hash=sha256:ea8ccf95e4c7e20419b7827aa5b6da6f02720270686ac63bd3493a651830235c \ - --hash=sha256:f7025930039a011ed7d7e7ef95a1cb5f516e23c5a6ecc7947259b67bea8e06ca - # via - # tb-nightly - # tensorboard -huggingface-hub==0.10.1 \ - --hash=sha256:5c188d5b16bec4b78449f8681f9975ff9d321c16046cc29bcf0d7e464ff29276 \ - --hash=sha256:dc3b0e9a663fe6cad6a8522055c02a9d8673dbd527223288e2442bc028c253db - # via - # diffusers - # transformers -idna==2.10 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 - # via - # requests - # yarl -imageio==2.22.3 \ - --hash=sha256:63f007b7f2a082306e36922b3fd529a7aa305d2b78f46195bab8e22bbfe866e9 \ - --hash=sha256:a4b88f9f3d428b8c0ceeb7e297df8c346a642bb7e3111743eb85717d60b26f6f - # via - # scikit-image - # test-tube -imageio-ffmpeg==0.4.7 \ - --hash=sha256:27b48c32becae1658aa81c3a6b922538e4099edf5fbcbdb4ff5dbc84b8ffd3d3 \ - --hash=sha256:6514f1380daf42815bc8c83aad63f33e0b8b47133421ddafe7b410cd8dfbbea5 \ - --hash=sha256:6aba52ddf0a64442ffcb8d30ac6afb668186acec99ecbc7ae5bd171c4f500bbc \ - --hash=sha256:7a08838f97f363e37ca41821b864fd3fdc99ab1fe2421040c78eb5f56a9e723e \ - --hash=sha256:8e724d12dfe83e2a6eb39619e820243ca96c81c47c2648e66e05f7ee24e14312 \ - --hash=sha256:fc60686ef03c2d0f842901b206223c30051a6a120384458761390104470846fd - # via -r installer/requirements.in -importlib-metadata==5.0.0 \ - --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ - --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 - # via - # diffusers - # streamlit -itsdangerous==2.1.2 \ - --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ - --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a - # via flask -jinja2==3.1.2 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - # via - # altair - # flask - # pydeck -joblib==1.2.0 \ - --hash=sha256:091138ed78f800342968c523bdde947e7a305b8594b910a0fea2ab83c3c6d385 \ - --hash=sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018 - # via scikit-learn -jsonmerge==1.9.0 \ - --hash=sha256:a2d1f80021c5c1d70a49e31f862b5f068f9db066080d8561e80654de74a3584d - # via k-diffusion -jsonschema==4.17.0 \ - --hash=sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d \ - --hash=sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248 - # via - # altair - # jsonmerge -k-diffusion @ https://github.com/Birch-san/k-diffusion/archive/363386981fee88620709cf8f6f2eea167bd6cd74.zip \ - --hash=sha256:8eac5cdc08736e6d61908a1b2948f2b2f62691b01dc1aab978bddb3451af0d66 - # via -r installer/requirements.in -kiwisolver==1.4.4 \ - --hash=sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b \ - --hash=sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166 \ - --hash=sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c \ - --hash=sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c \ - --hash=sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0 \ - --hash=sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4 \ - --hash=sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9 \ - --hash=sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286 \ - --hash=sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767 \ - --hash=sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c \ - --hash=sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6 \ - --hash=sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b \ - --hash=sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004 \ - --hash=sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf \ - --hash=sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494 \ - --hash=sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac \ - --hash=sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626 \ - --hash=sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766 \ - --hash=sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514 \ - --hash=sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6 \ - --hash=sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f \ - --hash=sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d \ - --hash=sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191 \ - --hash=sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d \ - --hash=sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51 \ - --hash=sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f \ - --hash=sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8 \ - --hash=sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454 \ - --hash=sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb \ - --hash=sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da \ - --hash=sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8 \ - --hash=sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de \ - --hash=sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a \ - --hash=sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9 \ - --hash=sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008 \ - --hash=sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3 \ - --hash=sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32 \ - --hash=sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938 \ - --hash=sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1 \ - --hash=sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9 \ - --hash=sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d \ - --hash=sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824 \ - --hash=sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b \ - --hash=sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd \ - --hash=sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2 \ - --hash=sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5 \ - --hash=sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69 \ - --hash=sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3 \ - --hash=sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae \ - --hash=sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597 \ - --hash=sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e \ - --hash=sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955 \ - --hash=sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca \ - --hash=sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a \ - --hash=sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea \ - --hash=sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede \ - --hash=sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4 \ - --hash=sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6 \ - --hash=sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686 \ - --hash=sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408 \ - --hash=sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871 \ - --hash=sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29 \ - --hash=sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750 \ - --hash=sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897 \ - --hash=sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0 \ - --hash=sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2 \ - --hash=sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09 \ - --hash=sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c - # via matplotlib -kornia==0.6.8 \ - --hash=sha256:0985e02453c0ab4f030e8d22a3a7554dab312ffa8f8a54ec872190e6f0b58c56 \ - --hash=sha256:0d6d69330b4fd24da742337b8134da0ce01b4d7da66770db5498d58e8b4a0832 - # via k-diffusion -llvmlite==0.39.1 \ - --hash=sha256:03aee0ccd81735696474dc4f8b6be60774892a2929d6c05d093d17392c237f32 \ - --hash=sha256:1578f5000fdce513712e99543c50e93758a954297575610f48cb1fd71b27c08a \ - --hash=sha256:16f56eb1eec3cda3a5c526bc3f63594fc24e0c8d219375afeb336f289764c6c7 \ - --hash=sha256:1ec3d70b3e507515936e475d9811305f52d049281eaa6c8273448a61c9b5b7e2 \ - --hash=sha256:22d36591cd5d02038912321d9ab8e4668e53ae2211da5523f454e992b5e13c36 \ - --hash=sha256:3803f11ad5f6f6c3d2b545a303d68d9fabb1d50e06a8d6418e6fcd2d0df00959 \ - --hash=sha256:39dc2160aed36e989610fc403487f11b8764b6650017ff367e45384dff88ffbf \ - --hash=sha256:3fc14e757bc07a919221f0cbaacb512704ce5774d7fcada793f1996d6bc75f2a \ - --hash=sha256:4c6ebace910410daf0bebda09c1859504fc2f33d122e9a971c4c349c89cca630 \ - --hash=sha256:50aea09a2b933dab7c9df92361b1844ad3145bfb8dd2deb9cd8b8917d59306fb \ - --hash=sha256:60f8dd1e76f47b3dbdee4b38d9189f3e020d22a173c00f930b52131001d801f9 \ - --hash=sha256:62c0ea22e0b9dffb020601bb65cb11dd967a095a488be73f07d8867f4e327ca5 \ - --hash=sha256:6546bed4e02a1c3d53a22a0bced254b3b6894693318b16c16c8e43e29d6befb6 \ - --hash=sha256:6717c7a6e93c9d2c3d07c07113ec80ae24af45cde536b34363d4bcd9188091d9 \ - --hash=sha256:7ebf1eb9badc2a397d4f6a6c8717447c81ac011db00064a00408bc83c923c0e4 \ - --hash=sha256:9ffc84ade195abd4abcf0bd3b827b9140ae9ef90999429b9ea84d5df69c9058c \ - --hash=sha256:a3f331a323d0f0ada6b10d60182ef06c20a2f01be21699999d204c5750ffd0b4 \ - --hash=sha256:b1a0bbdb274fb683f993198775b957d29a6f07b45d184c571ef2a721ce4388cf \ - --hash=sha256:b43abd7c82e805261c425d50335be9a6c4f84264e34d6d6e475207300005d572 \ - --hash=sha256:c0f158e4708dda6367d21cf15afc58de4ebce979c7a1aa2f6b977aae737e2a54 \ - --hash=sha256:d0bfd18c324549c0fec2c5dc610fd024689de6f27c6cc67e4e24a07541d6e49b \ - --hash=sha256:ddab526c5a2c4ccb8c9ec4821fcea7606933dc53f510e2a6eebb45a418d3488a \ - --hash=sha256:e172c73fccf7d6db4bd6f7de963dedded900d1a5c6778733241d878ba613980e \ - --hash=sha256:e2c00ff204afa721b0bb9835b5bf1ba7fba210eefcec5552a9e05a63219ba0dc \ - --hash=sha256:e31f4b799d530255aaf0566e3da2df5bfc35d3cd9d6d5a3dcc251663656c27b1 \ - --hash=sha256:e4f212c018db951da3e1dc25c2651abc688221934739721f2dad5ff1dd5f90e7 \ - --hash=sha256:fa9b26939ae553bf30a9f5c4c754db0fb2d2677327f2511e674aa2f5df941789 \ - --hash=sha256:fb62fc7016b592435d3e3a8f680e3ea8897c3c9e62e6e6cc58011e7a4801439e - # via numba -lmdb==1.3.0 \ - --hash=sha256:008243762decf8f6c90430a9bced56290ebbcdb5e877d90e42343bb97033e494 \ - --hash=sha256:08f4b5129f4683802569b02581142e415c8dcc0ff07605983ec1b07804cecbad \ - --hash=sha256:17215a42a4b9814c383deabecb160581e4fb75d00198eef0e3cea54f230ffbea \ - --hash=sha256:18c69fabdaf04efaf246587739cc1062b3e57c6ef0743f5c418df89e5e7e7b9b \ - --hash=sha256:2cfa4aa9c67f8aee89b23005e98d1f3f32490b6b905fd1cb604b207cbd5755ab \ - --hash=sha256:2df38115dd9428a54d59ae7c712a4c7cce0d6b1d66056de4b1a8c38718066106 \ - --hash=sha256:394df860c3f93cfd92b6f4caba785f38208cc9614c18b3803f83a2cc1695042f \ - --hash=sha256:41318717ab5d15ad2d6d263d34fbf614a045210f64b25e59ce734bb2105e421f \ - --hash=sha256:4172fba19417d7b29409beca7d73c067b54e5d8ab1fb9b51d7b4c1445d20a167 \ - --hash=sha256:5a14aca2651c3af6f0d0a6b9168200eea0c8f2d27c40b01a442f33329a6e8dff \ - --hash=sha256:5ddd590e1c7fcb395931aa3782fb89b9db4550ab2d81d006ecd239e0d462bc41 \ - --hash=sha256:60a11efc21aaf009d06518996360eed346f6000bfc9de05114374230879f992e \ - --hash=sha256:6260a526e4ad85b1f374a5ba9475bf369fb07e7728ea6ec57226b02c40d1976b \ - --hash=sha256:62ab28e3593bdc318ea2f2fa1574e5fca3b6d1f264686d773ba54a637d4f563b \ - --hash=sha256:63cb73fe7ce9eb93d992d632c85a0476b4332670d9e6a2802b5062f603b7809f \ - --hash=sha256:65334eafa5d430b18d81ebd5362559a41483c362e1931f6e1b15bab2ecb7d75d \ - --hash=sha256:7da05d70fcc6561ac6b09e9fb1bf64b7ca294652c64c8a2889273970cee796b9 \ - --hash=sha256:abbc439cd9fe60ffd6197009087ea885ac150017dc85384093b1d376f83f0ec4 \ - --hash=sha256:c6adbd6f7f9048e97f31a069e652eb51020a81e80a0ce92dbb9810d21da2409a \ - --hash=sha256:d6a816954d212f40fd15007cd81ab7a6bebb77436d949a6a9ae04af57fc127f3 \ - --hash=sha256:d9103aa4908f0bca43c5911ca067d4e3d01f682dff0c0381a1239bd2bd757984 \ - --hash=sha256:df2724bad7820114a205472994091097d0fa65a3e5fff5a8e688d123fb8c6326 \ - --hash=sha256:e568ae0887ae196340947d9800136e90feaed6b86a261ef01f01b2ba65fc8106 \ - --hash=sha256:e6a704b3baced9182836c7f77b769f23856f3a8f62d0282b1bc1feaf81a86712 \ - --hash=sha256:eefb392f6b5cd43aada49258c5a79be11cb2c8cd3fc3e2d9319a1e0b9f906458 \ - --hash=sha256:f291e3f561f58dddf63a92a5a6a4b8af3a0920b6705d35e2f80e52e86ee238a2 \ - --hash=sha256:fa6439356e591d3249ab0e1778a6f8d8408e993f66dc911914c78208f5310309 - # via - # basicsr - # gfpgan -markdown==3.4.1 \ - --hash=sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186 \ - --hash=sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff - # via - # tb-nightly - # tensorboard -markupsafe==2.1.1 \ - --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ - --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ - --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ - --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ - --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ - --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ - --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ - --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ - --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ - --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ - --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ - --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ - --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ - --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ - --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ - --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ - --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ - --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ - --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ - --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ - --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ - --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ - --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ - --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ - --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ - --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ - --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ - --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ - --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ - --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ - --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ - --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ - --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ - --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ - --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ - --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ - --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ - --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ - --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ - --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 - # via - # jinja2 - # werkzeug -matplotlib==3.6.2 \ - --hash=sha256:0844523dfaaff566e39dbfa74e6f6dc42e92f7a365ce80929c5030b84caa563a \ - --hash=sha256:0eda9d1b43f265da91fb9ae10d6922b5a986e2234470a524e6b18f14095b20d2 \ - --hash=sha256:168093410b99f647ba61361b208f7b0d64dde1172b5b1796d765cd243cadb501 \ - --hash=sha256:1836f366272b1557a613f8265db220eb8dd883202bbbabe01bad5a4eadfd0c95 \ - --hash=sha256:19d61ee6414c44a04addbe33005ab1f87539d9f395e25afcbe9a3c50ce77c65c \ - --hash=sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267 \ - --hash=sha256:32d29c8c26362169c80c5718ce367e8c64f4dd068a424e7110df1dd2ed7bd428 \ - --hash=sha256:380d48c15ec41102a2b70858ab1dedfa33eb77b2c0982cb65a200ae67a48e9cb \ - --hash=sha256:3964934731fd7a289a91d315919cf757f293969a4244941ab10513d2351b4e83 \ - --hash=sha256:3cef89888a466228fc4e4b2954e740ce8e9afde7c4315fdd18caa1b8de58ca17 \ - --hash=sha256:4426c74761790bff46e3d906c14c7aab727543293eed5a924300a952e1a3a3c1 \ - --hash=sha256:5024b8ed83d7f8809982d095d8ab0b179bebc07616a9713f86d30cf4944acb73 \ - --hash=sha256:52c2bdd7cd0bf9d5ccdf9c1816568fd4ccd51a4d82419cc5480f548981b47dd0 \ - --hash=sha256:54fa9fe27f5466b86126ff38123261188bed568c1019e4716af01f97a12fe812 \ - --hash=sha256:5ba73aa3aca35d2981e0b31230d58abb7b5d7ca104e543ae49709208d8ce706a \ - --hash=sha256:5e16dcaecffd55b955aa5e2b8a804379789c15987e8ebd2f32f01398a81e975b \ - --hash=sha256:5ecfc6559132116dedfc482d0ad9df8a89dc5909eebffd22f3deb684132d002f \ - --hash=sha256:74153008bd24366cf099d1f1e83808d179d618c4e32edb0d489d526523a94d9f \ - --hash=sha256:78ec3c3412cf277e6252764ee4acbdbec6920cc87ad65862272aaa0e24381eee \ - --hash=sha256:795ad83940732b45d39b82571f87af0081c120feff2b12e748d96bb191169e33 \ - --hash=sha256:7f716b6af94dc1b6b97c46401774472f0867e44595990fe80a8ba390f7a0a028 \ - --hash=sha256:83dc89c5fd728fdb03b76f122f43b4dcee8c61f1489e232d9ad0f58020523e1c \ - --hash=sha256:8a0ae37576ed444fe853709bdceb2be4c7df6f7acae17b8378765bd28e61b3ae \ - --hash=sha256:8a8dbe2cb7f33ff54b16bb5c500673502a35f18ac1ed48625e997d40c922f9cc \ - --hash=sha256:8a9d899953c722b9afd7e88dbefd8fb276c686c3116a43c577cfabf636180558 \ - --hash=sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5 \ - --hash=sha256:9347cc6822f38db2b1d1ce992f375289670e595a2d1c15961aacbe0977407dfc \ - --hash=sha256:9f335e5625feb90e323d7e3868ec337f7b9ad88b5d633f876e3b778813021dab \ - --hash=sha256:b03fd10a1709d0101c054883b550f7c4c5e974f751e2680318759af005964990 \ - --hash=sha256:b0ca2c60d3966dfd6608f5f8c49b8a0fcf76de6654f2eda55fc6ef038d5a6f27 \ - --hash=sha256:b2604c6450f9dd2c42e223b1f5dca9643a23cfecc9fde4a94bb38e0d2693b136 \ - --hash=sha256:ca0e7a658fbafcddcaefaa07ba8dae9384be2343468a8e011061791588d839fa \ - --hash=sha256:d0e9ac04065a814d4cf2c6791a2ad563f739ae3ae830d716d54245c2b96fead6 \ - --hash=sha256:d50e8c1e571ee39b5dfbc295c11ad65988879f68009dd281a6e1edbc2ff6c18c \ - --hash=sha256:d840adcad7354be6f2ec28d0706528b0026e4c3934cc6566b84eac18633eab1b \ - --hash=sha256:e0bbee6c2a5bf2a0017a9b5e397babb88f230e6f07c3cdff4a4c4bc75ed7c617 \ - --hash=sha256:e5afe0a7ea0e3a7a257907060bee6724a6002b7eec55d0db16fd32409795f3e1 \ - --hash=sha256:e68be81cd8c22b029924b6d0ee814c337c0e706b8d88495a617319e5dd5441c3 \ - --hash=sha256:ec9be0f4826cdb3a3a517509dcc5f87f370251b76362051ab59e42b6b765f8c4 \ - --hash=sha256:f04f97797df35e442ed09f529ad1235d1f1c0f30878e2fe09a2676b71a8801e0 \ - --hash=sha256:f41e57ad63d336fe50d3a67bb8eaa26c09f6dda6a59f76777a99b8ccd8e26aec - # via - # clipseg - # filterpy -multidict==6.0.2 \ - --hash=sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60 \ - --hash=sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c \ - --hash=sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672 \ - --hash=sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51 \ - --hash=sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032 \ - --hash=sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2 \ - --hash=sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b \ - --hash=sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80 \ - --hash=sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88 \ - --hash=sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a \ - --hash=sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d \ - --hash=sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389 \ - --hash=sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c \ - --hash=sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9 \ - --hash=sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c \ - --hash=sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516 \ - --hash=sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b \ - --hash=sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43 \ - --hash=sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee \ - --hash=sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227 \ - --hash=sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d \ - --hash=sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae \ - --hash=sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7 \ - --hash=sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4 \ - --hash=sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9 \ - --hash=sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f \ - --hash=sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013 \ - --hash=sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9 \ - --hash=sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e \ - --hash=sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693 \ - --hash=sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a \ - --hash=sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15 \ - --hash=sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb \ - --hash=sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96 \ - --hash=sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87 \ - --hash=sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376 \ - --hash=sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658 \ - --hash=sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0 \ - --hash=sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071 \ - --hash=sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360 \ - --hash=sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc \ - --hash=sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3 \ - --hash=sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba \ - --hash=sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8 \ - --hash=sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9 \ - --hash=sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2 \ - --hash=sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3 \ - --hash=sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68 \ - --hash=sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8 \ - --hash=sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d \ - --hash=sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49 \ - --hash=sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608 \ - --hash=sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57 \ - --hash=sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86 \ - --hash=sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20 \ - --hash=sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293 \ - --hash=sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849 \ - --hash=sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937 \ - --hash=sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d - # via - # aiohttp - # yarl -networkx==2.8.8 \ - --hash=sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e \ - --hash=sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524 - # via scikit-image -numba==0.56.4 \ - --hash=sha256:0240f9026b015e336069329839208ebd70ec34ae5bfbf402e4fcc8e06197528e \ - --hash=sha256:03634579d10a6129181129de293dd6b5eaabee86881369d24d63f8fe352dd6cb \ - --hash=sha256:03fe94cd31e96185cce2fae005334a8cc712fc2ba7756e52dff8c9400718173f \ - --hash=sha256:0611e6d3eebe4cb903f1a836ffdb2bda8d18482bcd0a0dcc56e79e2aa3fefef5 \ - --hash=sha256:0da583c532cd72feefd8e551435747e0e0fbb3c0530357e6845fcc11e38d6aea \ - --hash=sha256:14dbbabf6ffcd96ee2ac827389afa59a70ffa9f089576500434c34abf9b054a4 \ - --hash=sha256:32d9fef412c81483d7efe0ceb6cf4d3310fde8b624a9cecca00f790573ac96ee \ - --hash=sha256:3a993349b90569518739009d8f4b523dfedd7e0049e6838c0e17435c3e70dcc4 \ - --hash=sha256:3cb1a07a082a61df80a468f232e452d818f5ae254b40c26390054e4e868556e0 \ - --hash=sha256:42f9e1be942b215df7e6cc9948cf9c15bb8170acc8286c063a9e57994ef82fd1 \ - --hash=sha256:4373da9757049db7c90591e9ec55a2e97b2b36ba7ae3bf9c956a513374077470 \ - --hash=sha256:4e08e203b163ace08bad500b0c16f6092b1eb34fd1fce4feaf31a67a3a5ecf3b \ - --hash=sha256:553da2ce74e8862e18a72a209ed3b6d2924403bdd0fb341fa891c6455545ba7c \ - --hash=sha256:720886b852a2d62619ae3900fe71f1852c62db4f287d0c275a60219e1643fc04 \ - --hash=sha256:85dbaed7a05ff96492b69a8900c5ba605551afb9b27774f7f10511095451137c \ - --hash=sha256:8a95ca9cc77ea4571081f6594e08bd272b66060634b8324e99cd1843020364f9 \ - --hash=sha256:91f021145a8081f881996818474ef737800bcc613ffb1e618a655725a0f9e246 \ - --hash=sha256:9f62672145f8669ec08762895fe85f4cf0ead08ce3164667f2b94b2f62ab23c3 \ - --hash=sha256:a12ef323c0f2101529d455cfde7f4135eaa147bad17afe10b48634f796d96abd \ - --hash=sha256:c602d015478b7958408d788ba00a50272649c5186ea8baa6cf71d4a1c761bba1 \ - --hash=sha256:c75e8a5f810ce80a0cfad6e74ee94f9fde9b40c81312949bf356b7304ef20740 \ - --hash=sha256:d0ae9270a7a5cc0ede63cd234b4ff1ce166c7a749b91dbbf45e0000c56d3eade \ - --hash=sha256:d69ad934e13c15684e7887100a8f5f0f61d7a8e57e0fd29d9993210089a5b531 \ - --hash=sha256:dbcc847bac2d225265d054993a7f910fda66e73d6662fe7156452cac0325b073 \ - --hash=sha256:e64d338b504c9394a4a34942df4627e1e6cb07396ee3b49fe7b8d6420aa5104f \ - --hash=sha256:f4cfc3a19d1e26448032049c79fc60331b104f694cf570a9e94f4e2c9d0932bb \ - --hash=sha256:fbfb45e7b297749029cb28694abf437a78695a100e7c2033983d69f0ba2698d4 \ - --hash=sha256:fcdf84ba3ed8124eb7234adfbb8792f311991cbf8aed1cad4b1b1a7ee08380c1 - # via facexlib -numpy==1.23.4 \ - --hash=sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8 \ - --hash=sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735 \ - --hash=sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd \ - --hash=sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810 \ - --hash=sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db \ - --hash=sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962 \ - --hash=sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79 \ - --hash=sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911 \ - --hash=sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d \ - --hash=sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488 \ - --hash=sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5 \ - --hash=sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0 \ - --hash=sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f \ - --hash=sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f \ - --hash=sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2 \ - --hash=sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0 \ - --hash=sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68 \ - --hash=sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3 \ - --hash=sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6 \ - --hash=sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71 \ - --hash=sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894 \ - --hash=sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f \ - --hash=sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329 \ - --hash=sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba \ - --hash=sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c \ - --hash=sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e \ - --hash=sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef \ - --hash=sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7 - # via - # accelerate - # albumentations - # altair - # basicsr - # clean-fid - # clipseg - # contourpy - # diffusers - # facexlib - # filterpy - # gfpgan - # imageio - # matplotlib - # numba - # opencv-python - # opencv-python-headless - # pandas - # pyarrow - # pydeck - # pypatchmatch - # pytorch-lightning - # pywavelets - # qudida - # realesrgan - # scikit-image - # scikit-learn - # scipy - # streamlit - # taming-transformers-rom1504 - # tb-nightly - # tensorboard - # test-tube - # tifffile - # torch-fidelity - # torchmetrics - # torchsde - # torchvision - # transformers -oauthlib==3.2.2 \ - --hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \ - --hash=sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918 - # via requests-oauthlib -omegaconf==2.2.3 \ - --hash=sha256:59ff9fba864ffbb5fb710b64e8a9ba37c68fa339a2e2bb4f1b648d6901552523 \ - --hash=sha256:d6f2cbf79a992899eb76c6cb1aedfcf0fe7456a8654382edd5ee0c1b199c0657 - # via taming-transformers-rom1504 -opencv-python==4.6.0.66 \ - --hash=sha256:0dc82a3d8630c099d2f3ac1b1aabee164e8188db54a786abb7a4e27eba309440 \ - --hash=sha256:5af8ba35a4fcb8913ffb86e92403e9a656a4bff4a645d196987468f0f8947875 \ - --hash=sha256:6e32af22e3202748bd233ed8f538741876191863882eba44e332d1a34993165b \ - --hash=sha256:c5bfae41ad4031e66bb10ec4a0a2ffd3e514d092652781e8b1ac98d1b59f1158 \ - --hash=sha256:dbdc84a9b4ea2cbae33861652d25093944b9959279200b7ae0badd32439f74de \ - --hash=sha256:e6e448b62afc95c5b58f97e87ef84699e6607fe5c58730a03301c52496005cae \ - --hash=sha256:f482e78de6e7b0b060ff994ffd859bddc3f7f382bb2019ef157b0ea8ca8712f5 - # via - # basicsr - # clipseg - # facexlib - # gfpgan - # realesrgan -opencv-python-headless==4.6.0.66 \ - --hash=sha256:21e70f8b0c04098cdf466d27184fe6c3820aaef944a22548db95099959c95889 \ - --hash=sha256:2c032c373e447c3fc2a670bca20e2918a1205a6e72854df60425fd3f82c78c32 \ - --hash=sha256:3bacd806cce1f1988e58f3d6f761538e0215d6621d316de94c009dc0acbd6ad3 \ - --hash=sha256:d5291d7e10aa2c19cab6fd86f0d61af8617290ecd2d7ffcb051e446868d04cc5 \ - --hash=sha256:e6c069bc963d7e8fcec21b3e33e594d35948badd63eccb2e80f88b0a12102c03 \ - --hash=sha256:eec6281054346103d6af93f173b7c6a206beb2663d3adc04aa3ddc66e85093df \ - --hash=sha256:ffbf26fcd697af996408440a93bc69c49c05a36845771f984156dfbeaa95d497 - # via - # albumentations - # qudida -packaging==21.3 \ - --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ - --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 - # via - # accelerate - # huggingface-hub - # kornia - # matplotlib - # pytorch-lightning - # scikit-image - # streamlit - # torchmetrics - # transformers -pandas==1.5.1 \ - --hash=sha256:04e51b01d5192499390c0015630975f57836cc95c7411415b499b599b05c0c96 \ - --hash=sha256:05c527c64ee02a47a24031c880ee0ded05af0623163494173204c5b72ddce658 \ - --hash=sha256:0a78e05ec09731c5b3bd7a9805927ea631fe6f6cb06f0e7c63191a9a778d52b4 \ - --hash=sha256:17da7035d9e6f9ea9cdc3a513161f8739b8f8489d31dc932bc5a29a27243f93d \ - --hash=sha256:249cec5f2a5b22096440bd85c33106b6102e0672204abd2d5c014106459804ee \ - --hash=sha256:2c25e5c16ee5c0feb6cf9d982b869eec94a22ddfda9aa2fbed00842cbb697624 \ - --hash=sha256:32e3d9f65606b3f6e76555bfd1d0b68d94aff0929d82010b791b6254bf5a4b96 \ - --hash=sha256:36aa1f8f680d7584e9b572c3203b20d22d697c31b71189322f16811d4ecfecd3 \ - --hash=sha256:5b0c970e2215572197b42f1cff58a908d734503ea54b326412c70d4692256391 \ - --hash=sha256:5cee0c74e93ed4f9d39007e439debcaadc519d7ea5c0afc3d590a3a7b2edf060 \ - --hash=sha256:669c8605dba6c798c1863157aefde959c1796671ffb342b80fcb80a4c0bc4c26 \ - --hash=sha256:66a1ad667b56e679e06ba73bb88c7309b3f48a4c279bd3afea29f65a766e9036 \ - --hash=sha256:683779e5728ac9138406c59a11e09cd98c7d2c12f0a5fc2b9c5eecdbb4a00075 \ - --hash=sha256:6bb391659a747cf4f181a227c3e64b6d197100d53da98dcd766cc158bdd9ec68 \ - --hash=sha256:81f0674fa50b38b6793cd84fae5d67f58f74c2d974d2cb4e476d26eee33343d0 \ - --hash=sha256:927e59c694e039c75d7023465d311277a1fc29ed7236b5746e9dddf180393113 \ - --hash=sha256:932d2d7d3cab44cfa275601c982f30c2d874722ef6396bb539e41e4dc4618ed4 \ - --hash=sha256:a52419d9ba5906db516109660b114faf791136c94c1a636ed6b29cbfff9187ee \ - --hash=sha256:b156a971bc451c68c9e1f97567c94fd44155f073e3bceb1b0d195fd98ed12048 \ - --hash=sha256:bcf1a82b770b8f8c1e495b19a20d8296f875a796c4fe6e91da5ef107f18c5ecb \ - --hash=sha256:cb2a9cf1150302d69bb99861c5cddc9c25aceacb0a4ef5299785d0f5389a3209 \ - --hash=sha256:d8c709f4700573deb2036d240d140934df7e852520f4a584b2a8d5443b71f54d \ - --hash=sha256:db45b94885000981522fb92349e6b76f5aee0924cc5315881239c7859883117d \ - --hash=sha256:ddf46b940ef815af4e542697eaf071f0531449407a7607dd731bf23d156e20a7 \ - --hash=sha256:e675f8fe9aa6c418dc8d3aac0087b5294c1a4527f1eacf9fe5ea671685285454 \ - --hash=sha256:eb7e8cf2cf11a2580088009b43de84cabbf6f5dae94ceb489f28dba01a17cb77 \ - --hash=sha256:f340331a3f411910adfb4bbe46c2ed5872d9e473a783d7f14ecf49bc0869c594 - # via - # altair - # streamlit - # test-tube -pathtools==0.1.2 \ - --hash=sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0 - # via wandb -picklescan==0.0.5 \ - --hash=sha256:368cf1b9a075bc1b6460ad82b694f260532b836c82f99d13846cd36e1bbe7f9a \ - --hash=sha256:57153eca04d5df5009f2cdd595aef261b8a6f27e03046a1c84f672aa6869c592 - # via -r installer/requirements.in -pillow==9.3.0 \ - --hash=sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040 \ - --hash=sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8 \ - --hash=sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65 \ - --hash=sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2 \ - --hash=sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627 \ - --hash=sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07 \ - --hash=sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef \ - --hash=sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535 \ - --hash=sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c \ - --hash=sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc \ - --hash=sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3 \ - --hash=sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1 \ - --hash=sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c \ - --hash=sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa \ - --hash=sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32 \ - --hash=sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502 \ - --hash=sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4 \ - --hash=sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f \ - --hash=sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812 \ - --hash=sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636 \ - --hash=sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20 \ - --hash=sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c \ - --hash=sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91 \ - --hash=sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe \ - --hash=sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b \ - --hash=sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad \ - --hash=sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9 \ - --hash=sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72 \ - --hash=sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4 \ - --hash=sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de \ - --hash=sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29 \ - --hash=sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee \ - --hash=sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c \ - --hash=sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7 \ - --hash=sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11 \ - --hash=sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c \ - --hash=sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c \ - --hash=sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448 \ - --hash=sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b \ - --hash=sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20 \ - --hash=sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228 \ - --hash=sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd \ - --hash=sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699 \ - --hash=sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b \ - --hash=sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2 \ - --hash=sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4 \ - --hash=sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c \ - --hash=sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f \ - --hash=sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2 \ - --hash=sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c \ - --hash=sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3 \ - --hash=sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193 \ - --hash=sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48 \ - --hash=sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02 \ - --hash=sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8 \ - --hash=sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e \ - --hash=sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f \ - --hash=sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b \ - --hash=sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74 \ - --hash=sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb \ - --hash=sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0 - # via - # basicsr - # clean-fid - # diffusers - # facexlib - # imageio - # k-diffusion - # matplotlib - # pypatchmatch - # realesrgan - # scikit-image - # streamlit - # torch-fidelity - # torchvision -promise==2.3 \ - --hash=sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0 - # via wandb -protobuf==3.19.6 \ - --hash=sha256:010be24d5a44be7b0613750ab40bc8b8cedc796db468eae6c779b395f50d1fa1 \ - --hash=sha256:0469bc66160180165e4e29de7f445e57a34ab68f49357392c5b2f54c656ab25e \ - --hash=sha256:0c0714b025ec057b5a7600cb66ce7c693815f897cfda6d6efb58201c472e3437 \ - --hash=sha256:11478547958c2dfea921920617eb457bc26867b0d1aa065ab05f35080c5d9eb6 \ - --hash=sha256:14082457dc02be946f60b15aad35e9f5c69e738f80ebbc0900a19bc83734a5a4 \ - --hash=sha256:2b2d2913bcda0e0ec9a784d194bc490f5dc3d9d71d322d070b11a0ade32ff6ba \ - --hash=sha256:30a15015d86b9c3b8d6bf78d5b8c7749f2512c29f168ca259c9d7727604d0e39 \ - --hash=sha256:30f5370d50295b246eaa0296533403961f7e64b03ea12265d6dfce3a391d8992 \ - --hash=sha256:347b393d4dd06fb93a77620781e11c058b3b0a5289262f094379ada2920a3730 \ - --hash=sha256:4bc98de3cdccfb5cd769620d5785b92c662b6bfad03a202b83799b6ed3fa1fa7 \ - --hash=sha256:5057c64052a1f1dd7d4450e9aac25af6bf36cfbfb3a1cd89d16393a036c49157 \ - --hash=sha256:559670e006e3173308c9254d63facb2c03865818f22204037ab76f7a0ff70b5f \ - --hash=sha256:5a0d7539a1b1fb7e76bf5faa0b44b30f812758e989e59c40f77a7dab320e79b9 \ - --hash=sha256:5f5540d57a43042389e87661c6eaa50f47c19c6176e8cf1c4f287aeefeccb5c4 \ - --hash=sha256:7a552af4dc34793803f4e735aabe97ffc45962dfd3a237bdde242bff5a3de684 \ - --hash=sha256:84a04134866861b11556a82dd91ea6daf1f4925746b992f277b84013a7cc1229 \ - --hash=sha256:878b4cd080a21ddda6ac6d1e163403ec6eea2e206cf225982ae04567d39be7b0 \ - --hash=sha256:90b0d02163c4e67279ddb6dc25e063db0130fc299aefabb5d481053509fae5c8 \ - --hash=sha256:91d5f1e139ff92c37e0ff07f391101df77e55ebb97f46bbc1535298d72019462 \ - --hash=sha256:a8ce5ae0de28b51dff886fb922012dad885e66176663950cb2344c0439ecb473 \ - --hash=sha256:aa3b82ca1f24ab5326dcf4ea00fcbda703e986b22f3d27541654f749564d778b \ - --hash=sha256:bb6776bd18f01ffe9920e78e03a8676530a5d6c5911934c6a1ac6eb78973ecb6 \ - --hash=sha256:bbf5cea5048272e1c60d235c7bd12ce1b14b8a16e76917f371c718bd3005f045 \ - --hash=sha256:c0ccd3f940fe7f3b35a261b1dd1b4fc850c8fde9f74207015431f174be5976b3 \ - --hash=sha256:d0b635cefebd7a8a0f92020562dead912f81f401af7e71f16bf9506ff3bdbb38 - # via - # streamlit - # tb-nightly - # tensorboard - # wandb -psutil==5.9.3 \ - --hash=sha256:07d880053c6461c9b89cd5d4808f3b8336665fa3acdefd6777662c5ed73a851a \ - --hash=sha256:12500d761ac091f2426567f19f95fd3f15a197d96befb44a5c1e3cbe6db5752c \ - --hash=sha256:1b540599481c73408f6b392cdffef5b01e8ff7a2ac8caae0a91b8222e88e8f1e \ - --hash=sha256:35feafe232d1aaf35d51bd42790cbccb882456f9f18cdc411532902370d660df \ - --hash=sha256:3a7826e68b0cf4ce2c1ee385d64eab7d70e3133171376cac53d7c1790357ec8f \ - --hash=sha256:46907fa62acaac364fff0b8a9da7b360265d217e4fdeaca0a2397a6883dffba2 \ - --hash=sha256:4bd4854f0c83aa84a5a40d3b5d0eb1f3c128f4146371e03baed4589fe4f3c931 \ - --hash=sha256:538fcf6ae856b5e12d13d7da25ad67f02113c96f5989e6ad44422cb5994ca7fc \ - --hash=sha256:547ebb02031fdada635452250ff39942db8310b5c4a8102dfe9384ee5791e650 \ - --hash=sha256:5e8b50241dd3c2ed498507f87a6602825073c07f3b7e9560c58411c14fe1e1c9 \ - --hash=sha256:5fa88e3d5d0b480602553d362c4b33a63e0c40bfea7312a7bf78799e01e0810b \ - --hash=sha256:68fa227c32240c52982cb931801c5707a7f96dd8927f9102d6c7771ea1ff5698 \ - --hash=sha256:6ced1ad823ecfa7d3ce26fe8aa4996e2e53fb49b7fed8ad81c80958501ec0619 \ - --hash=sha256:71b1206e7909792d16933a0d2c1c7f04ae196186c51ba8567abae1d041f06dcb \ - --hash=sha256:767ef4fa33acda16703725c0473a91e1832d296c37c63896c7153ba81698f1ab \ - --hash=sha256:7ccfcdfea4fc4b0a02ca2c31de7fcd186beb9cff8207800e14ab66f79c773af6 \ - --hash=sha256:7e4939ff75149b67aef77980409f156f0082fa36accc475d45c705bb00c6c16a \ - --hash=sha256:828c9dc9478b34ab96be75c81942d8df0c2bb49edbb481f597314d92b6441d89 \ - --hash=sha256:8a4e07611997acf178ad13b842377e3d8e9d0a5bac43ece9bfc22a96735d9a4f \ - --hash=sha256:941a6c2c591da455d760121b44097781bc970be40e0e43081b9139da485ad5b7 \ - --hash=sha256:9a4af6ed1094f867834f5f07acd1250605a0874169a5fcadbcec864aec2496a6 \ - --hash=sha256:9ec296f565191f89c48f33d9544d8d82b0d2af7dd7d2d4e6319f27a818f8d1cc \ - --hash=sha256:9ec95df684583b5596c82bb380c53a603bb051cf019d5c849c47e117c5064395 \ - --hash=sha256:a04a1836894c8279e5e0a0127c0db8e198ca133d28be8a2a72b4db16f6cf99c1 \ - --hash=sha256:a3d81165b8474087bb90ec4f333a638ccfd1d69d34a9b4a1a7eaac06648f9fbe \ - --hash=sha256:b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71 \ - --hash=sha256:ba38cf9984d5462b506e239cf4bc24e84ead4b1d71a3be35e66dad0d13ded7c1 \ - --hash=sha256:beb57d8a1ca0ae0eb3d08ccaceb77e1a6d93606f0e1754f0d60a6ebd5c288837 \ - --hash=sha256:d266cd05bd4a95ca1c2b9b5aac50d249cf7c94a542f47e0b22928ddf8b80d1ef \ - --hash=sha256:d8c3cc6bb76492133474e130a12351a325336c01c96a24aae731abf5a47fe088 \ - --hash=sha256:db8e62016add2235cc87fb7ea000ede9e4ca0aa1f221b40cef049d02d5d2593d \ - --hash=sha256:e7507f6c7b0262d3e7b0eeda15045bf5881f4ada70473b87bc7b7c93b992a7d7 \ - --hash=sha256:ed15edb14f52925869250b1375f0ff58ca5c4fa8adefe4883cfb0737d32f5c02 \ - --hash=sha256:f57d63a2b5beaf797b87024d018772439f9d3103a395627b77d17a8d72009543 \ - --hash=sha256:fa5e32c7d9b60b2528108ade2929b115167fe98d59f89555574715054f50fa31 \ - --hash=sha256:fe79b4ad4836e3da6c4650cb85a663b3a51aef22e1a829c384e18fae87e5e727 - # via - # accelerate - # wandb -pyarrow==10.0.0 \ - --hash=sha256:10e031794d019425d34406edffe7e32157359e9455f9edb97a1732f8dabf802f \ - --hash=sha256:25f51dca780fc22cfd7ac30f6bdfe70eb99145aee9acfda987f2c49955d66ed9 \ - --hash=sha256:2d326a9d47ac237d81b8c4337e9d30a0b361835b536fc7ea53991455ce761fbd \ - --hash=sha256:3d2694f08c8d4482d14e3798ff036dbd81ae6b1c47948f52515e1aa90fbec3f0 \ - --hash=sha256:4051664d354b14939b5da35cfa77821ade594bc1cf56dd2032b3068c96697d74 \ - --hash=sha256:511735040b83f2993f78d7fb615e7b88253d75f41500e87e587c40156ff88120 \ - --hash=sha256:65d4a312f3ced318423704355acaccc7f7bdfe242472e59bdd54aa0f8837adf8 \ - --hash=sha256:68ccb82c04c0f7abf7a95541d5e9d9d94290fc66a2d36d3f6ea0777f40c15654 \ - --hash=sha256:69b8a1fd99201178799b02f18498633847109b701856ec762f314352a431b7d0 \ - --hash=sha256:758284e1ebd3f2a9abb30544bfec28d151a398bb7c0f2578cbca5ee5b000364a \ - --hash=sha256:7be7f42f713068293308c989a4a3a2de03b70199bdbe753901c6595ff8640c64 \ - --hash=sha256:7ce026274cd5d9934cd3694e89edecde4e036018bbc6cb735fd33b9e967e7d47 \ - --hash=sha256:7e6b837cc44cd62a0e280c8fc4de94ebce503d6d1190e6e94157ab49a8bea67b \ - --hash=sha256:b153b05765393557716e3729cf988442b3ae4f5567364ded40d58c07feed27c2 \ - --hash=sha256:b3e3148468d3eed3779d68241f1d13ed4ee7cca4c6dbc7c07e5062b93ad4da33 \ - --hash=sha256:b45f969ed924282e9d4ede38f3430630d809c36dbff65452cabce03141943d28 \ - --hash=sha256:b9f63ceb8346aac0bcb487fafe9faca642ad448ca649fcf66a027c6e120cbc12 \ - --hash=sha256:c79300e1a3e23f2bf4defcf0d70ff5ea25ef6ebf6f121d8670ee14bb662bb7ca \ - --hash=sha256:d45a59e2f47826544c0ca70bc0f7ed8ffa5ad23f93b0458230c7e983bcad1acf \ - --hash=sha256:e4c6da9f9e1ff96781ee1478f7cc0860e66c23584887b8e297c4b9905c3c9066 \ - --hash=sha256:f329951d56b3b943c353f7b27c894e02367a7efbb9fef7979c6b24e02dbfcf55 \ - --hash=sha256:f76157d9579571c865860e5fd004537c03e21139db76692d96fd8a186adab1f2 - # via streamlit -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 - # via google-auth -pydeck==0.8.0 \ - --hash=sha256:07edde833f7cfcef6749124351195aa7dcd24663d4909fd7898dbd0b6fbc01ec \ - --hash=sha256:a8fa7757c6f24bba033af39db3147cb020eef44012ba7e60d954de187f9ed4d5 - # via streamlit -pydeprecate==0.3.2 \ - --hash=sha256:d481116cc5d7f6c473e7c4be820efdd9b90a16b594b350276e9e66a6cb5bdd29 \ - --hash=sha256:ed86b68ed837e6465245904a3de2f59bf9eef78ac7a2502ee280533d04802457 - # via pytorch-lightning -pygments==2.13.0 \ - --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ - --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 - # via rich -pympler==1.0.1 \ - --hash=sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa \ - --hash=sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d - # via streamlit -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc - # via - # matplotlib - # packaging -pypatchmatch @ https://github.com/invoke-ai/PyPatchMatch/archive/129863937a8ab37f6bbcec327c994c0f932abdbc.zip \ - --hash=sha256:4ad6ec95379e7d122d494ff76633cc7cf9b71330d5efda147fceba81e3dc6cd2 - # via -r installer/requirements.in -pyreadline3==3.4.1 \ - --hash=sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae \ - --hash=sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb - # via -r installer/requirements.in -pyrsistent==0.19.2 \ - --hash=sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed \ - --hash=sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb \ - --hash=sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a \ - --hash=sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95 \ - --hash=sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712 \ - --hash=sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73 \ - --hash=sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41 \ - --hash=sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b \ - --hash=sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78 \ - --hash=sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab \ - --hash=sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308 \ - --hash=sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425 \ - --hash=sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2 \ - --hash=sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e \ - --hash=sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6 \ - --hash=sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2 \ - --hash=sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a \ - --hash=sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291 \ - --hash=sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584 \ - --hash=sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a \ - --hash=sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0 \ - --hash=sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770 - # via jsonschema -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 - # via - # matplotlib - # pandas - # streamlit -python-engineio==4.3.4 \ - --hash=sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c \ - --hash=sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae - # via python-socketio -python-socketio==5.7.2 \ - --hash=sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097 \ - --hash=sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3 - # via flask-socketio -pytorch-lightning==1.7.7 \ - --hash=sha256:27c2dd01a18db2415168e3fa3775ccb5a1fa1e2961a50439ad9365507fe9d4ae \ - --hash=sha256:4438b8284d7f7fdb06cf3566a7b5b6f102ac8971cf7bb6d3f1b1de64628241f3 - # via taming-transformers-rom1504 -pytz==2022.6 \ - --hash=sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427 \ - --hash=sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2 - # via pandas -pytz-deprecation-shim==0.1.0.post0 \ - --hash=sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6 \ - --hash=sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d - # via tzlocal -pywavelets==1.4.1 \ - --hash=sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b \ - --hash=sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4 \ - --hash=sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4 \ - --hash=sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6 \ - --hash=sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de \ - --hash=sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c \ - --hash=sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa \ - --hash=sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93 \ - --hash=sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875 \ - --hash=sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd \ - --hash=sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356 \ - --hash=sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1 \ - --hash=sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c \ - --hash=sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2 \ - --hash=sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784 \ - --hash=sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4 \ - --hash=sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b \ - --hash=sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc \ - --hash=sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966 \ - --hash=sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e \ - --hash=sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426 \ - --hash=sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c \ - --hash=sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202 \ - --hash=sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc \ - --hash=sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd - # via scikit-image -pyyaml==6.0 \ - --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \ - --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \ - --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \ - --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \ - --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \ - --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \ - --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \ - --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \ - --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \ - --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \ - --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \ - --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \ - --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \ - --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \ - --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \ - --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \ - --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \ - --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \ - --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \ - --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \ - --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \ - --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \ - --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \ - --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \ - --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \ - --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \ - --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \ - --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \ - --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \ - --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \ - --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \ - --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \ - --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \ - --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \ - --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \ - --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \ - --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \ - --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \ - --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \ - --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5 - # via - # accelerate - # albumentations - # basicsr - # gfpgan - # huggingface-hub - # omegaconf - # pytorch-lightning - # transformers - # wandb -qudida==0.0.4 \ - --hash=sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6 \ - --hash=sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8 - # via albumentations -realesrgan==0.3.0 \ - --hash=sha256:0d36da96ab9f447071606e91f502ccdfb08f80cc82ee4f8caf720c7745ccec7e \ - --hash=sha256:59336c16c30dd5130eff350dd27424acb9b7281d18a6810130e265606c9a6088 - # via -r installer/requirements.in -regex==2022.10.31 \ - --hash=sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad \ - --hash=sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4 \ - --hash=sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd \ - --hash=sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc \ - --hash=sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d \ - --hash=sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066 \ - --hash=sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec \ - --hash=sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9 \ - --hash=sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e \ - --hash=sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8 \ - --hash=sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e \ - --hash=sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783 \ - --hash=sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6 \ - --hash=sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1 \ - --hash=sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c \ - --hash=sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4 \ - --hash=sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1 \ - --hash=sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1 \ - --hash=sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7 \ - --hash=sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8 \ - --hash=sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe \ - --hash=sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d \ - --hash=sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b \ - --hash=sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8 \ - --hash=sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c \ - --hash=sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af \ - --hash=sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49 \ - --hash=sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714 \ - --hash=sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542 \ - --hash=sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318 \ - --hash=sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e \ - --hash=sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5 \ - --hash=sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc \ - --hash=sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144 \ - --hash=sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453 \ - --hash=sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5 \ - --hash=sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61 \ - --hash=sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11 \ - --hash=sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a \ - --hash=sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54 \ - --hash=sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73 \ - --hash=sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc \ - --hash=sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347 \ - --hash=sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c \ - --hash=sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66 \ - --hash=sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c \ - --hash=sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93 \ - --hash=sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443 \ - --hash=sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc \ - --hash=sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1 \ - --hash=sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892 \ - --hash=sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8 \ - --hash=sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001 \ - --hash=sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa \ - --hash=sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90 \ - --hash=sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c \ - --hash=sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0 \ - --hash=sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692 \ - --hash=sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4 \ - --hash=sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5 \ - --hash=sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690 \ - --hash=sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83 \ - --hash=sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66 \ - --hash=sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f \ - --hash=sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f \ - --hash=sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4 \ - --hash=sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee \ - --hash=sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81 \ - --hash=sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95 \ - --hash=sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9 \ - --hash=sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff \ - --hash=sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e \ - --hash=sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5 \ - --hash=sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6 \ - --hash=sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7 \ - --hash=sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1 \ - --hash=sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394 \ - --hash=sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6 \ - --hash=sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742 \ - --hash=sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57 \ - --hash=sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b \ - --hash=sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7 \ - --hash=sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b \ - --hash=sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244 \ - --hash=sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af \ - --hash=sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185 \ - --hash=sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8 \ - --hash=sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5 - # via - # clip - # diffusers - # transformers -requests==2.25.1 \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e - # via - # basicsr - # clean-fid - # diffusers - # fsspec - # huggingface-hub - # requests-oauthlib - # streamlit - # tb-nightly - # tensorboard - # torchvision - # transformers - # wandb -requests-oauthlib==1.3.1 \ - --hash=sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5 \ - --hash=sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a - # via google-auth-oauthlib -resize-right==0.0.2 \ - --hash=sha256:78351ca3eda0872208fcbc90861b45de559f90fb4027ce41825fdeb9b995005c \ - --hash=sha256:7dc35b72ce4012b77f7cc9049835163793ab98a58ab8893610fb119fe59af520 - # via k-diffusion -rich==12.6.0 \ - --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ - --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 - # via streamlit -rsa==4.9 \ - --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ - --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 - # via google-auth -scikit-image==0.19.3 \ - --hash=sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099 \ - --hash=sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d \ - --hash=sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790 \ - --hash=sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450 \ - --hash=sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef \ - --hash=sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245 \ - --hash=sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919 \ - --hash=sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6 \ - --hash=sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8 \ - --hash=sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d \ - --hash=sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19 \ - --hash=sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2 \ - --hash=sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92 \ - --hash=sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced \ - --hash=sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e \ - --hash=sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732 \ - --hash=sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34 \ - --hash=sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83 \ - --hash=sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f \ - --hash=sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9 \ - --hash=sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe \ - --hash=sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7 \ - --hash=sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b \ - --hash=sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6 \ - --hash=sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc \ - --hash=sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5 - # via - # albumentations - # basicsr - # k-diffusion -scikit-learn==1.1.3 \ - --hash=sha256:23fb9e74b813cc2528b5167d82ed08950b11106ccf50297161875e45152fb311 \ - --hash=sha256:250da993701da88bf475e7c5746abf1285ea0ae47e4d0917cd13afd6600bb162 \ - --hash=sha256:28b2bd6a1419acd522ff45d282c8ba23dbccb5338802ab0ee12baa4ade0aba4c \ - --hash=sha256:2ee2c649f2231b68511aabb0dc827edd8936aad682acc6263c34aed11bc95dac \ - --hash=sha256:30e27721adc308e8fd9f419f43068e43490005f911edf4476a9e585059fa8a83 \ - --hash=sha256:38814f66285318f2e241305cca545eaa9b4126c65aa5dd78c69371f235f78e2b \ - --hash=sha256:4d3a19166d4e1cdfcab975c68f471e046ce01e74c42a9a33fa89a14c2fcedf60 \ - --hash=sha256:5699cded6c0685426433c7e5afe0fecad80ec831ec7fa264940e50c796775cc5 \ - --hash=sha256:6785b8a3093329bf90ac01801be5525551728ae73edb11baa175df660820add4 \ - --hash=sha256:6d1c1394e38a3319ace620381f6f23cc807d8780e9915c152449a86fc8f1db21 \ - --hash=sha256:701181792a28c82fecae12adb5d15d0ecf57bffab7cf4bdbb52c7b3fd428d540 \ - --hash=sha256:748f2bd632d6993e8918d43f1a26c380aeda4e122a88840d4c3a9af99d4239fe \ - --hash=sha256:8e9dd76c7274055d1acf4526b8efb16a3531c26dcda714a0c16da99bf9d41900 \ - --hash=sha256:bef51978a51ec19977700fe7b86aecea49c825884f3811756b74a3b152bb4e35 \ - --hash=sha256:cd55c6fbef7608dbce1f22baf289dfcc6eb323247daa3c3542f73d389c724786 \ - --hash=sha256:da5a2e95fef9805b1750e4abda4e834bf8835d26fc709a391543b53feee7bd0e \ - --hash=sha256:ee47f68d973cee7009f06edb956f2f5588a0f230f24a2a70175fd0ecf36e2653 \ - --hash=sha256:f4931f2a6c06e02c6c17a05f8ae397e2545965bc7a0a6cb38c8cd7d4fba8624d \ - --hash=sha256:f5644663987ee221f5d1f47a593271b966c271c236fe05634e6bdc06041b5a2b \ - --hash=sha256:f5d4231af7199531e77da1b78a4cc6b3d960a00b1ec672578ac818aae2b9c35d \ - --hash=sha256:fd3ee69d36d42a7dcbb17e355a5653af5fd241a7dfd9133080b3dde8d9e2aafb - # via qudida -scipy==1.9.3 \ - --hash=sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31 \ - --hash=sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108 \ - --hash=sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0 \ - --hash=sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b \ - --hash=sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e \ - --hash=sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e \ - --hash=sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5 \ - --hash=sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840 \ - --hash=sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58 \ - --hash=sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523 \ - --hash=sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd \ - --hash=sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab \ - --hash=sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c \ - --hash=sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb \ - --hash=sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096 \ - --hash=sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0 \ - --hash=sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc \ - --hash=sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9 \ - --hash=sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c \ - --hash=sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95 \ - --hash=sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027 - # via - # albumentations - # basicsr - # clean-fid - # clipseg - # facexlib - # filterpy - # gfpgan - # k-diffusion - # scikit-image - # scikit-learn - # torch-fidelity - # torchdiffeq - # torchsde -semver==2.13.0 \ - --hash=sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4 \ - --hash=sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f - # via streamlit -send2trash==1.8.0 \ - --hash=sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d \ - --hash=sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08 - # via -r installer/requirements.in -sentry-sdk==1.10.1 \ - --hash=sha256:06c0fa9ccfdc80d7e3b5d2021978d6eb9351fa49db9b5847cf4d1f2a473414ad \ - --hash=sha256:105faf7bd7b7fa25653404619ee261527266b14103fe1389e0ce077bd23a9691 - # via wandb -setproctitle==1.3.2 \ - --hash=sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b \ - --hash=sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9 \ - --hash=sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31 \ - --hash=sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83 \ - --hash=sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f \ - --hash=sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca \ - --hash=sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494 \ - --hash=sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe \ - --hash=sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172 \ - --hash=sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084 \ - --hash=sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4 \ - --hash=sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1 \ - --hash=sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c \ - --hash=sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973 \ - --hash=sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c \ - --hash=sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202 \ - --hash=sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5 \ - --hash=sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65 \ - --hash=sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18 \ - --hash=sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13 \ - --hash=sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005 \ - --hash=sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02 \ - --hash=sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7 \ - --hash=sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665 \ - --hash=sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f \ - --hash=sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1 \ - --hash=sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989 \ - --hash=sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827 \ - --hash=sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42 \ - --hash=sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927 \ - --hash=sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122 \ - --hash=sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9 \ - --hash=sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f \ - --hash=sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d \ - --hash=sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98 \ - --hash=sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796 \ - --hash=sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb \ - --hash=sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd \ - --hash=sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe \ - --hash=sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806 \ - --hash=sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484 \ - --hash=sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e \ - --hash=sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575 \ - --hash=sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76 \ - --hash=sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246 \ - --hash=sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb \ - --hash=sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099 \ - --hash=sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c \ - --hash=sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258 \ - --hash=sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865 \ - --hash=sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b \ - --hash=sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d \ - --hash=sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10 \ - --hash=sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f \ - --hash=sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94 \ - --hash=sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6 \ - --hash=sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963 \ - --hash=sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4 \ - --hash=sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8 \ - --hash=sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761 - # via wandb -shortuuid==1.0.9 \ - --hash=sha256:459f12fa1acc34ff213b1371467c0325169645a31ed989e268872339af7563d5 \ - --hash=sha256:b2bb9eb7773170e253bb7ba25971023acb473517a8b76803d9618668cb1dd46f - # via wandb -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # docker-pycreds - # eventlet - # flask-cors - # google-auth - # grpcio - # promise - # python-dateutil - # validators - # wandb -smmap==5.0.0 \ - --hash=sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94 \ - --hash=sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936 - # via gitdb -streamlit==1.14.0 \ - --hash=sha256:62556d873567e1b3427bcd118a57ee6946619f363bd6bba38df2d1f8225ecba0 \ - --hash=sha256:e078b8143d150ba721bdb9194218e311c5fe1d6d4156473a2dea6cc848a6c9fc - # via -r installer/requirements.in -taming-transformers-rom1504==0.0.6 \ - --hash=sha256:051b5804c58caa247bcd51d17ddb525b4d5f892a29d42dc460f40e3e9e34e5d8 \ - --hash=sha256:73fe5fc1108accee4236ee6976e0987ab236afad0af06cb9f037641a908d2c32 - # via -r installer/requirements.in -tb-nightly==2.11.0a20221106 \ - --hash=sha256:8940457ee42db92f01da8bcdbbea1a476735eda559dde5976f5728919960af4a - # via - # basicsr - # gfpgan -tensorboard==2.10.1 \ - --hash=sha256:fb9222c1750e2fa35ef170d998a1e229f626eeced3004494a8849c88c15d8c1c - # via - # pytorch-lightning - # test-tube -tensorboard-data-server==0.6.1 \ - --hash=sha256:809fe9887682d35c1f7d1f54f0f40f98bb1f771b14265b453ca051e2ce58fca7 \ - --hash=sha256:d8237580755e58eff68d1f3abefb5b1e39ae5c8b127cc40920f9c4fb33f4b98a \ - --hash=sha256:fa8cef9be4fcae2f2363c88176638baf2da19c5ec90addb49b1cde05c95c88ee - # via - # tb-nightly - # tensorboard -tensorboard-plugin-wit==1.8.1 \ - --hash=sha256:ff26bdd583d155aa951ee3b152b3d0cffae8005dc697f72b44a8e8c2a77a8cbe - # via - # tb-nightly - # tensorboard -test-tube==0.7.5 \ - --hash=sha256:1379c33eb8cde3e9b36610f87da0f16c2e06496b1cfebac473df4e7be2faa124 - # via -r installer/requirements.in -threadpoolctl==3.1.0 \ - --hash=sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b \ - --hash=sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380 - # via scikit-learn -tifffile==2022.10.10 \ - --hash=sha256:50b61ba943b866d191295bc38a00191c9fdab23ece063544c7f1a264e3f6aa8e \ - --hash=sha256:87f3aee8a0d06b74655269a105de75c1958a24653e1930d523eb516100043503 - # via scikit-image -tokenizers==0.13.1 \ - --hash=sha256:0a3412830ad66a643723d6b0fc3202e64e9e299bd9c9eda04b2914b5b1e0ddb0 \ - --hash=sha256:126bcb18a77cf65961ece04f54bd10ef3713412195e543d9d3eda2f0e4fd677c \ - --hash=sha256:16434c61c5eb72f6692b9bc52052b07ca92d3eba9dd72a1bc371890e1bdc3f07 \ - --hash=sha256:1d4acfdb6e7ef974677bb8445462db7fed240185fdc0f5b061db357d4ef8d85d \ - --hash=sha256:3333d1cee5c8f47c96362ea0abc1f81c77c9b92c6c3d11cbf1d01985f0d5cf1d \ - --hash=sha256:3acf3cae4c4739fc9ec49fa0e6cce224c1aa0fabc8f8d1358fd7de5c7d49cdca \ - --hash=sha256:3ba43b3f6f6b41c97c1d041785342cd72ba142999f6c4605d628e8e937398f20 \ - --hash=sha256:3c69a8389fd88bc32115e99db70f63bef577ba5c54f40a632580038a49612856 \ - --hash=sha256:3de653a551cc616a442a123da21706cb3a3163cf6919973f978f0921eee1bdf0 \ - --hash=sha256:4b3be8af87b357340b9b049d28067287b5e5e296e3120b6e4875d3b8469b67e6 \ - --hash=sha256:680bc0e357b7da6d0d01634bffbd002e866fdaccde303e1d1af58f32464cf308 \ - --hash=sha256:70de69681a264a5808d39f4bb6331be9a4dec51fd48cd1b959a94da76c4939cc \ - --hash=sha256:73198cda6e1d991c583ed798514863e16763aa600eb7aa6df7722373290575b2 \ - --hash=sha256:80864f456f715829f901ad5bb85af97e9ae52fc902270944804f6476ab8c6699 \ - --hash=sha256:80b9552295fdce0a2513dcb795a3f8591eca1a8dcf8afe0de3214209e6924ad1 \ - --hash=sha256:84fa41b58a8d3b7363ecdf3397d4b38f345fcf7d4dd14565b4360e7bffc9cae0 \ - --hash=sha256:890d2139100b5c8ac6d585438d5e145ede1d7b32b4090a6c078db6db0ef1daea \ - --hash=sha256:8b3f97041f7716998e474d3c7ffd19ac6941f117616696aef2b5ba927bf091e3 \ - --hash=sha256:910479e92d5fbdf91e8106b4c658fd43d418893d7cfd5fb11983c54a1ff53869 \ - --hash=sha256:96a1beef1e64d44597627f4e29d794047a66ad4d7474d93daf5a0ee27928e012 \ - --hash=sha256:98bef54cf51ac335fda1408112df7ff3e584107633bd9066616033e12b0bd519 \ - --hash=sha256:afcb1bd6d9ed59d5c8e633765589cab12f98aae09804f156b5965b4463b8b8e3 \ - --hash=sha256:b72dec85488af3e1e8d58fb4b86b5dbe5171c176002b5e098ea6d52a70004bb5 \ - --hash=sha256:c3109ba62bea56c68c7c2a976250b040afee61b5f86fc791f17afaa2a09fce94 \ - --hash=sha256:c73b9e6c107e980e65077b89c54311d8d645f6a9efdde82990777fa43c0a8cae \ - --hash=sha256:d8fca8b492a4697b0182e0c40b164cb0c44a9669d9c98033fec2f88174605eb0 \ - --hash=sha256:db6872294339bf35c158219fc65bad939ba87d14c936ae7a33a3ca2d1532c5b1 \ - --hash=sha256:e1a90bc97f53600f52e902f3ae097424de712d8ae0e42d957efc7ed908573a20 \ - --hash=sha256:f75f476fe183c03c515a0f0f5d195cb05d93fcdc76e31fe3c9753d01f3ee990b \ - --hash=sha256:fd17b14f84bec0b171869abd17ca0d9bfc564aa1e7f3451f44da526949a911c1 \ - --hash=sha256:fea71780b66f8c278ebae7221c8959404cf7343b8d2f4b7308aa668cf6f02364 - # via transformers -toml==0.10.2 \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f - # via streamlit -toolz==0.12.0 \ - --hash=sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f \ - --hash=sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194 - # via altair -torch==1.12.0+cu116 ; platform_system == "Linux" or platform_system == "Windows" \ - --hash=sha256:1d9557d1e871794a31a71c40dec8589d6c3347f3f2953a8dd74cfd58e1ecb52e \ - --hash=sha256:72538e4505087668a4642f861578dfed470fae5da20b1758b0f34e4a070d6b21 \ - --hash=sha256:74f5b137190a6face6859d630f129289e7fae6a4d9a747430b3b5d5c6297a3ae \ - --hash=sha256:7665e906995328746c6f70016ee90cafe50cbf434b6ef576e1de2678929ee63e \ - --hash=sha256:7ee1899e9afe5f5e35ba46bc70e17735d2c02cedede1fa69a288cc680b5ab3db \ - --hash=sha256:97d63afcb6358071737f8325aa933e9db2f30cd2f068591d27d4ea72f3cabad2 \ - --hash=sha256:aa43d7b54b86f723f17c5c44df1078c59a6149fc4d42fbef08aafab9d61451c9 \ - --hash=sha256:f772be831447dd01ebd26cbedf619e668d1b269d69bf6b4ff46b1378362bff26 - # via - # -r installer/requirements.in - # accelerate - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # kornia - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # test-tube - # torch-fidelity - # torchdiffeq - # torchmetrics - # torchsde - # torchvision -torch-fidelity==0.3.0 \ - --hash=sha256:3d3e33db98919759cc4f3f24cb27e1e74bdc7c905d90a780630e4e1c18492b66 \ - --hash=sha256:d01284825595feb7dc3eae3dc9a0d8ced02be764813a3483f109bc142b52a1d3 - # via -r installer/requirements.in -torchdiffeq==0.2.3 \ - --hash=sha256:b5b01ec1294a2d8d5f77e567bf17c5de1237c0573cb94deefa88326f0e18c338 \ - --hash=sha256:fe75f434b9090ac0c27702e02bed21472b0f87035be6581f51edc5d4013ea31a - # via k-diffusion -torchmetrics==0.10.2 \ - --hash=sha256:43757d82266969906fc74b6e80766fcb2a0d52d6c3d09e3b7c98cf3b733fd20c \ - --hash=sha256:daa29d96bff5cff04d80eec5b9f5076993d6ac9c2d2163e88b6b31f8d38f7c25 - # via pytorch-lightning -torchsde==0.2.5 \ - --hash=sha256:222be9e15610d37a4b5a71cfa0c442178f9fd9ca02f6522a3e11c370b3d0906b \ - --hash=sha256:4c34373a94a357bdf60bbfee00c850f3563d634491555820b900c9a4f7eff300 - # via k-diffusion -torchvision==0.13.0+cu116 ; platform_system == "Linux" or platform_system == "Windows" \ - --hash=sha256:1696feadf1921c8fa1549bad774221293298288ebedaa14e44bc3e57e964a369 \ - --hash=sha256:572544b108eaf12638f3dca0f496a453c4b8d8256bcc8333d5355df641c0380c \ - --hash=sha256:76dbe71be271e2f246d556a8201c6f73a431851045d866c51bd945521817b892 \ - --hash=sha256:90b9461f57e1219ca900bfd9e85548b840ec56d57ec331b7a7eb871113b34c0a \ - --hash=sha256:941a8c958f2fe9184ce522567f4a471b52dd306891870e979fe6569062432258 \ - --hash=sha256:9ce27c87a8581d00dcef416ec75f8eca9c225d8c36b81150a1f2a60eb70155dc \ - --hash=sha256:cb6bf0117b8f4b601baeae54e8a6bb5c4942b054835ba997f438ddcb7adcfb90 \ - --hash=sha256:d1a3c124645e3460b3e50b54eb89a2575a5036bfa618f15dc4f5d635c716069d - # via - # -r installer/requirements.in - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # k-diffusion - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity -tornado==6.2 \ - --hash=sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca \ - --hash=sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72 \ - --hash=sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23 \ - --hash=sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8 \ - --hash=sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b \ - --hash=sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9 \ - --hash=sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13 \ - --hash=sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75 \ - --hash=sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac \ - --hash=sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e \ - --hash=sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b - # via streamlit -tqdm==4.64.1 \ - --hash=sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4 \ - --hash=sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1 - # via - # basicsr - # clean-fid - # clip - # facexlib - # gfpgan - # huggingface-hub - # k-diffusion - # pytorch-lightning - # realesrgan - # taming-transformers-rom1504 - # torch-fidelity - # transformers -trampoline==0.1.2 \ - --hash=sha256:36cc9a4ff9811843d177fc0e0740efbd7da39eadfe6e50c9e2937cbc06d899d9 - # via torchsde -transformers==4.24.0 \ - --hash=sha256:486f353a8e594002e48be0e2aba723d96eda839e63bfe274702a4b5eda85559b \ - --hash=sha256:b7ab50039ef9bf817eff14ab974f306fd20a72350bdc9df3a858fd009419322e - # via -r installer/requirements.in -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e - # via - # huggingface-hub - # pytorch-lightning - # qudida - # streamlit - # torch - # torchvision -tzdata==2022.6 \ - --hash=sha256:04a680bdc5b15750c39c12a448885a51134a27ec9af83667663f0b3a1bf3f342 \ - --hash=sha256:91f11db4503385928c15598c98573e3af07e7229181bee5375bd30f1695ddcae - # via - # pytz-deprecation-shim - # tzlocal -tzlocal==4.2 \ - --hash=sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745 \ - --hash=sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7 - # via streamlit -urllib3==1.26.12 \ - --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ - --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 - # via - # requests - # sentry-sdk -validators==0.18.2 \ - --hash=sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd \ - --hash=sha256:37cd9a9213278538ad09b5b9f9134266e7c226ab1fede1d500e29e0a8fbb9ea6 - # via streamlit -wandb==0.13.5 \ - --hash=sha256:11f30a22e30abaa9c187e8b6aa4c12d76160b40bbe98a6f14b0dde9297bbfbe2 \ - --hash=sha256:60d5bcc524b8a314c8e072c03f7702dbd5406261b00a4ce75e7556b805fdc765 - # via k-diffusion -watchdog==2.1.9 \ - --hash=sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412 \ - --hash=sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654 \ - --hash=sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306 \ - --hash=sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33 \ - --hash=sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd \ - --hash=sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7 \ - --hash=sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892 \ - --hash=sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609 \ - --hash=sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6 \ - --hash=sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1 \ - --hash=sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591 \ - --hash=sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d \ - --hash=sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d \ - --hash=sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c \ - --hash=sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3 \ - --hash=sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39 \ - --hash=sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213 \ - --hash=sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330 \ - --hash=sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428 \ - --hash=sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1 \ - --hash=sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846 \ - --hash=sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153 \ - --hash=sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3 \ - --hash=sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9 \ - --hash=sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658 - # via streamlit -wcwidth==0.2.5 \ - --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ - --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 - # via ftfy -werkzeug==2.2.2 \ - --hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \ - --hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5 - # via - # flask - # tb-nightly - # tensorboard -wheel==0.38.2 \ - --hash=sha256:3d492ef22379a156ec923d2a77051cedfd4df4b667864e9e41ba83f0b70b7149 \ - --hash=sha256:7a5a3095dceca97a3cac869b8fef4e89b83fafde21b6688f47b6fda7600eb441 - # via - # tb-nightly - # tensorboard -whichcraft==0.6.1 \ - --hash=sha256:acdbb91b63d6a15efbd6430d1d7b2d36e44a71697e93e19b7ded477afd9fce87 \ - --hash=sha256:deda9266fbb22b8c64fd3ee45c050d61139cd87419765f588e37c8d23e236dd9 - # via flaskwebgui -yapf==0.32.0 \ - --hash=sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32 \ - --hash=sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b - # via - # basicsr - # gfpgan -yarl==1.8.1 \ - --hash=sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb \ - --hash=sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3 \ - --hash=sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035 \ - --hash=sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453 \ - --hash=sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d \ - --hash=sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a \ - --hash=sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231 \ - --hash=sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f \ - --hash=sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae \ - --hash=sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b \ - --hash=sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3 \ - --hash=sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507 \ - --hash=sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd \ - --hash=sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae \ - --hash=sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe \ - --hash=sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c \ - --hash=sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4 \ - --hash=sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64 \ - --hash=sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357 \ - --hash=sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54 \ - --hash=sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461 \ - --hash=sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4 \ - --hash=sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497 \ - --hash=sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0 \ - --hash=sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1 \ - --hash=sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957 \ - --hash=sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350 \ - --hash=sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780 \ - --hash=sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843 \ - --hash=sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548 \ - --hash=sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6 \ - --hash=sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40 \ - --hash=sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee \ - --hash=sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b \ - --hash=sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6 \ - --hash=sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0 \ - --hash=sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e \ - --hash=sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880 \ - --hash=sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc \ - --hash=sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e \ - --hash=sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead \ - --hash=sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28 \ - --hash=sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf \ - --hash=sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd \ - --hash=sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae \ - --hash=sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0 \ - --hash=sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0 \ - --hash=sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae \ - --hash=sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda \ - --hash=sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546 \ - --hash=sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802 \ - --hash=sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be \ - --hash=sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07 \ - --hash=sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936 \ - --hash=sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272 \ - --hash=sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc \ - --hash=sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a \ - --hash=sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28 \ - --hash=sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b - # via aiohttp -zipp==3.10.0 \ - --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ - --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -setuptools==65.5.1 \ - --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ - --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f - # via - # numba - # tb-nightly - # tensorboard - # wandb diff --git a/binary_installer/readme.txt b/binary_installer/readme.txt deleted file mode 100644 index 31456327e9..0000000000 --- a/binary_installer/readme.txt +++ /dev/null @@ -1,17 +0,0 @@ -InvokeAI - -Project homepage: https://github.com/invoke-ai/InvokeAI - -Installation on Windows: - NOTE: You might need to enable Windows Long Paths. If you're not sure, - then you almost certainly need to. Simply double-click the 'WinLongPathsEnabled.reg' - file. Note that you will need to have admin privileges in order to - do this. - - Please double-click the 'install.bat' file (while keeping it inside the invokeAI folder). - -Installation on Linux and Mac: - Please open the terminal, and run './install.sh' (while keeping it inside the invokeAI folder). - -After installation, please run the 'invoke.bat' file (on Windows) or 'invoke.sh' -file (on Linux/Mac) to start InvokeAI. diff --git a/binary_installer/requirements.in b/binary_installer/requirements.in deleted file mode 100644 index 66e0618f78..0000000000 --- a/binary_installer/requirements.in +++ /dev/null @@ -1,33 +0,0 @@ ---prefer-binary ---extra-index-url https://download.pytorch.org/whl/torch_stable.html ---extra-index-url https://download.pytorch.org/whl/cu116 ---trusted-host https://download.pytorch.org -accelerate~=0.15 -albumentations -diffusers[torch]~=0.11 -einops -eventlet -flask_cors -flask_socketio -flaskwebgui==1.0.3 -getpass_asterisk -imageio-ffmpeg -pyreadline3 -realesrgan -send2trash -streamlit -taming-transformers-rom1504 -test-tube -torch-fidelity -torch==1.12.1 ; platform_system == 'Darwin' -torch==1.12.0+cu116 ; platform_system == 'Linux' or platform_system == 'Windows' -torchvision==0.13.1 ; platform_system == 'Darwin' -torchvision==0.13.0+cu116 ; platform_system == 'Linux' or platform_system == 'Windows' -transformers -picklescan -https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip -https://github.com/invoke-ai/clipseg/archive/1f754751c85d7d4255fa681f4491ff5711c1c288.zip -https://github.com/invoke-ai/GFPGAN/archive/3f5d2397361199bc4a91c08bb7d80f04d7805615.zip ; platform_system=='Windows' -https://github.com/invoke-ai/GFPGAN/archive/c796277a1cf77954e5fc0b288d7062d162894248.zip ; platform_system=='Linux' or platform_system=='Darwin' -https://github.com/Birch-san/k-diffusion/archive/363386981fee88620709cf8f6f2eea167bd6cd74.zip -https://github.com/invoke-ai/PyPatchMatch/archive/129863937a8ab37f6bbcec327c994c0f932abdbc.zip diff --git a/docs/contributing/INVOCATIONS.md b/docs/contributing/INVOCATIONS.md index 1f12cfc8f5..212233f497 100644 --- a/docs/contributing/INVOCATIONS.md +++ b/docs/contributing/INVOCATIONS.md @@ -19,31 +19,56 @@ An invocation looks like this: ```py class UpscaleInvocation(BaseInvocation): """Upscales an image.""" - type: Literal['upscale'] = 'upscale' + + # fmt: off + type: Literal["upscale"] = "upscale" # Inputs - image: Union[ImageField,None] = Field(description="The input image") - strength: float = Field(default=0.75, gt=0, le=1, description="The strength") - level: Literal[2,4] = Field(default=2, description = "The upscale level") + image: Union[ImageField, None] = Field(description="The input image", default=None) + strength: float = Field(default=0.75, gt=0, le=1, description="The strength") + level: Literal[2, 4] = Field(default=2, description="The upscale level") + # fmt: on + + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["upscaling", "image"], + }, + } def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get(self.image.image_type, self.image.image_name) - results = context.services.generate.upscale_and_reconstruct( - image_list = [[image, 0]], - upscale = (self.level, self.strength), - strength = 0.0, # GFPGAN strength - save_original = False, - image_callback = None, + image = context.services.images.get_pil_image( + self.image.image_origin, self.image.image_name + ) + results = context.services.restoration.upscale_and_reconstruct( + image_list=[[image, 0]], + upscale=(self.level, self.strength), + strength=0.0, # GFPGAN strength + save_original=False, + image_callback=None, ) # Results are image and seed, unwrap for now # TODO: can this return multiple results? - image_type = ImageType.RESULT - image_name = context.services.images.create_name(context.graph_execution_state_id, self.id) - context.services.images.save(image_type, image_name, results[0][0]) - return ImageOutput( - image = ImageField(image_type = image_type, image_name = image_name) + image_dto = context.services.images.create( + image=results[0][0], + image_origin=ResourceOrigin.INTERNAL, + image_category=ImageCategory.GENERAL, + node_id=self.id, + session_id=context.graph_execution_state_id, + is_intermediate=self.is_intermediate, ) + + return ImageOutput( + image=ImageField( + image_name=image_dto.image_name, + image_origin=image_dto.image_origin, + ), + width=image_dto.width, + height=image_dto.height, + ) + ``` Each portion is important to implement correctly. @@ -95,25 +120,67 @@ Finally, note that for all linking, the `type` of the linked fields must match. If the `name` also matches, then the field can be **automatically linked** to a previous invocation by name and matching. +### Config + +```py + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["upscaling", "image"], + }, + } +``` + +This is an optional configuration for the invocation. It inherits from +pydantic's model `Config` class, and it used primarily to customize the +autogenerated OpenAPI schema. + +The UI relies on the OpenAPI schema in two ways: + +- An API client & Typescript types are generated from it. This happens at build + time. +- The node editor parses the schema into a template used by the UI to create the + node editor UI. This parsing happens at runtime. + +In this example, a `ui` key has been added to the `schema_extra` dict to provide +some tags for the UI, to facilitate filtering nodes. + +See the Schema Generation section below for more information. + ### Invoke Function ```py def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get(self.image.image_type, self.image.image_name) - results = context.services.generate.upscale_and_reconstruct( - image_list = [[image, 0]], - upscale = (self.level, self.strength), - strength = 0.0, # GFPGAN strength - save_original = False, - image_callback = None, + image = context.services.images.get_pil_image( + self.image.image_origin, self.image.image_name + ) + results = context.services.restoration.upscale_and_reconstruct( + image_list=[[image, 0]], + upscale=(self.level, self.strength), + strength=0.0, # GFPGAN strength + save_original=False, + image_callback=None, ) # Results are image and seed, unwrap for now - image_type = ImageType.RESULT - image_name = context.services.images.create_name(context.graph_execution_state_id, self.id) - context.services.images.save(image_type, image_name, results[0][0]) + # TODO: can this return multiple results? + image_dto = context.services.images.create( + image=results[0][0], + image_origin=ResourceOrigin.INTERNAL, + image_category=ImageCategory.GENERAL, + node_id=self.id, + session_id=context.graph_execution_state_id, + is_intermediate=self.is_intermediate, + ) + return ImageOutput( - image = ImageField(image_type = image_type, image_name = image_name) + image=ImageField( + image_name=image_dto.image_name, + image_origin=image_dto.image_origin, + ), + width=image_dto.width, + height=image_dto.height, ) ``` @@ -135,9 +202,16 @@ scenarios. If you need functionality, please provide it as a service in the ```py class ImageOutput(BaseInvocationOutput): """Base class for invocations that output an image""" - type: Literal['image'] = 'image' - image: ImageField = Field(default=None, description="The output image") + # fmt: off + type: Literal["image_output"] = "image_output" + image: ImageField = Field(default=None, description="The output image") + width: int = Field(description="The width of the image in pixels") + height: int = Field(description="The height of the image in pixels") + # fmt: on + + class Config: + schema_extra = {"required": ["type", "image", "width", "height"]} ``` Output classes look like an invocation class without the invoke method. Prefer @@ -168,35 +242,36 @@ Here's that `ImageOutput` class, without the needed schema customisation: class ImageOutput(BaseInvocationOutput): """Base class for invocations that output an image""" - type: Literal["image"] = "image" + # fmt: off + type: Literal["image_output"] = "image_output" image: ImageField = Field(default=None, description="The output image") + width: int = Field(description="The width of the image in pixels") + height: int = Field(description="The height of the image in pixels") + # fmt: on ``` -The generated OpenAPI schema, and all clients/types generated from it, will have -the `type` and `image` properties marked as optional, even though we know they -will always have a value by the time we can interact with them via the API. - -Here's the same class, but with the schema customisation added: +The OpenAPI schema that results from this `ImageOutput` will have the `type`, +`image`, `width` and `height` properties marked as optional, even though we know +they will always have a value. ```python class ImageOutput(BaseInvocationOutput): """Base class for invocations that output an image""" - type: Literal["image"] = "image" + # fmt: off + type: Literal["image_output"] = "image_output" image: ImageField = Field(default=None, description="The output image") + width: int = Field(description="The width of the image in pixels") + height: int = Field(description="The height of the image in pixels") + # fmt: on + # Add schema customization class Config: - schema_extra = { - 'required': [ - 'type', - 'image', - ] - } + schema_extra = {"required": ["type", "image", "width", "height"]} ``` -The resultant schema (and any API client or types generated from it) will now -have see `type` as string literal `"image"` and `image` as an `ImageField` -object. +With the customization in place, the schema will now show these properties as +required, obviating the need for extensive null checks in client code. See this `pydantic` issue for discussion on this solution: diff --git a/docs/features/LOGGING.md b/docs/features/LOGGING.md new file mode 100644 index 0000000000..bda968140b --- /dev/null +++ b/docs/features/LOGGING.md @@ -0,0 +1,171 @@ +--- +title: Controlling Logging +--- + +# :material-image-off: Controlling Logging + +## Controlling How InvokeAI Logs Status Messages + +InvokeAI logs status messages using a configurable logging system. You +can log to the terminal window, to a designated file on the local +machine, to the syslog facility on a Linux or Mac, or to a properly +configured web server. You can configure several logs at the same +time, and control the level of message logged and the logging format +(to a limited extent). + +Three command-line options control logging: + +### `--log_handlers ...` + +This option activates one or more log handlers. Options are "console", +"file", "syslog" and "http". To specify more than one, separate them +by spaces: + +```bash +invokeai-web --log_handlers console syslog=/dev/log file=C:\Users\fred\invokeai.log +``` + +The format of these options is described below. + +### `--log_format {plain|color|legacy|syslog}` + +This controls the format of log messages written to the console. Only +the "console" log handler is currently affected by this setting. + +* "plain" provides formatted messages like this: + +```bash + +[2023-05-24 23:18:2[2023-05-24 23:18:50,352]::[InvokeAI]::DEBUG --> this is a debug message +[2023-05-24 23:18:50,352]::[InvokeAI]::INFO --> this is an informational messages +[2023-05-24 23:18:50,352]::[InvokeAI]::WARNING --> this is a warning +[2023-05-24 23:18:50,352]::[InvokeAI]::ERROR --> this is an error +[2023-05-24 23:18:50,352]::[InvokeAI]::CRITICAL --> this is a critical error +``` + +* "color" produces similar output, but the text will be color coded to +indicate the severity of the message. + +* "legacy" produces output similar to InvokeAI versions 2.3 and earlier: + +```bash +### this is a critical error +*** this is an error +** this is a warning +>> this is an informational messages + | this is a debug message +``` + +* "syslog" produces messages suitable for syslog entries: + +```bash +InvokeAI [2691178] this is a critical error +InvokeAI [2691178] this is an error +InvokeAI [2691178] this is a warning +InvokeAI [2691178] this is an informational messages +InvokeAI [2691178] this is a debug message +``` + +(note that the date, time and hostname will be added by the syslog +system) + +### `--log_level {debug|info|warning|error|critical}` + +Providing this command-line option will cause only messages at the +specified level or above to be emitted. + +## Console logging + +When "console" is provided to `--log_handlers`, messages will be +written to the command line window in which InvokeAI was launched. By +default, the color formatter will be used unless overridden by +`--log_format`. + +## File logging + +When "file" is provided to `--log_handlers`, entries will be written +to the file indicated in the path argument. By default, the "plain" +format will be used: + +```bash +invokeai-web --log_handlers file=/var/log/invokeai.log +``` + +## Syslog logging + +When "syslog" is requested, entries will be sent to the syslog +system. There are a variety of ways to control where the log message +is sent: + +* Send to the local machine using the `/dev/log` socket: + +``` +invokeai-web --log_handlers syslog=/dev/log +``` + +* Send to the local machine using a UDP message: + +``` +invokeai-web --log_handlers syslog=localhost +``` + +* Send to the local machine using a UDP message on a nonstandard + port: + +``` +invokeai-web --log_handlers syslog=localhost:512 +``` + +* Send to a remote machine named "loghost" on the local LAN using + facility LOG_USER and UDP packets: + +``` +invokeai-web --log_handlers syslog=loghost,facility=LOG_USER,socktype=SOCK_DGRAM +``` + +This can be abbreviated `syslog=loghost`, as LOG_USER and SOCK_DGRAM +are defaults. + +* Send to a remote machine named "loghost" using the facility LOCAL0 + and using a TCP socket: + +``` +invokeai-web --log_handlers syslog=loghost,facility=LOG_LOCAL0,socktype=SOCK_STREAM +``` + +If no arguments are specified (just a bare "syslog"), then the logging +system will look for a UNIX socket named `/dev/log`, and if not found +try to send a UDP message to `localhost`. The Macintosh OS used to +support logging to a socket named `/var/run/syslog`, but this feature +has since been disabled. + +## Web logging + +If you have access to a web server that is configured to log messages +when a particular URL is requested, you can log using the "http" +method: + +``` +invokeai-web --log_handlers http=http://my.server/path/to/logger,method=POST +``` + +The optional [,method=] part can be used to specify whether the URL +accepts GET (default) or POST messages. + +Currently password authentication and SSL are not supported. + +## Using the configuration file + +You can set and forget logging options by adding a "Logging" section +to `invokeai.yaml`: + +``` +InvokeAI: + [... other settings...] + Logging: + log_handlers: + - console + - syslog=/dev/log + log_level: info + log_format: color +``` diff --git a/docs/features/index.md b/docs/features/index.md index f7a3c40a25..12539ca98f 100644 --- a/docs/features/index.md +++ b/docs/features/index.md @@ -45,6 +45,9 @@ Personalize models by adding your own style or subjects. ## * [The NSFW Checker](NSFW.md) Prevent InvokeAI from displaying unwanted racy images. +## * [Controlling Logging](LOGGING.md) +Control how InvokeAI logs status messages. + ## * [Miscellaneous](OTHER.md) Run InvokeAI on Google Colab, generate images with repeating patterns, batch process a file of prompts, increase the "creativity" of image diff --git a/docs/index.md b/docs/index.md index 4071af0a14..3af8571df9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -68,7 +68,7 @@ title: Home implementation of Stable Diffusion, the open source text-to-image and image-to-image generator. It provides a streamlined process with various new features and options to aid the image generation process. It runs on Windows, -Mac and Linux machines, and runs on GPU cards with as little as 4 GB or RAM. +Mac and Linux machines, and runs on GPU cards with as little as 4 GB of RAM. **Quick links**: [Discord Server] [Code and Downloads] [ None: "3. Create initial configuration files.", "", "[i]At any point you may interrupt this program and resume later.", + "", + "[b]For the best user experience, please enlarge or maximize this window", ), ) ) diff --git a/installer/templates/invoke.bat.in b/installer/templates/invoke.bat.in index d5ec9d07e9..b5816164a3 100644 --- a/installer/templates/invoke.bat.in +++ b/installer/templates/invoke.bat.in @@ -7,42 +7,42 @@ call .venv\Scripts\activate.bat set INVOKEAI_ROOT=. :start -echo Do you want to generate images using the -echo 1. command-line interface -echo 2. browser-based UI -echo 3. run textual inversion training -echo 4. merge models (diffusers type only) -echo 5. download and install models -echo 6. change InvokeAI startup options -echo 7. re-run the configure script to fix a broken install -echo 8. open the developer console -echo 9. update InvokeAI -echo 10. command-line help -echo Q - quit -set /P restore="Please enter 1-10, Q: [2] " -if not defined restore set restore=2 -IF /I "%restore%" == "1" ( +echo Desired action: +echo 1. Generate images with the browser-based interface +echo 2. Explore InvokeAI nodes using a command-line interface +echo 3. Run textual inversion training +echo 4. Merge models (diffusers type only) +echo 5. Download and install models +echo 6. Change InvokeAI startup options +echo 7. Re-run the configure script to fix a broken install +echo 8. Open the developer console +echo 9. Update InvokeAI +echo 10. Command-line help +echo Q - Quit +set /P choice="Please enter 1-10, Q: [2] " +if not defined choice set choice=2 +IF /I "%choice%" == "1" ( + echo Starting the InvokeAI browser-based UI.. + python .venv\Scripts\invokeai-web.exe %* +) ELSE IF /I "%choice%" == "2" ( echo Starting the InvokeAI command-line.. python .venv\Scripts\invokeai.exe %* -) ELSE IF /I "%restore%" == "2" ( - echo Starting the InvokeAI browser-based UI.. - python .venv\Scripts\invokeai.exe --web %* -) ELSE IF /I "%restore%" == "3" ( +) ELSE IF /I "%choice%" == "3" ( echo Starting textual inversion training.. python .venv\Scripts\invokeai-ti.exe --gui -) ELSE IF /I "%restore%" == "4" ( +) ELSE IF /I "%choice%" == "4" ( echo Starting model merging script.. python .venv\Scripts\invokeai-merge.exe --gui -) ELSE IF /I "%restore%" == "5" ( +) ELSE IF /I "%choice%" == "5" ( echo Running invokeai-model-install... python .venv\Scripts\invokeai-model-install.exe -) ELSE IF /I "%restore%" == "6" ( +) ELSE IF /I "%choice%" == "6" ( echo Running invokeai-configure... python .venv\Scripts\invokeai-configure.exe --skip-sd-weight --skip-support-models -) ELSE IF /I "%restore%" == "7" ( +) ELSE IF /I "%choice%" == "7" ( echo Running invokeai-configure... python .venv\Scripts\invokeai-configure.exe --yes --default_only -) ELSE IF /I "%restore%" == "8" ( +) ELSE IF /I "%choice%" == "8" ( echo Developer Console echo Python command is: where python @@ -54,15 +54,15 @@ IF /I "%restore%" == "1" ( echo ************************* echo *** Type `exit` to quit this shell and deactivate the Python virtual environment *** call cmd /k -) ELSE IF /I "%restore%" == "9" ( +) ELSE IF /I "%choice%" == "9" ( echo Running invokeai-update... python .venv\Scripts\invokeai-update.exe %* -) ELSE IF /I "%restore%" == "10" ( +) ELSE IF /I "%choice%" == "10" ( echo Displaying command line help... python .venv\Scripts\invokeai.exe --help %* pause exit /b -) ELSE IF /I "%restore%" == "q" ( +) ELSE IF /I "%choice%" == "q" ( echo Goodbye! goto ending ) ELSE ( diff --git a/installer/templates/invoke.sh.in b/installer/templates/invoke.sh.in index 4576c7172f..e457d57842 100644 --- a/installer/templates/invoke.sh.in +++ b/installer/templates/invoke.sh.in @@ -1,5 +1,10 @@ #!/bin/bash +# MIT License + +# Coauthored by Lincoln Stein, Eugene Brodsky and Joshua Kimsey +# Copyright 2023, The InvokeAI Development Team + #### # This launch script assumes that: # 1. it is located in the runtime directory, @@ -11,85 +16,168 @@ set -eu -# ensure we're in the correct folder in case user's CWD is somewhere else +# Ensure we're in the correct folder in case user's CWD is somewhere else scriptdir=$(dirname "$0") cd "$scriptdir" . .venv/bin/activate export INVOKEAI_ROOT="$scriptdir" +PARAMS=$@ -# set required env var for torch on mac MPS +# Check to see if dialog is installed (it seems to be fairly standard, but good to check regardless) and if the user has passed the --no-tui argument to disable the dialog TUI +tui=true +if command -v dialog &>/dev/null; then + # This must use $@ to properly loop through the arguments passed by the user + for arg in "$@"; do + if [ "$arg" == "--no-tui" ]; then + tui=false + # Remove the --no-tui argument to avoid errors later on when passing arguments to InvokeAI + PARAMS=$(echo "$PARAMS" | sed 's/--no-tui//') + break + fi + done +else + tui=false +fi + +# Set required env var for torch on mac MPS if [ "$(uname -s)" == "Darwin" ]; then export PYTORCH_ENABLE_MPS_FALLBACK=1 fi -if [ "$0" != "bash" ]; then - while true - do - echo "Do you want to generate images using the" - echo "1. command-line interface" - echo "2. browser-based UI" - echo "3. run textual inversion training" - echo "4. merge models (diffusers type only)" - echo "5. download and install models" - echo "6. change InvokeAI startup options" - echo "7. re-run the configure script to fix a broken install" - echo "8. open the developer console" - echo "9. update InvokeAI" - echo "10. command-line help" - echo "Q - Quit" - echo "" - read -p "Please enter 1-10, Q: [2] " yn - choice=${yn:='2'} - case $choice in - 1) - echo "Starting the InvokeAI command-line..." - invokeai $@ - ;; - 2) - echo "Starting the InvokeAI browser-based UI..." - invokeai --web $@ - ;; - 3) - echo "Starting Textual Inversion:" - invokeai-ti --gui $@ - ;; - 4) - echo "Merging Models:" - invokeai-merge --gui $@ - ;; - 5) - invokeai-model-install --root ${INVOKEAI_ROOT} - ;; - 6) - invokeai-configure --root ${INVOKEAI_ROOT} --skip-sd-weights --skip-support-models - ;; - 7) - invokeai-configure --root ${INVOKEAI_ROOT} --yes --default_only - ;; - 8) - echo "Developer Console:" - file_name=$(basename "${BASH_SOURCE[0]}") - bash --init-file "$file_name" - ;; - 9) - echo "Update:" - invokeai-update - ;; - 10) - invokeai --help - ;; - [qQ]) - exit 0 - ;; - *) - echo "Invalid selection" - exit;; +# Primary function for the case statement to determine user input +do_choice() { + case $1 in + 1) + clear + printf "Generate images with a browser-based interface\n" + invokeai-web $PARAMS + ;; + 2) + clear + printf "Explore InvokeAI nodes using a command-line interface\n" + invokeai $PARAMS + ;; + 3) + clear + printf "Textual inversion training\n" + invokeai-ti --gui $PARAMS + ;; + 4) + clear + printf "Merge models (diffusers type only)\n" + invokeai-merge --gui $PARAMS + ;; + 5) + clear + printf "Download and install models\n" + invokeai-model-install --root ${INVOKEAI_ROOT} + ;; + 6) + clear + printf "Change InvokeAI startup options\n" + invokeai-configure --root ${INVOKEAI_ROOT} --skip-sd-weights --skip-support-models + ;; + 7) + clear + printf "Re-run the configure script to fix a broken install\n" + invokeai-configure --root ${INVOKEAI_ROOT} --yes --default_only + ;; + 8) + clear + printf "Open the developer console\n" + file_name=$(basename "${BASH_SOURCE[0]}") + bash --init-file "$file_name" + ;; + 9) + clear + printf "Update InvokeAI\n" + invokeai-update + ;; + 10) + clear + printf "Command-line help\n" + invokeai --help + ;; + "HELP 1") + clear + printf "Command-line help\n" + invokeai --help + ;; + *) + clear + printf "Exiting...\n" + exit + ;; esac - done + clear +} + +# Dialog-based TUI for launcing Invoke functions +do_dialog() { + options=( + 1 "Generate images with a browser-based interface" + 2 "Generate images using a command-line interface" + 3 "Textual inversion training" + 4 "Merge models (diffusers type only)" + 5 "Download and install models" + 6 "Change InvokeAI startup options" + 7 "Re-run the configure script to fix a broken install" + 8 "Open the developer console" + 9 "Update InvokeAI") + + choice=$(dialog --clear \ + --backtitle "\Zb\Zu\Z3InvokeAI" \ + --colors \ + --title "What would you like to do?" \ + --ok-label "Run" \ + --cancel-label "Exit" \ + --help-button \ + --help-label "CLI Help" \ + --menu "Select an option:" \ + 0 0 0 \ + "${options[@]}" \ + 2>&1 >/dev/tty) || clear + do_choice "$choice" + clear +} + +# Command-line interface for launching Invoke functions +do_line_input() { + clear + printf " ** For a more attractive experience, please install the 'dialog' utility using your package manager. **\n\n" + printf "What would you like to do?\n" + printf "1: Generate images using the browser-based interface\n" + printf "2: Explore InvokeAI nodes using the command-line interface\n" + printf "3: Run textual inversion training\n" + printf "4: Merge models (diffusers type only)\n" + printf "5: Download and install models\n" + printf "6: Change InvokeAI startup options\n" + printf "7: Re-run the configure script to fix a broken install\n" + printf "8: Open the developer console\n" + printf "9: Update InvokeAI\n" + printf "10: Command-line help\n" + printf "Q: Quit\n\n" + read -p "Please enter 1-10, Q: [1] " yn + choice=${yn:='1'} + do_choice $choice + clear +} + +# Main IF statement for launching Invoke with either the TUI or CLI, and for checking if the user is in the developer console +if [ "$0" != "bash" ]; then + while true; do + if $tui; then + # .dialogrc must be located in the same directory as the invoke.sh script + export DIALOGRC="./.dialogrc" + do_dialog + else + do_line_input + fi + done else # in developer console python --version - echo "Press ^D to exit" + printf "Press ^D to exit\n" export PS1="(InvokeAI) \u@\h \w> " fi diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index dfef5d2176..efeb778922 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -2,15 +2,24 @@ from logging import Logger import os +from invokeai.app.services.board_image_record_storage import ( + SqliteBoardImageRecordStorage, +) +from invokeai.app.services.board_images import ( + BoardImagesService, + BoardImagesServiceDependencies, +) +from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage +from invokeai.app.services.boards import BoardService, BoardServiceDependencies from invokeai.app.services.image_record_storage import SqliteImageRecordStorage -from invokeai.app.services.images import ImageService +from invokeai.app.services.images import ImageService, ImageServiceDependencies from invokeai.app.services.metadata import CoreMetadataService +from invokeai.app.services.resource_name import SimpleNameService from invokeai.app.services.urls import LocalUrlService from invokeai.backend.util.logging import InvokeAILogger from ..services.default_graphs import create_system_graphs from ..services.latent_storage import DiskLatentsStorage, ForwardCacheLatentsStorage -from ..services.model_manager_initializer import get_model_manager from ..services.restoration_services import RestorationServices from ..services.graph import GraphExecutionState, LibraryGraph from ..services.image_file_storage import DiskImageFileStorage @@ -19,6 +28,7 @@ from ..services.invocation_services import InvocationServices from ..services.invoker import Invoker from ..services.processor import DefaultInvocationProcessor from ..services.sqlite import SqliteItemStorage +from ..services.model_manager_service import ModelManagerService from .events import FastAPIEventService @@ -52,12 +62,11 @@ class ApiDependencies: events = FastAPIEventService(event_handler_id) - output_folder = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../../../outputs") - ) + output_folder = config.output_path # TODO: build a file/path manager? - db_location = os.path.join(output_folder, "invokeai.db") + db_location = config.db_path + db_location.parent.mkdir(parents=True, exist_ok=True) graph_execution_manager = SqliteItemStorage[GraphExecutionState]( filename=db_location, table_name="graph_executions" @@ -67,25 +76,54 @@ class ApiDependencies: metadata = CoreMetadataService() image_record_storage = SqliteImageRecordStorage(db_location) image_file_storage = DiskImageFileStorage(f"{output_folder}/images") - + names = SimpleNameService() latents = ForwardCacheLatentsStorage( DiskLatentsStorage(f"{output_folder}/latents") ) + board_record_storage = SqliteBoardRecordStorage(db_location) + board_image_record_storage = SqliteBoardImageRecordStorage(db_location) + + boards = BoardService( + services=BoardServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) + ) + + board_images = BoardImagesService( + services=BoardImagesServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) + ) + images = ImageService( - image_record_storage=image_record_storage, - image_file_storage=image_file_storage, - metadata=metadata, - url=urls, - logger=logger, - graph_execution_manager=graph_execution_manager, + services=ImageServiceDependencies( + board_image_record_storage=board_image_record_storage, + image_record_storage=image_record_storage, + image_file_storage=image_file_storage, + metadata=metadata, + url=urls, + logger=logger, + names=names, + graph_execution_manager=graph_execution_manager, + ) ) services = InvocationServices( - model_manager=get_model_manager(config, logger), + model_manager=ModelManagerService(config,logger), events=events, latents=latents, images=images, + boards=boards, + board_images=board_images, queue=MemoryInvocationQueue(), graph_library=SqliteItemStorage[LibraryGraph]( filename=db_location, table_name="graphs" diff --git a/invokeai/app/api/models/images.py b/invokeai/app/api/models/images.py deleted file mode 100644 index fa04702326..0000000000 --- a/invokeai/app/api/models/images.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field - -from invokeai.app.models.image import ImageType - - -class ImageResponseMetadata(BaseModel): - """An image's metadata. Used only in HTTP responses.""" - - created: int = Field(description="The creation timestamp of the image") - width: int = Field(description="The width of the image in pixels") - height: int = Field(description="The height of the image in pixels") - # invokeai: Optional[InvokeAIMetadata] = Field( - # description="The image's InvokeAI-specific metadata" - # ) - - -class ImageResponse(BaseModel): - """The response type for images""" - - image_type: ImageType = Field(description="The type of the image") - image_name: str = Field(description="The name of the image") - image_url: str = Field(description="The url of the image") - thumbnail_url: str = Field(description="The url of the image's thumbnail") - metadata: ImageResponseMetadata = Field(description="The image's metadata") - - -class ProgressImage(BaseModel): - """The progress image sent intermittently during processing""" - - width: int = Field(description="The effective width of the image in pixels") - height: int = Field(description="The effective height of the image in pixels") - dataURL: str = Field(description="The image data as a b64 data URL") - - -class SavedImage(BaseModel): - image_name: str = Field(description="The name of the saved image") - thumbnail_name: str = Field(description="The name of the saved thumbnail") - created: int = Field(description="The created timestamp of the saved image") diff --git a/invokeai/app/api/routers/board_images.py b/invokeai/app/api/routers/board_images.py new file mode 100644 index 0000000000..b206ab500d --- /dev/null +++ b/invokeai/app/api/routers/board_images.py @@ -0,0 +1,69 @@ +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO +from invokeai.app.services.models.image_record import ImageDTO + +from ..dependencies import ApiDependencies + +board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"]) + + +@board_images_router.post( + "/", + operation_id="create_board_image", + responses={ + 201: {"description": "The image was added to a board successfully"}, + }, + status_code=201, +) +async def create_board_image( + board_id: str = Body(description="The id of the board to add to"), + image_name: str = Body(description="The name of the image to add"), +): + """Creates a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.add_image_to_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to add to board") + +@board_images_router.delete( + "/", + operation_id="remove_board_image", + responses={ + 201: {"description": "The image was removed from the board successfully"}, + }, + status_code=201, +) +async def remove_board_image( + board_id: str = Body(description="The id of the board"), + image_name: str = Body(description="The name of the image to remove"), +): + """Deletes a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.remove_image_from_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") + + + +@board_images_router.get( + "/{board_id}", + operation_id="list_board_images", + response_model=OffsetPaginatedResults[ImageDTO], +) +async def list_board_images( + board_id: str = Path(description="The id of the board"), + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[ImageDTO]: + """Gets a list of images for a board""" + + results = ApiDependencies.invoker.services.board_images.get_images_for_board( + board_id, + ) + return results + diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py new file mode 100644 index 0000000000..94d8667ae4 --- /dev/null +++ b/invokeai/app/api/routers/boards.py @@ -0,0 +1,117 @@ +from typing import Optional, Union +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO + + +from ..dependencies import ApiDependencies + +boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) + + +@boards_router.post( + "/", + operation_id="create_board", + responses={ + 201: {"description": "The board was created successfully"}, + }, + status_code=201, + response_model=BoardDTO, +) +async def create_board( + board_name: str = Query(description="The name of the board to create"), +) -> BoardDTO: + """Creates a board""" + try: + result = ApiDependencies.invoker.services.boards.create(board_name=board_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to create board") + + +@boards_router.get("/{board_id}", operation_id="get_board", response_model=BoardDTO) +async def get_board( + board_id: str = Path(description="The id of board to get"), +) -> BoardDTO: + """Gets a board""" + + try: + result = ApiDependencies.invoker.services.boards.get_dto(board_id=board_id) + return result + except Exception as e: + raise HTTPException(status_code=404, detail="Board not found") + + +@boards_router.patch( + "/{board_id}", + operation_id="update_board", + responses={ + 201: { + "description": "The board was updated successfully", + }, + }, + status_code=201, + response_model=BoardDTO, +) +async def update_board( + board_id: str = Path(description="The id of board to update"), + changes: BoardChanges = Body(description="The changes to apply to the board"), +) -> BoardDTO: + """Updates a board""" + try: + result = ApiDependencies.invoker.services.boards.update( + board_id=board_id, changes=changes + ) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") + + +@boards_router.delete("/{board_id}", operation_id="delete_board") +async def delete_board( + board_id: str = Path(description="The id of board to delete"), + include_images: Optional[bool] = Query( + description="Permanently delete all images on the board", default=False + ), +) -> None: + """Deletes a board""" + try: + if include_images is True: + ApiDependencies.invoker.services.images.delete_images_on_board( + board_id=board_id + ) + ApiDependencies.invoker.services.boards.delete(board_id=board_id) + else: + ApiDependencies.invoker.services.boards.delete(board_id=board_id) + except Exception as e: + # TODO: Does this need any exception handling at all? + pass + + +@boards_router.get( + "/", + operation_id="list_boards", + response_model=Union[OffsetPaginatedResults[BoardDTO], list[BoardDTO]], +) +async def list_boards( + all: Optional[bool] = Query(default=None, description="Whether to list all boards"), + offset: Optional[int] = Query(default=None, description="The page offset"), + limit: Optional[int] = Query( + default=None, description="The number of boards per page" + ), +) -> Union[OffsetPaginatedResults[BoardDTO], list[BoardDTO]]: + """Gets a list of boards""" + if all: + return ApiDependencies.invoker.services.boards.get_all() + elif offset is not None and limit is not None: + return ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + else: + raise HTTPException( + status_code=400, + detail="Invalid request: Must provide either 'all' or both 'offset' and 'limit'", + ) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index 920181ff8b..a8c84b81b9 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -6,8 +6,9 @@ from fastapi.responses import FileResponse from PIL import Image from invokeai.app.models.image import ( ImageCategory, - ImageType, + ResourceOrigin, ) +from invokeai.app.services.image_record_storage import OffsetPaginatedResults from invokeai.app.services.models.image_record import ( ImageDTO, ImageRecordChanges, @@ -34,12 +35,8 @@ async def upload_image( file: UploadFile, request: Request, response: Response, - image_category: ImageCategory = Query( - default=ImageCategory.GENERAL, description="The category of the image" - ), - is_intermediate: bool = Query( - default=False, description="Whether this is an intermediate image" - ), + image_category: ImageCategory = Query(description="The category of the image"), + is_intermediate: bool = Query(description="Whether this is an intermediate image"), session_id: Optional[str] = Query( default=None, description="The session ID associated with this upload, if any" ), @@ -59,7 +56,7 @@ async def upload_image( try: image_dto = ApiDependencies.invoker.services.images.create( image=pil_image, - image_type=ImageType.UPLOAD, + image_origin=ResourceOrigin.EXTERNAL, image_category=image_category, session_id=session_id, is_intermediate=is_intermediate, @@ -73,27 +70,25 @@ async def upload_image( raise HTTPException(status_code=500, detail="Failed to create image") -@images_router.delete("/{image_type}/{image_name}", operation_id="delete_image") +@images_router.delete("/{image_name}", operation_id="delete_image") async def delete_image( - image_type: ImageType = Path(description="The type of image to delete"), image_name: str = Path(description="The name of the image to delete"), ) -> None: """Deletes an image""" try: - ApiDependencies.invoker.services.images.delete(image_type, image_name) + ApiDependencies.invoker.services.images.delete(image_name) except Exception as e: # TODO: Does this need any exception handling at all? pass @images_router.patch( - "/{image_type}/{image_name}", + "/{image_name}", operation_id="update_image", response_model=ImageDTO, ) async def update_image( - image_type: ImageType = Path(description="The type of image to update"), image_name: str = Path(description="The name of the image to update"), image_changes: ImageRecordChanges = Body( description="The changes to apply to the image" @@ -102,32 +97,29 @@ async def update_image( """Updates an image""" try: - return ApiDependencies.invoker.services.images.update( - image_type, image_name, image_changes - ) + return ApiDependencies.invoker.services.images.update(image_name, image_changes) except Exception as e: raise HTTPException(status_code=400, detail="Failed to update image") @images_router.get( - "/{image_type}/{image_name}/metadata", + "/{image_name}/metadata", operation_id="get_image_metadata", response_model=ImageDTO, ) async def get_image_metadata( - image_type: ImageType = Path(description="The type of image to get"), image_name: str = Path(description="The name of image to get"), ) -> ImageDTO: """Gets an image's metadata""" try: - return ApiDependencies.invoker.services.images.get_dto(image_type, image_name) + return ApiDependencies.invoker.services.images.get_dto(image_name) except Exception as e: raise HTTPException(status_code=404) @images_router.get( - "/{image_type}/{image_name}", + "/{image_name}", operation_id="get_image_full", response_class=Response, responses={ @@ -139,15 +131,12 @@ async def get_image_metadata( }, ) async def get_image_full( - image_type: ImageType = Path( - description="The type of full-resolution image file to get" - ), image_name: str = Path(description="The name of full-resolution image file to get"), ) -> FileResponse: """Gets a full-resolution image file""" try: - path = ApiDependencies.invoker.services.images.get_path(image_type, image_name) + path = ApiDependencies.invoker.services.images.get_path(image_name) if not ApiDependencies.invoker.services.images.validate_path(path): raise HTTPException(status_code=404) @@ -163,7 +152,7 @@ async def get_image_full( @images_router.get( - "/{image_type}/{image_name}/thumbnail", + "/{image_name}/thumbnail", operation_id="get_image_thumbnail", response_class=Response, responses={ @@ -175,14 +164,13 @@ async def get_image_full( }, ) async def get_image_thumbnail( - image_type: ImageType = Path(description="The type of thumbnail image file to get"), image_name: str = Path(description="The name of thumbnail image file to get"), ) -> FileResponse: """Gets a thumbnail image file""" try: path = ApiDependencies.invoker.services.images.get_path( - image_type, image_name, thumbnail=True + image_name, thumbnail=True ) if not ApiDependencies.invoker.services.images.validate_path(path): raise HTTPException(status_code=404) @@ -195,25 +183,21 @@ async def get_image_thumbnail( @images_router.get( - "/{image_type}/{image_name}/urls", + "/{image_name}/urls", operation_id="get_image_urls", response_model=ImageUrlsDTO, ) async def get_image_urls( - image_type: ImageType = Path(description="The type of the image whose URL to get"), image_name: str = Path(description="The name of the image whose URL to get"), ) -> ImageUrlsDTO: """Gets an image and thumbnail URL""" try: - image_url = ApiDependencies.invoker.services.images.get_url( - image_type, image_name - ) + image_url = ApiDependencies.invoker.services.images.get_url(image_name) thumbnail_url = ApiDependencies.invoker.services.images.get_url( - image_type, image_name, thumbnail=True + image_name, thumbnail=True ) return ImageUrlsDTO( - image_type=image_type, image_name=image_name, image_url=image_url, thumbnail_url=thumbnail_url, @@ -225,23 +209,33 @@ async def get_image_urls( @images_router.get( "/", operation_id="list_images_with_metadata", - response_model=PaginatedResults[ImageDTO], + response_model=OffsetPaginatedResults[ImageDTO], ) async def list_images_with_metadata( - image_type: ImageType = Query(description="The type of images to list"), - image_category: ImageCategory = Query(description="The kind of images to list"), - page: int = Query(default=0, description="The page of image metadata to get"), - per_page: int = Query( - default=10, description="The number of image metadata per page" + image_origin: Optional[ResourceOrigin] = Query( + default=None, description="The origin of images to list" ), -) -> PaginatedResults[ImageDTO]: - """Gets a list of images with metadata""" + categories: Optional[list[ImageCategory]] = Query( + default=None, description="The categories of image to include" + ), + is_intermediate: Optional[bool] = Query( + default=None, description="Whether to list intermediate images" + ), + board_id: Optional[str] = Query( + default=None, description="The board id to filter by" + ), + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of images per page"), +) -> OffsetPaginatedResults[ImageDTO]: + """Gets a list of images""" image_dtos = ApiDependencies.invoker.services.images.get_many( - image_type, - image_category, - page, - per_page, + offset, + limit, + image_origin, + categories, + is_intermediate, + board_id, ) return image_dtos diff --git a/invokeai/app/api/routers/models.py b/invokeai/app/api/routers/models.py index ca83b44bf3..0b03c8e729 100644 --- a/invokeai/app/api/routers/models.py +++ b/invokeai/app/api/routers/models.py @@ -1,13 +1,14 @@ # Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654) and 2023 Kent Keirsey (https://github.com/hipsterusername) -import shutil -import asyncio -from typing import Annotated, Any, List, Literal, Optional, Union +from typing import Literal, Optional, Union +from fastapi import Query from fastapi.routing import APIRouter, HTTPException from pydantic import BaseModel, Field, parse_obj_as -from pathlib import Path from ..dependencies import ApiDependencies +from invokeai.backend import BaseModelType, ModelType +from invokeai.backend.model_management.models import OPENAPI_MODEL_CONFIGS, SchedulerPredictionType +MODEL_CONFIGS = Union[tuple(OPENAPI_MODEL_CONFIGS)] models_router = APIRouter(prefix="/v1/models", tags=["models"]) @@ -19,6 +20,15 @@ class VaeRepo(BaseModel): class ModelInfo(BaseModel): description: Optional[str] = Field(description="A description of the model") + model_name: str = Field(description="The name of the model") + model_type: str = Field(description="The type of the model") + +class DiffusersModelInfo(ModelInfo): + format: Literal['folder'] = 'folder' + + vae: Optional[VaeRepo] = Field(description="The VAE repo to use for this model") + repo_id: Optional[str] = Field(description="The repo ID to use for this model") + path: Optional[str] = Field(description="The path to the model") class CkptModelInfo(ModelInfo): format: Literal['ckpt'] = 'ckpt' @@ -29,12 +39,8 @@ class CkptModelInfo(ModelInfo): width: Optional[int] = Field(description="The width of the model") height: Optional[int] = Field(description="The height of the model") -class DiffusersModelInfo(ModelInfo): - format: Literal['diffusers'] = 'diffusers' - - vae: Optional[VaeRepo] = Field(description="The VAE repo to use for this model") - repo_id: Optional[str] = Field(description="The repo ID to use for this model") - path: Optional[str] = Field(description="The path to the model") +class SafetensorsModelInfo(CkptModelInfo): + format: Literal['safetensors'] = 'safetensors' class CreateModelRequest(BaseModel): name: str = Field(description="The name of the model") @@ -45,18 +51,21 @@ class CreateModelResponse(BaseModel): info: Union[CkptModelInfo, DiffusersModelInfo] = Field(discriminator="format", description="The model info") status: str = Field(description="The status of the API response") +class ImportModelRequest(BaseModel): + name: str = Field(description="A model path, repo_id or URL to import") + prediction_type: Optional[Literal['epsilon','v_prediction','sample']] = Field(description='Prediction type for SDv2 checkpoint files') + class ConversionRequest(BaseModel): name: str = Field(description="The name of the new model") info: CkptModelInfo = Field(description="The converted model info") save_location: str = Field(description="The path to save the converted model weights") - class ConvertedModelResponse(BaseModel): name: str = Field(description="The name of the new model") info: DiffusersModelInfo = Field(description="The converted model info") class ModelsList(BaseModel): - models: dict[str, Annotated[Union[(CkptModelInfo,DiffusersModelInfo)], Field(discriminator="format")]] + models: list[MODEL_CONFIGS] @models_router.get( @@ -64,9 +73,16 @@ class ModelsList(BaseModel): operation_id="list_models", responses={200: {"model": ModelsList }}, ) -async def list_models() -> ModelsList: +async def list_models( + base_model: Optional[BaseModelType] = Query( + default=None, description="Base model" + ), + model_type: Optional[ModelType] = Query( + default=None, description="The type of model to get" + ), +) -> ModelsList: """Gets a list of models""" - models_raw = ApiDependencies.invoker.services.model_manager.list_models() + models_raw = ApiDependencies.invoker.services.model_manager.list_models(base_model, model_type) models = parse_obj_as(ModelsList, { "models": models_raw }) return models @@ -92,6 +108,28 @@ async def update_model( return model_response +@models_router.post( + "/", + operation_id="import_model", + responses={200: {"status": "success"}}, +) +async def import_model( + model_request: ImportModelRequest +) -> None: + """ Add Model """ + items_to_import = set([model_request.name]) + prediction_types = { x.value: x for x in SchedulerPredictionType } + logger = ApiDependencies.invoker.services.logger + + installed_models = ApiDependencies.invoker.services.model_manager.heuristic_import( + items_to_import = items_to_import, + prediction_type_helper = lambda x: prediction_types.get(model_request.prediction_type) + ) + if len(installed_models) > 0: + logger.info(f'Successfully imported {model_request.name}') + else: + logger.error(f'Model {model_request.name} not imported') + raise HTTPException(status_code=500, detail=f'Model {model_request.name} not imported') @models_router.delete( "/{model_name}", @@ -121,7 +159,7 @@ async def delete_model(model_name: str) -> None: raise HTTPException(status_code=204, detail=f"Model '{model_name}' deleted successfully") else: - logger.error(f"Model not found") + logger.error("Model not found") raise HTTPException(status_code=404, detail=f"Model '{model_name}' not found") diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 69d322578d..e14c58bab7 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -3,7 +3,7 @@ import asyncio from inspect import signature import uvicorn -from invokeai.backend.util.logging import InvokeAILogger + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html @@ -11,15 +11,22 @@ from fastapi.openapi.utils import get_openapi from fastapi.staticfiles import StaticFiles from fastapi_events.handlers.local import local_handler from fastapi_events.middleware import EventHandlerASGIMiddleware +from pathlib import Path from pydantic.schema import schema +#This should come early so that modules can log their initialization properly +from .services.config import InvokeAIAppConfig +from ..backend.util.logging import InvokeAILogger +app_config = InvokeAIAppConfig.get_config() +app_config.parse_args() +logger = InvokeAILogger.getLogger(config=app_config) + +import invokeai.frontend.web as web_dir + from .api.dependencies import ApiDependencies -from .api.routers import sessions, models, images +from .api.routers import sessions, models, images, boards, board_images from .api.sockets import SocketIO from .invocations.baseinvocation import BaseInvocation -from .services.config import InvokeAIAppConfig - -logger = InvokeAILogger.getLogger() # Create the app # TODO: create this all in a method so configuration/etc. can be passed in? @@ -37,10 +44,6 @@ app.add_middleware( socket_io = SocketIO(app) -# initialize config -# this is a module global -app_config = InvokeAIAppConfig() - # Add startup event to load dependencies @app.on_event("startup") async def startup_event(): @@ -75,6 +78,10 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") +app.include_router(boards.boards_router, prefix="/api") + +app.include_router(board_images.board_images_router, prefix="/api") + # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? def custom_openapi(): @@ -113,6 +120,22 @@ def custom_openapi(): invoker_schema["output"] = outputs_ref + from invokeai.backend.model_management.models import get_model_config_enums + for model_config_format_enum in set(get_model_config_enums()): + name = model_config_format_enum.__qualname__ + + if name in openapi_schema["components"]["schemas"]: + # print(f"Config with name {name} already defined") + continue + + # "BaseModelType":{"title":"BaseModelType","description":"An enumeration.","enum":["sd-1","sd-2"],"type":"string"} + openapi_schema["components"]["schemas"][name] = dict( + title=name, + description="An enumeration.", + type="string", + enum=list(v.value for v in model_config_format_enum), + ) + app.openapi_schema = openapi_schema return app.openapi_schema @@ -120,8 +143,7 @@ def custom_openapi(): app.openapi = custom_openapi # Override API doc favicons -app.mount("/static", StaticFiles(directory="static/dream_web"), name="static") - +app.mount("/static", StaticFiles(directory=Path(web_dir.__path__[0], 'static/dream_web')), name="static") @app.get("/docs", include_in_schema=False) def overridden_swagger(): @@ -142,10 +164,11 @@ def overridden_redoc(): # Must mount *after* the other routes else it borks em -app.mount( - "/", StaticFiles(directory="invokeai/frontend/web/dist", html=True), name="ui" -) - +app.mount("/", + StaticFiles(directory=Path(web_dir.__path__[0],"dist"), + html=True + ), name="ui" + ) def invoke_api(): # Start our own event loop for eventing usage diff --git a/invokeai/app/cli_app.py b/invokeai/app/cli_app.py index de543d2d85..07193c8500 100644 --- a/invokeai/app/cli_app.py +++ b/invokeai/app/cli_app.py @@ -6,38 +6,53 @@ import re import shlex import sys import time -from typing import ( - Union, - get_type_hints, -) +from typing import Union, get_type_hints from pydantic import BaseModel, ValidationError from pydantic.fields import Field + +# This should come early so that the logger can pick up its configuration options +from .services.config import InvokeAIAppConfig +from invokeai.backend.util.logging import InvokeAILogger +config = InvokeAIAppConfig.get_config() +config.parse_args() +logger = InvokeAILogger().getLogger(config=config) + +from invokeai.app.services.board_image_record_storage import ( + SqliteBoardImageRecordStorage, +) +from invokeai.app.services.board_images import ( + BoardImagesService, + BoardImagesServiceDependencies, +) +from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage +from invokeai.app.services.boards import BoardService, BoardServiceDependencies from invokeai.app.services.image_record_storage import SqliteImageRecordStorage -from invokeai.app.services.images import ImageService +from invokeai.app.services.images import ImageService, ImageServiceDependencies from invokeai.app.services.metadata import CoreMetadataService +from invokeai.app.services.resource_name import SimpleNameService from invokeai.app.services.urls import LocalUrlService - - -import invokeai.backend.util.logging as logger -from .services.default_graphs import create_system_graphs +from .services.default_graphs import (default_text_to_image_graph_id, + create_system_graphs) from .services.latent_storage import DiskLatentsStorage, ForwardCacheLatentsStorage -from .cli.commands import BaseCommand, CliContext, ExitCli, add_graph_parsers, add_parsers, SortedHelpFormatter +from .cli.commands import (BaseCommand, CliContext, ExitCli, + SortedHelpFormatter, add_graph_parsers, add_parsers) from .cli.completer import set_autocompleter from .invocations.baseinvocation import BaseInvocation from .services.events import EventServiceBase -from .services.model_manager_initializer import get_model_manager -from .services.restoration_services import RestorationServices -from .services.graph import Edge, EdgeConnection, GraphExecutionState, GraphInvocation, LibraryGraph, are_connection_types_compatible -from .services.default_graphs import default_text_to_image_graph_id +from .services.graph import (Edge, EdgeConnection, GraphExecutionState, + GraphInvocation, LibraryGraph, + are_connection_types_compatible) from .services.image_file_storage import DiskImageFileStorage from .services.invocation_queue import MemoryInvocationQueue from .services.invocation_services import InvocationServices from .services.invoker import Invoker +from .services.model_manager_service import ModelManagerService from .services.processor import DefaultInvocationProcessor +from .services.restoration_services import RestorationServices from .services.sqlite import SqliteItemStorage -from .services.config import get_invokeai_config + class CliCommand(BaseModel): command: Union[BaseCommand.get_commands() + BaseInvocation.get_invocations()] = Field(discriminator="type") # type: ignore @@ -46,7 +61,6 @@ class CliCommand(BaseModel): class InvalidArgs(Exception): pass - def add_invocation_args(command_parser): # Add linking capability command_parser.add_argument( @@ -190,14 +204,7 @@ def invoke_all(context: CliContext): raise SessionError() - -logger = logger.InvokeAILogger.getLogger() - - def invoke_cli(): - # this gets the basic configuration - config = get_invokeai_config() - # get the optional list of invocations to execute on the command line parser = config.get_parser() parser.add_argument('commands',nargs='*') @@ -208,8 +215,8 @@ def invoke_cli(): if infile := config.from_file: sys.stdin = open(infile,"r") - model_manager = get_model_manager(config,logger=logger) - + model_manager = ModelManagerService(config,logger) + events = EventServiceBase() output_folder = config.output_path @@ -217,7 +224,8 @@ def invoke_cli(): if config.use_memory_db: db_location = ":memory:" else: - db_location = os.path.join(output_folder, "invokeai.db") + db_location = config.db_path + db_location.parent.mkdir(parents=True,exist_ok=True) logger.info(f'InvokeAI database location is "{db_location}"') @@ -229,21 +237,51 @@ def invoke_cli(): metadata = CoreMetadataService() image_record_storage = SqliteImageRecordStorage(db_location) image_file_storage = DiskImageFileStorage(f"{output_folder}/images") + names = SimpleNameService() - images = ImageService( - image_record_storage=image_record_storage, - image_file_storage=image_file_storage, - metadata=metadata, - url=urls, - logger=logger, - graph_execution_manager=graph_execution_manager, + board_record_storage = SqliteBoardRecordStorage(db_location) + board_image_record_storage = SqliteBoardImageRecordStorage(db_location) + + boards = BoardService( + services=BoardServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) ) + board_images = BoardImagesService( + services=BoardImagesServiceDependencies( + board_image_record_storage=board_image_record_storage, + board_record_storage=board_record_storage, + image_record_storage=image_record_storage, + url=urls, + logger=logger, + ) + ) + + images = ImageService( + services=ImageServiceDependencies( + board_image_record_storage=board_image_record_storage, + image_record_storage=image_record_storage, + image_file_storage=image_file_storage, + metadata=metadata, + url=urls, + logger=logger, + names=names, + graph_execution_manager=graph_execution_manager, + ) + ) + services = InvocationServices( model_manager=model_manager, events=events, latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f'{output_folder}/latents')), images=images, + boards=boards, + board_images=board_images, queue=MemoryInvocationQueue(), graph_library=SqliteItemStorage[LibraryGraph]( filename=db_location, table_name="graphs" @@ -254,9 +292,11 @@ def invoke_cli(): logger=logger, configuration=config, ) + system_graphs = create_system_graphs(services.graph_library) system_graph_names = set([g.name for g in system_graphs]) + set_autocompleter(services) invoker = Invoker(services) session: GraphExecutionState = invoker.create_execution_state() diff --git a/invokeai/app/invocations/compel.py b/invokeai/app/invocations/compel.py index 076ce81021..8c6b23944c 100644 --- a/invokeai/app/invocations/compel.py +++ b/invokeai/app/invocations/compel.py @@ -1,19 +1,22 @@ from typing import Literal, Optional, Union from pydantic import BaseModel, Field +from contextlib import ExitStack +import re -from invokeai.app.invocations.util.choose_model import choose_model from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig +from .model import ClipField -from ...backend.util.devices import choose_torch_device, torch_dtype +from ...backend.util.devices import torch_dtype from ...backend.stable_diffusion.diffusion import InvokeAIDiffuserComponent -from ...backend.stable_diffusion.textual_inversion_manager import TextualInversionManager +from ...backend.model_management import BaseModelType, ModelType, SubModelType +from ...backend.model_management.lora import ModelPatcher from compel import Compel from compel.prompt_parser import ( Blend, CrossAttentionControlSubstitute, FlattenedPrompt, - Fragment, + Fragment, Conjunction, ) @@ -39,7 +42,7 @@ class CompelInvocation(BaseInvocation): type: Literal["compel"] = "compel" prompt: str = Field(default="", description="Prompt") - model: str = Field(default="", description="Model to use") + clip: ClipField = Field(None, description="Clip to use") # Schema customisation class Config(InvocationConfig): @@ -55,87 +58,90 @@ class CompelInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> CompelOutput: - # TODO: load without model - model = choose_model(context.services.model_manager, self.model) - pipeline = model["model"] - tokenizer = pipeline.tokenizer - text_encoder = pipeline.text_encoder - - # TODO: global? input? - #use_full_precision = precision == "float32" or precision == "autocast" - #use_full_precision = False - - # TODO: redo TI when separate model loding implemented - #textual_inversion_manager = TextualInversionManager( - # tokenizer=tokenizer, - # text_encoder=text_encoder, - # full_precision=use_full_precision, - #) - - def load_huggingface_concepts(concepts: list[str]): - pipeline.textual_inversion_manager.load_huggingface_concepts(concepts) - - # apply the concepts library to the prompt - prompt_str = pipeline.textual_inversion_manager.hf_concepts_library.replace_concepts_with_triggers( - self.prompt, - lambda concepts: load_huggingface_concepts(concepts), - pipeline.textual_inversion_manager.get_all_trigger_strings(), + tokenizer_info = context.services.model_manager.get_model( + **self.clip.tokenizer.dict(), ) - - # lazy-load any deferred textual inversions. - # this might take a couple of seconds the first time a textual inversion is used. - pipeline.textual_inversion_manager.create_deferred_token_ids_for_any_trigger_terms( - prompt_str + text_encoder_info = context.services.model_manager.get_model( + **self.clip.text_encoder.dict(), ) + with tokenizer_info as orig_tokenizer,\ + text_encoder_info as text_encoder: - compel = Compel( - tokenizer=tokenizer, - text_encoder=text_encoder, - textual_inversion_manager=pipeline.textual_inversion_manager, - dtype_for_device_getter=torch_dtype, - truncate_long_prompts=True, # TODO: - ) + loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras] - # TODO: support legacy blend? + ti_list = [] + for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt): + name = trigger[1:-1] + try: + ti_list.append( + context.services.model_manager.get_model( + model_name=name, + base_model=self.clip.text_encoder.base_model, + model_type=ModelType.TextualInversion, + ).context.model + ) + except Exception: + #print(e) + #import traceback + #print(traceback.format_exc()) + print(f"Warn: trigger: \"{trigger}\" not found") - conjunction = Compel.parse_prompt_string(prompt_str) - prompt: Union[FlattenedPrompt, Blend] = conjunction.prompts[0] + with ModelPatcher.apply_lora_text_encoder(text_encoder, loras),\ + ModelPatcher.apply_ti(orig_tokenizer, text_encoder, ti_list) as (tokenizer, ti_manager): - if context.services.configuration.log_tokenization: - log_tokenization_for_prompt_object(prompt, tokenizer) + compel = Compel( + tokenizer=tokenizer, + text_encoder=text_encoder, + textual_inversion_manager=ti_manager, + dtype_for_device_getter=torch_dtype, + truncate_long_prompts=True, # TODO: + ) + + conjunction = Compel.parse_prompt_string(self.prompt) + prompt: Union[FlattenedPrompt, Blend] = conjunction.prompts[0] - c, options = compel.build_conditioning_tensor_for_prompt_object(prompt) + if context.services.configuration.log_tokenization: + log_tokenization_for_prompt_object(prompt, tokenizer) - # TODO: long prompt support - #if not self.truncate_long_prompts: - # [c, uc] = compel.pad_conditioning_tensors_to_same_length([c, uc]) + c, options = compel.build_conditioning_tensor_for_prompt_object(prompt) + + # TODO: long prompt support + #if not self.truncate_long_prompts: + # [c, uc] = compel.pad_conditioning_tensors_to_same_length([c, uc]) + ec = InvokeAIDiffuserComponent.ExtraConditioningInfo( + tokens_count_including_eos_bos=get_max_token_count(tokenizer, conjunction), + cross_attention_control_args=options.get("cross_attention_control", None), + ) + + conditioning_name = f"{context.graph_execution_state_id}_{self.id}_conditioning" - ec = InvokeAIDiffuserComponent.ExtraConditioningInfo( - tokens_count_including_eos_bos=get_max_token_count(tokenizer, prompt), - cross_attention_control_args=options.get("cross_attention_control", None), - ) + # TODO: hacky but works ;D maybe rename latents somehow? + context.services.latents.save(conditioning_name, (c, ec)) - conditioning_name = f"{context.graph_execution_state_id}_{self.id}_conditioning" - - # TODO: hacky but works ;D maybe rename latents somehow? - context.services.latents.save(conditioning_name, (c, ec)) - - return CompelOutput( - conditioning=ConditioningField( - conditioning_name=conditioning_name, - ), - ) + return CompelOutput( + conditioning=ConditioningField( + conditioning_name=conditioning_name, + ), + ) def get_max_token_count( - tokenizer, prompt: Union[FlattenedPrompt, Blend], truncate_if_too_long=False + tokenizer, prompt: Union[FlattenedPrompt, Blend, Conjunction], truncate_if_too_long=False ) -> int: if type(prompt) is Blend: blend: Blend = prompt return max( [ - get_max_token_count(tokenizer, c, truncate_if_too_long) - for c in blend.prompts + get_max_token_count(tokenizer, p, truncate_if_too_long) + for p in blend.prompts + ] + ) + elif type(prompt) is Conjunction: + conjunction: Conjunction = prompt + return sum( + [ + get_max_token_count(tokenizer, p, truncate_if_too_long) + for p in conjunction.prompts ] ) else: @@ -170,6 +176,22 @@ def get_tokens_for_prompt_object( return tokens +def log_tokenization_for_conjunction( + c: Conjunction, tokenizer, display_label_prefix=None +): + display_label_prefix = display_label_prefix or "" + for i, p in enumerate(c.prompts): + if len(c.prompts)>1: + this_display_label_prefix = f"{display_label_prefix}(conjunction part {i + 1}, weight={c.weights[i]})" + else: + this_display_label_prefix = display_label_prefix + log_tokenization_for_prompt_object( + p, + tokenizer, + display_label_prefix=this_display_label_prefix + ) + + def log_tokenization_for_prompt_object( p: Union[Blend, FlattenedPrompt], tokenizer, display_label_prefix=None ): diff --git a/invokeai/app/invocations/controlnet_image_processors.py b/invokeai/app/invocations/controlnet_image_processors.py index 187784b29e..8cfe35598d 100644 --- a/invokeai/app/invocations/controlnet_image_processors.py +++ b/invokeai/app/invocations/controlnet_image_processors.py @@ -1,19 +1,22 @@ -# InvokeAI nodes for ControlNet image preprocessors +# Invocations for ControlNet image preprocessors # initial implementation by Gregg Helt, 2023 # heavily leverages controlnet_aux package: https://github.com/patrickvonplaten/controlnet_aux +from builtins import float, bool +import cv2 import numpy as np -from typing import Literal, Optional, Union, List +from typing import Literal, Optional, Union, List, Dict from PIL import Image, ImageFilter, ImageOps -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, validator -from ..models.image import ImageField, ImageType, ImageCategory +from ..models.image import ImageField, ImageCategory, ResourceOrigin from .baseinvocation import ( BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig, ) + from controlnet_aux import ( CannyDetector, HEDdetector, @@ -27,8 +30,13 @@ from controlnet_aux import ( ContentShuffleDetector, ZoeDetector, MediapipeFaceDetector, + SamDetector, + LeresDetector, ) +from controlnet_aux.util import HWC3, ade_palette + + from .image import ImageOutput, PILInvocationConfig CONTROLNET_DEFAULT_MODELS = [ @@ -92,19 +100,43 @@ CONTROLNET_DEFAULT_MODELS = [ ] CONTROLNET_NAME_VALUES = Literal[tuple(CONTROLNET_DEFAULT_MODELS)] +CONTROLNET_MODE_VALUES = Literal[tuple(["balanced", "more_prompt", "more_control", "unbalanced"])] +# crop and fill options not ready yet +# CONTROLNET_RESIZE_VALUES = Literal[tuple(["just_resize", "crop_resize", "fill_resize"])] + class ControlField(BaseModel): - image: ImageField = Field(default=None, description="processed image") - control_model: Optional[str] = Field(default=None, description="control model used") - control_weight: Optional[float] = Field(default=1, description="weight given to controlnet") + image: ImageField = Field(default=None, description="The control image") + control_model: Optional[str] = Field(default=None, description="The ControlNet model to use") + # control_weight: Optional[float] = Field(default=1, description="weight given to controlnet") + control_weight: Union[float, List[float]] = Field(default=1, description="The weight given to the ControlNet") begin_step_percent: float = Field(default=0, ge=0, le=1, - description="% of total steps at which controlnet is first applied") + description="When the ControlNet is first applied (% of total steps)") end_step_percent: float = Field(default=1, ge=0, le=1, - description="% of total steps at which controlnet is last applied") + description="When the ControlNet is last applied (% of total steps)") + control_mode: CONTROLNET_MODE_VALUES = Field(default="balanced", description="The control mode to use") + # resize_mode: CONTROLNET_RESIZE_VALUES = Field(default="just_resize", description="The resize mode to use") + @validator("control_weight") + def abs_le_one(cls, v): + """validate that all abs(values) are <=1""" + if isinstance(v, list): + for i in v: + if abs(i) > 1: + raise ValueError('all abs(control_weight) must be <= 1') + else: + if abs(v) > 1: + raise ValueError('abs(control_weight) must be <= 1') + return v class Config: schema_extra = { - "required": ["image", "control_model", "control_weight", "begin_step_percent", "end_step_percent"] + "required": ["image", "control_model", "control_weight", "begin_step_percent", "end_step_percent"], + "ui": { + "type_hints": { + "control_weight": "float", + # "control_weight": "number", + } + } } @@ -112,7 +144,7 @@ class ControlOutput(BaseInvocationOutput): """node output for ControlNet info""" # fmt: off type: Literal["control_output"] = "control_output" - control: ControlField = Field(default=None, description="The control info dict") + control: ControlField = Field(default=None, description="The control info") # fmt: on @@ -121,20 +153,32 @@ class ControlNetInvocation(BaseInvocation): # fmt: off type: Literal["controlnet"] = "controlnet" # Inputs - image: ImageField = Field(default=None, description="image to process") + image: ImageField = Field(default=None, description="The control image") control_model: CONTROLNET_NAME_VALUES = Field(default="lllyasviel/sd-controlnet-canny", description="control model used") - control_weight: float = Field(default=1.0, ge=0, le=1, description="weight given to controlnet") - # TODO: add support in backend core for begin_step_percent, end_step_percent, guess_mode + control_weight: Union[float, List[float]] = Field(default=1.0, description="The weight given to the ControlNet") begin_step_percent: float = Field(default=0, ge=0, le=1, - description="% of total steps at which controlnet is first applied") + description="When the ControlNet is first applied (% of total steps)") end_step_percent: float = Field(default=1, ge=0, le=1, - description="% of total steps at which controlnet is last applied") + description="When the ControlNet is last applied (% of total steps)") + control_mode: CONTROLNET_MODE_VALUES = Field(default="balanced", description="The control mode used") # fmt: on + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["latents"], + "type_hints": { + "model": "model", + "control": "control", + # "cfg_scale": "float", + "cfg_scale": "number", + "control_weight": "float", + } + }, + } def invoke(self, context: InvocationContext) -> ControlOutput: - return ControlOutput( control=ControlField( image=self.image, @@ -142,17 +186,18 @@ class ControlNetInvocation(BaseInvocation): control_weight=self.control_weight, begin_step_percent=self.begin_step_percent, end_step_percent=self.end_step_percent, + control_mode=self.control_mode, ), ) -# TODO: move image processors to separate file (image_analysis.py + class ImageProcessorInvocation(BaseInvocation, PILInvocationConfig): """Base class for invocations that preprocess images for ControlNet""" # fmt: off type: Literal["image_processor"] = "image_processor" # Inputs - image: ImageField = Field(default=None, description="image to process") + image: ImageField = Field(default=None, description="The image to process") # fmt: on @@ -161,10 +206,7 @@ class ImageProcessorInvocation(BaseInvocation, PILInvocationConfig): return image def invoke(self, context: InvocationContext) -> ImageOutput: - - raw_image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + raw_image = context.services.images.get_pil_image(self.image.image_name) # image type should be PIL.PngImagePlugin.PngImageFile ? processed_image = self.run_processor(raw_image) @@ -177,18 +219,15 @@ class ImageProcessorInvocation(BaseInvocation, PILInvocationConfig): # so for now setting image_type to RESULT instead of INTERMEDIATE so will get saved in gallery image_dto = context.services.images.create( image=processed_image, - image_type=ImageType.RESULT, - image_category=ImageCategory.GENERAL, + image_origin=ResourceOrigin.INTERNAL, + image_category=ImageCategory.CONTROL, session_id=context.graph_execution_state_id, node_id=self.id, is_intermediate=self.is_intermediate ) """Builds an ImageOutput and its ImageField""" - processed_image_field = ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ) + processed_image_field = ImageField(image_name=image_dto.image_name) return ImageOutput( image=processed_image_field, # width=processed_image.width, @@ -204,8 +243,8 @@ class CannyImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfi # fmt: off type: Literal["canny_image_processor"] = "canny_image_processor" # Input - low_threshold: float = Field(default=100, ge=0, description="low threshold of Canny pixel gradient") - high_threshold: float = Field(default=200, ge=0, description="high threshold of Canny pixel gradient") + low_threshold: int = Field(default=100, ge=0, le=255, description="The low threshold of the Canny pixel gradient (0-255)") + high_threshold: int = Field(default=200, ge=0, le=255, description="The high threshold of the Canny pixel gradient (0-255)") # fmt: on def run_processor(self, image): @@ -214,16 +253,16 @@ class CannyImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfi return processed_image -class HedImageprocessorInvocation(ImageProcessorInvocation, PILInvocationConfig): +class HedImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig): """Applies HED edge detection to image""" # fmt: off type: Literal["hed_image_processor"] = "hed_image_processor" # Inputs - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") # safe not supported in controlnet_aux v0.0.3 # safe: bool = Field(default=False, description="whether to use safe mode") - scribble: bool = Field(default=False, description="whether to use scribble mode") + scribble: bool = Field(default=False, description="Whether to use scribble mode") # fmt: on def run_processor(self, image): @@ -243,9 +282,9 @@ class LineartImageProcessorInvocation(ImageProcessorInvocation, PILInvocationCon # fmt: off type: Literal["lineart_image_processor"] = "lineart_image_processor" # Inputs - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") - coarse: bool = Field(default=False, description="whether to use coarse mode") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") + coarse: bool = Field(default=False, description="Whether to use coarse mode") # fmt: on def run_processor(self, image): @@ -262,8 +301,8 @@ class LineartAnimeImageProcessorInvocation(ImageProcessorInvocation, PILInvocati # fmt: off type: Literal["lineart_anime_image_processor"] = "lineart_anime_image_processor" # Inputs - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") # fmt: on def run_processor(self, image): @@ -280,9 +319,9 @@ class OpenposeImageProcessorInvocation(ImageProcessorInvocation, PILInvocationCo # fmt: off type: Literal["openpose_image_processor"] = "openpose_image_processor" # Inputs - hand_and_face: bool = Field(default=False, description="whether to use hands and face mode") - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") + hand_and_face: bool = Field(default=False, description="Whether to use hands and face mode") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") # fmt: on def run_processor(self, image): @@ -300,8 +339,8 @@ class MidasDepthImageProcessorInvocation(ImageProcessorInvocation, PILInvocation # fmt: off type: Literal["midas_depth_image_processor"] = "midas_depth_image_processor" # Inputs - a_mult: float = Field(default=2.0, ge=0, description="Midas parameter a = amult * PI") - bg_th: float = Field(default=0.1, ge=0, description="Midas parameter bg_th") + a_mult: float = Field(default=2.0, ge=0, description="Midas parameter `a_mult` (a = a_mult * PI)") + bg_th: float = Field(default=0.1, ge=0, description="Midas parameter `bg_th`") # depth_and_normal not supported in controlnet_aux v0.0.3 # depth_and_normal: bool = Field(default=False, description="whether to use depth and normal mode") # fmt: on @@ -322,8 +361,8 @@ class NormalbaeImageProcessorInvocation(ImageProcessorInvocation, PILInvocationC # fmt: off type: Literal["normalbae_image_processor"] = "normalbae_image_processor" # Inputs - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") # fmt: on def run_processor(self, image): @@ -339,10 +378,10 @@ class MlsdImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig # fmt: off type: Literal["mlsd_image_processor"] = "mlsd_image_processor" # Inputs - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") - thr_v: float = Field(default=0.1, ge=0, description="MLSD parameter thr_v") - thr_d: float = Field(default=0.1, ge=0, description="MLSD parameter thr_d") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") + thr_v: float = Field(default=0.1, ge=0, description="MLSD parameter `thr_v`") + thr_d: float = Field(default=0.1, ge=0, description="MLSD parameter `thr_d`") # fmt: on def run_processor(self, image): @@ -360,10 +399,10 @@ class PidiImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig # fmt: off type: Literal["pidi_image_processor"] = "pidi_image_processor" # Inputs - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") - safe: bool = Field(default=False, description="whether to use safe mode") - scribble: bool = Field(default=False, description="whether to use scribble mode") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") + safe: bool = Field(default=False, description="Whether to use safe mode") + scribble: bool = Field(default=False, description="Whether to use scribble mode") # fmt: on def run_processor(self, image): @@ -381,11 +420,11 @@ class ContentShuffleImageProcessorInvocation(ImageProcessorInvocation, PILInvoca # fmt: off type: Literal["content_shuffle_image_processor"] = "content_shuffle_image_processor" # Inputs - detect_resolution: int = Field(default=512, ge=0, description="pixel resolution for edge detection") - image_resolution: int = Field(default=512, ge=0, description="pixel resolution for output image") - h: Union[int | None] = Field(default=512, ge=0, description="content shuffle h parameter") - w: Union[int | None] = Field(default=512, ge=0, description="content shuffle w parameter") - f: Union[int | None] = Field(default=256, ge=0, description="cont") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") + h: Union[int, None] = Field(default=512, ge=0, description="Content shuffle `h` parameter") + w: Union[int, None] = Field(default=512, ge=0, description="Content shuffle `w` parameter") + f: Union[int, None] = Field(default=256, ge=0, description="Content shuffle `f` parameter") # fmt: on def run_processor(self, image): @@ -418,11 +457,109 @@ class MediapipeFaceProcessorInvocation(ImageProcessorInvocation, PILInvocationCo # fmt: off type: Literal["mediapipe_face_processor"] = "mediapipe_face_processor" # Inputs - max_faces: int = Field(default=1, ge=1, description="maximum number of faces to detect") - min_confidence: float = Field(default=0.5, ge=0, le=1, description="minimum confidence for face detection") + max_faces: int = Field(default=1, ge=1, description="Maximum number of faces to detect") + min_confidence: float = Field(default=0.5, ge=0, le=1, description="Minimum confidence for face detection") # fmt: on def run_processor(self, image): + # MediaPipeFaceDetector throws an error if image has alpha channel + # so convert to RGB if needed + if image.mode == 'RGBA': + image = image.convert('RGB') mediapipe_face_processor = MediapipeFaceDetector() processed_image = mediapipe_face_processor(image, max_faces=self.max_faces, min_confidence=self.min_confidence) return processed_image + +class LeresImageProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig): + """Applies leres processing to image""" + # fmt: off + type: Literal["leres_image_processor"] = "leres_image_processor" + # Inputs + thr_a: float = Field(default=0, description="Leres parameter `thr_a`") + thr_b: float = Field(default=0, description="Leres parameter `thr_b`") + boost: bool = Field(default=False, description="Whether to use boost mode") + detect_resolution: int = Field(default=512, ge=0, description="The pixel resolution for detection") + image_resolution: int = Field(default=512, ge=0, description="The pixel resolution for the output image") + # fmt: on + + def run_processor(self, image): + leres_processor = LeresDetector.from_pretrained("lllyasviel/Annotators") + processed_image = leres_processor(image, + thr_a=self.thr_a, + thr_b=self.thr_b, + boost=self.boost, + detect_resolution=self.detect_resolution, + image_resolution=self.image_resolution) + return processed_image + + +class TileResamplerProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig): + + # fmt: off + type: Literal["tile_image_processor"] = "tile_image_processor" + # Inputs + #res: int = Field(default=512, ge=0, le=1024, description="The pixel resolution for each tile") + down_sampling_rate: float = Field(default=1.0, ge=1.0, le=8.0, description="Down sampling rate") + # fmt: on + + # tile_resample copied from sd-webui-controlnet/scripts/processor.py + def tile_resample(self, + np_img: np.ndarray, + res=512, # never used? + down_sampling_rate=1.0, + ): + np_img = HWC3(np_img) + if down_sampling_rate < 1.1: + return np_img + H, W, C = np_img.shape + H = int(float(H) / float(down_sampling_rate)) + W = int(float(W) / float(down_sampling_rate)) + np_img = cv2.resize(np_img, (W, H), interpolation=cv2.INTER_AREA) + return np_img + + def run_processor(self, img): + np_img = np.array(img, dtype=np.uint8) + processed_np_image = self.tile_resample(np_img, + #res=self.tile_size, + down_sampling_rate=self.down_sampling_rate + ) + processed_image = Image.fromarray(processed_np_image) + return processed_image + + + + +class SegmentAnythingProcessorInvocation(ImageProcessorInvocation, PILInvocationConfig): + """Applies segment anything processing to image""" + # fmt: off + type: Literal["segment_anything_processor"] = "segment_anything_processor" + # fmt: on + + def run_processor(self, image): + # segment_anything_processor = SamDetector.from_pretrained("ybelkada/segment-anything", subfolder="checkpoints") + segment_anything_processor = SamDetectorReproducibleColors.from_pretrained("ybelkada/segment-anything", subfolder="checkpoints") + np_img = np.array(image, dtype=np.uint8) + processed_image = segment_anything_processor(np_img) + return processed_image + +class SamDetectorReproducibleColors(SamDetector): + + # overriding SamDetector.show_anns() method to use reproducible colors for segmentation image + # base class show_anns() method randomizes colors, + # which seems to also lead to non-reproducible image generation + # so using ADE20k color palette instead + def show_anns(self, anns: List[Dict]): + if len(anns) == 0: + return + sorted_anns = sorted(anns, key=(lambda x: x['area']), reverse=True) + h, w = anns[0]['segmentation'].shape + final_img = Image.fromarray(np.zeros((h, w, 3), dtype=np.uint8), mode="RGB") + palette = ade_palette() + for i, ann in enumerate(sorted_anns): + m = ann['segmentation'] + img = np.empty((m.shape[0], m.shape[1], 3), dtype=np.uint8) + # doing modulo just in case number of annotated regions exceeds number of colors in palette + ann_color = palette[i % len(palette)] + img[:, :] = ann_color + final_img.paste(Image.fromarray(img, mode="RGB"), (0, 0), Image.fromarray(np.uint8(m * 255))) + return np.array(final_img, dtype=np.uint8) diff --git a/invokeai/app/invocations/cv.py b/invokeai/app/invocations/cv.py index 5e9fe088b5..dd0ab4d027 100644 --- a/invokeai/app/invocations/cv.py +++ b/invokeai/app/invocations/cv.py @@ -7,7 +7,7 @@ import numpy from PIL import Image, ImageOps from pydantic import BaseModel, Field -from invokeai.app.models.image import ImageCategory, ImageField, ImageType +from invokeai.app.models.image import ImageCategory, ImageField, ResourceOrigin from .baseinvocation import BaseInvocation, InvocationContext, InvocationConfig from .image import ImageOutput @@ -36,12 +36,8 @@ class CvInpaintInvocation(BaseInvocation, CvInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) - mask = context.services.images.get_pil_image( - self.mask.image_type, self.mask.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) + mask = context.services.images.get_pil_image(self.mask.image_name) # Convert to cv image/mask # TODO: consider making these utility functions @@ -57,7 +53,7 @@ class CvInpaintInvocation(BaseInvocation, CvInvocationConfig): image_dto = context.services.images.create( image=image_inpainted, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -65,10 +61,7 @@ class CvInpaintInvocation(BaseInvocation, CvInvocationConfig): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) diff --git a/invokeai/app/invocations/generate.py b/invokeai/app/invocations/generate.py index 0385c6a9f0..7135e66a02 100644 --- a/invokeai/app/invocations/generate.py +++ b/invokeai/app/invocations/generate.py @@ -3,23 +3,27 @@ from functools import partial from typing import Literal, Optional, Union, get_args -import numpy as np -from diffusers import ControlNetModel -from torch import Tensor import torch - +from diffusers import ControlNetModel from pydantic import BaseModel, Field -from invokeai.app.models.image import ColorField, ImageField, ImageType -from invokeai.app.invocations.util.choose_model import choose_model -from invokeai.app.models.image import ImageCategory, ImageType +from invokeai.app.models.image import (ColorField, ImageCategory, ImageField, + ResourceOrigin) from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.backend.generator.inpaint import infill_methods -from .baseinvocation import BaseInvocation, InvocationContext, InvocationConfig -from .image import ImageOutput -from ...backend.generator import Txt2Img, Img2Img, Inpaint, InvokeAIGenerator + +from ...backend.generator import Inpaint, InvokeAIGenerator from ...backend.stable_diffusion import PipelineIntermediateState from ..util.step_callback import stable_diffusion_step_callback +from .baseinvocation import BaseInvocation, InvocationConfig, InvocationContext +from .image import ImageOutput + +import re +from ...backend.model_management.lora import ModelPatcher +from ...backend.stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline +from .model import UNetField, VaeField +from .compel import ConditioningField +from contextlib import contextmanager, ExitStack, ContextDecorator SAMPLER_NAME_VALUES = Literal[tuple(InvokeAIGenerator.schedulers())] INFILL_METHODS = Literal[tuple(infill_methods())] @@ -28,119 +32,48 @@ DEFAULT_INFILL_METHOD = ( ) -class SDImageInvocation(BaseModel): - """Helper class to provide all Stable Diffusion raster image invocations with additional config""" +from .latent import get_scheduler - # Schema customisation - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["stable-diffusion", "image"], - "type_hints": { - "model": "model", - }, - }, - } +class OldModelContext(ContextDecorator): + model: StableDiffusionGeneratorPipeline + + def __init__(self, model): + self.model = model + + def __enter__(self): + return self.model + + def __exit__(self, *exc): + return False + +class OldModelInfo: + name: str + hash: str + context: OldModelContext + + def __init__(self, name: str, hash: str, model: StableDiffusionGeneratorPipeline): + self.name = name + self.hash = hash + self.context = OldModelContext( + model=model, + ) -# Text to image -class TextToImageInvocation(BaseInvocation, SDImageInvocation): - """Generates an image using text2img.""" +class InpaintInvocation(BaseInvocation): + """Generates an image using inpaint.""" - type: Literal["txt2img"] = "txt2img" + type: Literal["inpaint"] = "inpaint" - # Inputs - # TODO: consider making prompt optional to enable providing prompt through a link - # fmt: off - prompt: Optional[str] = Field(description="The prompt to generate an image from") + positive_conditioning: Optional[ConditioningField] = Field(description="Positive conditioning for generation") + negative_conditioning: Optional[ConditioningField] = Field(description="Negative conditioning for generation") seed: int = Field(ge=0, le=SEED_MAX, description="The seed to use (omit for random)", default_factory=get_random_seed) steps: int = Field(default=30, gt=0, description="The number of steps to use to generate the image") width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting image", ) height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting image", ) cfg_scale: float = Field(default=7.5, ge=1, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="euler", description="The scheduler to use" ) - model: str = Field(default="", description="The model to use (currently ignored)") - progress_images: bool = Field(default=False, description="Whether or not to produce progress images during generation", ) - control_model: Optional[str] = Field(default=None, description="The control model to use") - control_image: Optional[ImageField] = Field(default=None, description="The processed control image") - # fmt: on - - # TODO: pass this an emitter method or something? or a session for dispatching? - def dispatch_progress( - self, - context: InvocationContext, - source_node_id: str, - intermediate_state: PipelineIntermediateState, - ) -> None: - stable_diffusion_step_callback( - context=context, - intermediate_state=intermediate_state, - node=self.dict(), - source_node_id=source_node_id, - ) - - def invoke(self, context: InvocationContext) -> ImageOutput: - # Handle invalid model parameter - model = choose_model(context.services.model_manager, self.model) - - # loading controlnet image (currently requires pre-processed image) - control_image = ( - None if self.control_image is None - else context.services.images.get( - self.control_image.image_type, self.control_image.image_name - ) - ) - # loading controlnet model - if (self.control_model is None or self.control_model==''): - control_model = None - else: - # FIXME: change this to dropdown menu? - # FIXME: generalize so don't have to hardcode torch_dtype and device - control_model = ControlNetModel.from_pretrained(self.control_model, - torch_dtype=torch.float16).to("cuda") - - # Get the source node id (we are invoking the prepared node) - graph_execution_state = context.services.graph_execution_manager.get( - context.graph_execution_state_id - ) - source_node_id = graph_execution_state.prepared_source_mapping[self.id] - - txt2img = Txt2Img(model, control_model=control_model) - outputs = txt2img.generate( - prompt=self.prompt, - step_callback=partial(self.dispatch_progress, context, source_node_id), - control_image=control_image, - **self.dict( - exclude={"prompt", "control_image" } - ), # Shorthand for passing all of the parameters above manually - ) - # Outputs is an infinite iterator that will return a new InvokeAIGeneratorOutput object - # each time it is called. We only need the first one. - generate_output = next(outputs) - - image_dto = context.services.images.create( - image=generate_output.image, - image_type=ImageType.RESULT, - image_category=ImageCategory.GENERAL, - session_id=context.graph_execution_state_id, - node_id=self.id, - is_intermediate=self.is_intermediate, - ) - - return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), - width=image_dto.width, - height=image_dto.height, - ) - - -class ImageToImageInvocation(TextToImageInvocation): - """Generates an image using img2img.""" - - type: Literal["img2img"] = "img2img" + unet: UNetField = Field(default=None, description="UNet model") + vae: VaeField = Field(default=None, description="Vae model") # Inputs image: Union[ImageField, None] = Field(description="The input image") @@ -152,77 +85,6 @@ class ImageToImageInvocation(TextToImageInvocation): description="Whether or not the result should be fit to the aspect ratio of the input image", ) - def dispatch_progress( - self, - context: InvocationContext, - source_node_id: str, - intermediate_state: PipelineIntermediateState, - ) -> None: - stable_diffusion_step_callback( - context=context, - intermediate_state=intermediate_state, - node=self.dict(), - source_node_id=source_node_id, - ) - - def invoke(self, context: InvocationContext) -> ImageOutput: - image = ( - None - if self.image is None - else context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) - ) - - if self.fit: - image = image.resize((self.width, self.height)) - - # Handle invalid model parameter - model = choose_model(context.services.model_manager, self.model) - - # Get the source node id (we are invoking the prepared node) - graph_execution_state = context.services.graph_execution_manager.get( - context.graph_execution_state_id - ) - source_node_id = graph_execution_state.prepared_source_mapping[self.id] - - outputs = Img2Img(model).generate( - prompt=self.prompt, - init_image=image, - step_callback=partial(self.dispatch_progress, context, source_node_id), - **self.dict( - exclude={"prompt", "image", "mask"} - ), # Shorthand for passing all of the parameters above manually - ) - - # Outputs is an infinite iterator that will return a new InvokeAIGeneratorOutput object - # each time it is called. We only need the first one. - generator_output = next(outputs) - - image_dto = context.services.images.create( - image=generator_output.image, - image_type=ImageType.RESULT, - image_category=ImageCategory.GENERAL, - session_id=context.graph_execution_state_id, - node_id=self.id, - is_intermediate=self.is_intermediate, - ) - - return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), - width=image_dto.width, - height=image_dto.height, - ) - - -class InpaintInvocation(ImageToImageInvocation): - """Generates an image using inpaint.""" - - type: Literal["inpaint"] = "inpaint" - # Inputs mask: Union[ImageField, None] = Field(description="The mask") seam_size: int = Field(default=96, ge=1, description="The seam inpaint size (px)") @@ -265,6 +127,14 @@ class InpaintInvocation(ImageToImageInvocation): description="The amount by which to replace masked areas with latent noise", ) + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["stable-diffusion", "image"], + }, + } + def dispatch_progress( self, context: InvocationContext, @@ -278,46 +148,93 @@ class InpaintInvocation(ImageToImageInvocation): source_node_id=source_node_id, ) + def get_conditioning(self, context): + c, extra_conditioning_info = context.services.latents.get(self.positive_conditioning.conditioning_name) + uc, _ = context.services.latents.get(self.negative_conditioning.conditioning_name) + + return (uc, c, extra_conditioning_info) + + @contextmanager + def load_model_old_way(self, context, scheduler): + unet_info = context.services.model_manager.get_model(**self.unet.unet.dict()) + vae_info = context.services.model_manager.get_model(**self.vae.vae.dict()) + + #unet = unet_info.context.model + #vae = vae_info.context.model + + with ExitStack() as stack: + loras = [(stack.enter_context(context.services.model_manager.get_model(**lora.dict(exclude={"weight"}))), lora.weight) for lora in self.unet.loras] + + with vae_info as vae,\ + unet_info as unet,\ + ModelPatcher.apply_lora_unet(unet, loras): + + device = context.services.model_manager.mgr.cache.execution_device + dtype = context.services.model_manager.mgr.cache.precision + + pipeline = StableDiffusionGeneratorPipeline( + vae=vae, + text_encoder=None, + tokenizer=None, + unet=unet, + scheduler=scheduler, + safety_checker=None, + feature_extractor=None, + requires_safety_checker=False, + precision="float16" if dtype == torch.float16 else "float32", + execution_device=device, + ) + + yield OldModelInfo( + name=self.unet.unet.model_name, + hash="", + model=pipeline, + ) + def invoke(self, context: InvocationContext) -> ImageOutput: image = ( None if self.image is None - else context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + else context.services.images.get_pil_image(self.image.image_name) ) mask = ( None if self.mask is None - else context.services.images.get_pil_image(self.mask.image_type, self.mask.image_name) + else context.services.images.get_pil_image(self.mask.image_name) ) - # Handle invalid model parameter - model = choose_model(context.services.model_manager, self.model) - # Get the source node id (we are invoking the prepared node) graph_execution_state = context.services.graph_execution_manager.get( context.graph_execution_state_id ) source_node_id = graph_execution_state.prepared_source_mapping[self.id] - outputs = Inpaint(model).generate( - prompt=self.prompt, - init_image=image, - mask_image=mask, - step_callback=partial(self.dispatch_progress, context, source_node_id), - **self.dict( - exclude={"prompt", "image", "mask"} - ), # Shorthand for passing all of the parameters above manually + conditioning = self.get_conditioning(context) + scheduler = get_scheduler( + context=context, + scheduler_info=self.unet.scheduler, + scheduler_name=self.scheduler, ) + with self.load_model_old_way(context, scheduler) as model: + outputs = Inpaint(model).generate( + conditioning=conditioning, + scheduler=scheduler, + init_image=image, + mask_image=mask, + step_callback=partial(self.dispatch_progress, context, source_node_id), + **self.dict( + exclude={"positive_conditioning", "negative_conditioning", "scheduler", "image", "mask"} + ), # Shorthand for passing all of the parameters above manually + ) + # Outputs is an infinite iterator that will return a new InvokeAIGeneratorOutput object # each time it is called. We only need the first one. generator_output = next(outputs) image_dto = context.services.images.create( image=generator_output.image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, session_id=context.graph_execution_state_id, node_id=self.id, @@ -325,10 +242,7 @@ class InpaintInvocation(ImageToImageInvocation): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index 69d51e6158..f85669eab1 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -7,7 +7,7 @@ import numpy from PIL import Image, ImageFilter, ImageOps, ImageChops from pydantic import BaseModel, Field -from ..models.image import ImageCategory, ImageField, ImageType +from ..models.image import ImageCategory, ImageField, ResourceOrigin from .baseinvocation import ( BaseInvocation, BaseInvocationOutput, @@ -72,13 +72,10 @@ class LoadImageInvocation(BaseInvocation): ) # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image(self.image.image_type, self.image.image_name) + image = context.services.images.get_pil_image(self.image.image_name) return ImageOutput( - image=ImageField( - image_name=self.image.image_name, - image_type=self.image.image_type, - ), + image=ImageField(image_name=self.image.image_name), width=image.width, height=image.height, ) @@ -95,19 +92,14 @@ class ShowImageInvocation(BaseInvocation): ) def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) if image: image.show() # TODO: how to handle failure? return ImageOutput( - image=ImageField( - image_name=self.image.image_name, - image_type=self.image.image_type, - ), + image=ImageField(image_name=self.image.image_name), width=image.width, height=image.height, ) @@ -128,9 +120,7 @@ class ImageCropInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) image_crop = Image.new( mode="RGBA", size=(self.width, self.height), color=(0, 0, 0, 0) @@ -139,7 +129,7 @@ class ImageCropInvocation(BaseInvocation, PILInvocationConfig): image_dto = context.services.images.create( image=image_crop, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -147,10 +137,7 @@ class ImageCropInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -171,19 +158,13 @@ class ImagePasteInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - base_image = context.services.images.get_pil_image( - self.base_image.image_type, self.base_image.image_name - ) - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + base_image = context.services.images.get_pil_image(self.base_image.image_name) + image = context.services.images.get_pil_image(self.image.image_name) mask = ( None if self.mask is None else ImageOps.invert( - context.services.images.get_pil_image( - self.mask.image_type, self.mask.image_name - ) + context.services.images.get_pil_image(self.mask.image_name) ) ) # TODO: probably shouldn't invert mask here... should user be required to do it? @@ -201,7 +182,7 @@ class ImagePasteInvocation(BaseInvocation, PILInvocationConfig): image_dto = context.services.images.create( image=new_image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -209,10 +190,7 @@ class ImagePasteInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -230,9 +208,7 @@ class MaskFromAlphaInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> MaskOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) image_mask = image.split()[-1] if self.invert: @@ -240,7 +216,7 @@ class MaskFromAlphaInvocation(BaseInvocation, PILInvocationConfig): image_dto = context.services.images.create( image=image_mask, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.MASK, node_id=self.id, session_id=context.graph_execution_state_id, @@ -248,9 +224,7 @@ class MaskFromAlphaInvocation(BaseInvocation, PILInvocationConfig): ) return MaskOutput( - mask=ImageField( - image_type=image_dto.image_type, image_name=image_dto.image_name - ), + mask=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -268,18 +242,14 @@ class ImageMultiplyInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image1 = context.services.images.get_pil_image( - self.image1.image_type, self.image1.image_name - ) - image2 = context.services.images.get_pil_image( - self.image2.image_type, self.image2.image_name - ) + image1 = context.services.images.get_pil_image(self.image1.image_name) + image2 = context.services.images.get_pil_image(self.image2.image_name) multiply_image = ImageChops.multiply(image1, image2) image_dto = context.services.images.create( image=multiply_image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -287,9 +257,7 @@ class ImageMultiplyInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_type=image_dto.image_type, image_name=image_dto.image_name - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -310,15 +278,13 @@ class ImageChannelInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) channel_image = image.getchannel(self.channel) image_dto = context.services.images.create( image=channel_image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -326,9 +292,7 @@ class ImageChannelInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_type=image_dto.image_type, image_name=image_dto.image_name - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -349,15 +313,13 @@ class ImageConvertInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) converted_image = image.convert(self.mode) image_dto = context.services.images.create( image=converted_image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -365,9 +327,7 @@ class ImageConvertInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_type=image_dto.image_type, image_name=image_dto.image_name - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -386,9 +346,7 @@ class ImageBlurInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) blur = ( ImageFilter.GaussianBlur(self.radius) @@ -399,7 +357,7 @@ class ImageBlurInvocation(BaseInvocation, PILInvocationConfig): image_dto = context.services.images.create( image=blur_image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -407,10 +365,106 @@ class ImageBlurInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), + width=image_dto.width, + height=image_dto.height, + ) + + +PIL_RESAMPLING_MODES = Literal[ + "nearest", + "box", + "bilinear", + "hamming", + "bicubic", + "lanczos", +] + + +PIL_RESAMPLING_MAP = { + "nearest": Image.Resampling.NEAREST, + "box": Image.Resampling.BOX, + "bilinear": Image.Resampling.BILINEAR, + "hamming": Image.Resampling.HAMMING, + "bicubic": Image.Resampling.BICUBIC, + "lanczos": Image.Resampling.LANCZOS, +} + + +class ImageResizeInvocation(BaseInvocation, PILInvocationConfig): + """Resizes an image to specific dimensions""" + + # fmt: off + type: Literal["img_resize"] = "img_resize" + + # Inputs + image: Union[ImageField, None] = Field(default=None, description="The image to resize") + width: int = Field(ge=64, multiple_of=8, description="The width to resize to (px)") + height: int = Field(ge=64, multiple_of=8, description="The height to resize to (px)") + resample_mode: PIL_RESAMPLING_MODES = Field(default="bicubic", description="The resampling mode") + # fmt: on + + def invoke(self, context: InvocationContext) -> ImageOutput: + image = context.services.images.get_pil_image(self.image.image_name) + + resample_mode = PIL_RESAMPLING_MAP[self.resample_mode] + + resize_image = image.resize( + (self.width, self.height), + resample=resample_mode, + ) + + image_dto = context.services.images.create( + image=resize_image, + image_origin=ResourceOrigin.INTERNAL, + image_category=ImageCategory.GENERAL, + node_id=self.id, + session_id=context.graph_execution_state_id, + is_intermediate=self.is_intermediate, + ) + + return ImageOutput( + image=ImageField(image_name=image_dto.image_name), + width=image_dto.width, + height=image_dto.height, + ) + + +class ImageScaleInvocation(BaseInvocation, PILInvocationConfig): + """Scales an image by a factor""" + + # fmt: off + type: Literal["img_scale"] = "img_scale" + + # Inputs + image: Union[ImageField, None] = Field(default=None, description="The image to scale") + scale_factor: float = Field(gt=0, description="The factor by which to scale the image") + resample_mode: PIL_RESAMPLING_MODES = Field(default="bicubic", description="The resampling mode") + # fmt: on + + def invoke(self, context: InvocationContext) -> ImageOutput: + image = context.services.images.get_pil_image(self.image.image_name) + + resample_mode = PIL_RESAMPLING_MAP[self.resample_mode] + width = int(image.width * self.scale_factor) + height = int(image.height * self.scale_factor) + + resize_image = image.resize( + (width, height), + resample=resample_mode, + ) + + image_dto = context.services.images.create( + image=resize_image, + image_origin=ResourceOrigin.INTERNAL, + image_category=ImageCategory.GENERAL, + node_id=self.id, + session_id=context.graph_execution_state_id, + is_intermediate=self.is_intermediate, + ) + + return ImageOutput( + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -429,9 +483,7 @@ class ImageLerpInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) image_arr = numpy.asarray(image, dtype=numpy.float32) / 255 image_arr = image_arr * (self.max - self.min) + self.max @@ -440,7 +492,7 @@ class ImageLerpInvocation(BaseInvocation, PILInvocationConfig): image_dto = context.services.images.create( image=lerp_image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -448,10 +500,7 @@ class ImageLerpInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -470,9 +519,7 @@ class ImageInverseLerpInvocation(BaseInvocation, PILInvocationConfig): # fmt: on def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) image_arr = numpy.asarray(image, dtype=numpy.float32) image_arr = ( @@ -486,7 +533,7 @@ class ImageInverseLerpInvocation(BaseInvocation, PILInvocationConfig): image_dto = context.services.images.create( image=ilerp_image, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -494,10 +541,7 @@ class ImageInverseLerpInvocation(BaseInvocation, PILInvocationConfig): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index ad60b62633..ad67594c29 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -11,7 +11,7 @@ from invokeai.app.invocations.image import ImageOutput from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.backend.image_util.patchmatch import PatchMatch -from ..models.image import ColorField, ImageCategory, ImageField, ImageType +from ..models.image import ColorField, ImageCategory, ImageField, ResourceOrigin from .baseinvocation import ( BaseInvocation, InvocationContext, @@ -134,9 +134,7 @@ class InfillColorInvocation(BaseInvocation): ) def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) solid_bg = Image.new("RGBA", image.size, self.color.tuple()) infilled = Image.alpha_composite(solid_bg, image.convert("RGBA")) @@ -145,7 +143,7 @@ class InfillColorInvocation(BaseInvocation): image_dto = context.services.images.create( image=infilled, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -153,10 +151,7 @@ class InfillColorInvocation(BaseInvocation): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -179,9 +174,7 @@ class InfillTileInvocation(BaseInvocation): ) def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) infilled = tile_fill_missing( image.copy(), seed=self.seed, tile_size=self.tile_size @@ -190,7 +183,7 @@ class InfillTileInvocation(BaseInvocation): image_dto = context.services.images.create( image=infilled, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -198,10 +191,7 @@ class InfillTileInvocation(BaseInvocation): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) @@ -217,9 +207,7 @@ class InfillPatchMatchInvocation(BaseInvocation): ) def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) if PatchMatch.patchmatch_available(): infilled = infill_patchmatch(image.copy()) @@ -228,7 +216,7 @@ class InfillPatchMatchInvocation(BaseInvocation): image_dto = context.services.images.create( image=infilled, - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -236,10 +224,7 @@ class InfillPatchMatchInvocation(BaseInvocation): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 4975b7b578..a9576a2fe1 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -1,42 +1,36 @@ # Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654) -import random -import einops -from typing import Literal, Optional, Union, List +from contextlib import ExitStack +from typing import List, Literal, Optional, Union -from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_controlnet import MultiControlNetModel +import einops from pydantic import BaseModel, Field, validator import torch - -from invokeai.app.invocations.util.choose_model import choose_model -from invokeai.app.models.image import ImageCategory -from invokeai.app.util.misc import SEED_MAX, get_random_seed - -from invokeai.app.util.step_callback import stable_diffusion_step_callback -from .controlnet_image_processors import ControlField - -from ...backend.model_management.model_manager import ModelManager -from ...backend.util.devices import choose_torch_device, torch_dtype -from ...backend.stable_diffusion.diffusion.shared_invokeai_diffusion import PostprocessingSettings -from ...backend.image_util.seamless import configure_model_padding -from ...backend.prompting.conditioning import get_uc_and_c_and_ec - -from ...backend.stable_diffusion.diffusers_pipeline import ConditioningData, StableDiffusionGeneratorPipeline, image_resized_to_grid_as_tensor -from ...backend.stable_diffusion.schedulers import SCHEDULER_MAP -from ...backend.stable_diffusion.diffusers_pipeline import ControlNetData - -from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig -import numpy as np -from ..services.image_file_storage import ImageType -from .baseinvocation import BaseInvocation, InvocationContext -from .image import ImageField, ImageOutput -from .compel import ConditioningField -from ...backend.stable_diffusion import PipelineIntermediateState +from diffusers import ControlNetModel, DPMSolverMultistepScheduler +from diffusers.image_processor import VaeImageProcessor from diffusers.schedulers import SchedulerMixin as Scheduler -import diffusers -from diffusers import DiffusionPipeline, ControlNetModel +from invokeai.app.util.misc import SEED_MAX, get_random_seed +from invokeai.app.util.step_callback import stable_diffusion_step_callback + +from ..models.image import ImageCategory, ImageField, ResourceOrigin +from ...backend.image_util.seamless import configure_model_padding +from ...backend.stable_diffusion import PipelineIntermediateState +from ...backend.stable_diffusion.diffusers_pipeline import ( + ConditioningData, ControlNetData, StableDiffusionGeneratorPipeline, + image_resized_to_grid_as_tensor) +from ...backend.stable_diffusion.diffusion.shared_invokeai_diffusion import \ + PostprocessingSettings +from ...backend.stable_diffusion.schedulers import SCHEDULER_MAP +from ...backend.util.devices import torch_dtype +from ...backend.model_management.lora import ModelPatcher +from .baseinvocation import (BaseInvocation, BaseInvocationOutput, + InvocationConfig, InvocationContext) +from .compel import ConditioningField +from .controlnet_image_processors import ControlField +from .image import ImageOutput +from .model import ModelInfo, UNetField, VaeField class LatentsField(BaseModel): """A latents field used for passing latents between invocations""" @@ -65,102 +59,33 @@ def build_latents_output(latents_name: str, latents: torch.Tensor): height=latents.size()[2] * 8, ) -class NoiseOutput(BaseInvocationOutput): - """Invocation noise output""" - #fmt: off - type: Literal["noise_output"] = "noise_output" - - # Inputs - noise: LatentsField = Field(default=None, description="The output noise") - width: int = Field(description="The width of the noise in pixels") - height: int = Field(description="The height of the noise in pixels") - #fmt: on - -def build_noise_output(latents_name: str, latents: torch.Tensor): - return NoiseOutput( - noise=LatentsField(latents_name=latents_name), - width=latents.size()[3] * 8, - height=latents.size()[2] * 8, - ) - SAMPLER_NAME_VALUES = Literal[ tuple(list(SCHEDULER_MAP.keys())) ] -def get_scheduler(scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: +def get_scheduler( + context: InvocationContext, + scheduler_info: ModelInfo, + scheduler_name: str, +) -> Scheduler: scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP['ddim']) - - scheduler_config = model.scheduler.config + orig_scheduler_info = context.services.model_manager.get_model(**scheduler_info.dict()) + with orig_scheduler_info as orig_scheduler: + scheduler_config = orig_scheduler.config + if "_backup" in scheduler_config: scheduler_config = scheduler_config["_backup"] scheduler_config = {**scheduler_config, **scheduler_extra_config, "_backup": scheduler_config} scheduler = scheduler_class.from_config(scheduler_config) - + # hack copied over from generate.py if not hasattr(scheduler, 'uses_inpainting_model'): scheduler.uses_inpainting_model = lambda: False return scheduler -def get_noise(width:int, height:int, device:torch.device, seed:int = 0, latent_channels:int=4, use_mps_noise:bool=False, downsampling_factor:int = 8): - # limit noise to only the diffusion image channels, not the mask channels - input_channels = min(latent_channels, 4) - use_device = "cpu" if (use_mps_noise or device.type == "mps") else device - generator = torch.Generator(device=use_device).manual_seed(seed) - x = torch.randn( - [ - 1, - input_channels, - height // downsampling_factor, - width // downsampling_factor, - ], - dtype=torch_dtype(device), - device=use_device, - generator=generator, - ).to(device) - # if self.perlin > 0.0: - # perlin_noise = self.get_perlin_noise( - # width // self.downsampling_factor, height // self.downsampling_factor - # ) - # x = (1 - self.perlin) * x + self.perlin * perlin_noise - return x - - -class NoiseInvocation(BaseInvocation): - """Generates latent noise.""" - - type: Literal["noise"] = "noise" - - # Inputs - seed: int = Field(ge=0, le=SEED_MAX, description="The seed to use", default_factory=get_random_seed) - width: int = Field(default=512, multiple_of=8, gt=0, description="The width of the resulting noise", ) - height: int = Field(default=512, multiple_of=8, gt=0, description="The height of the resulting noise", ) - - - # Schema customisation - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["latents", "noise"], - }, - } - - @validator("seed", pre=True) - def modulo_seed(cls, v): - """Returns the seed modulo SEED_MAX to ensure it is within the valid range.""" - return v % SEED_MAX - - def invoke(self, context: InvocationContext) -> NoiseOutput: - device = torch.device(choose_torch_device()) - noise = get_noise(self.width, self.height, device, self.seed) - - name = f'{context.graph_execution_state_id}__{self.id}' - context.services.latents.save(name, noise) - return build_noise_output(latents_name=name, latents=noise) - - # Text to image class TextToLatentsInvocation(BaseInvocation): """Generates latents from conditionings.""" @@ -173,22 +98,36 @@ class TextToLatentsInvocation(BaseInvocation): negative_conditioning: Optional[ConditioningField] = Field(description="Negative conditioning for generation") noise: Optional[LatentsField] = Field(description="The noise to use") steps: int = Field(default=10, gt=0, description="The number of steps to use to generate the image") - cfg_scale: float = Field(default=7.5, gt=0, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) + cfg_scale: Union[float, List[float]] = Field(default=7.5, ge=1, description="The Classifier-Free Guidance, higher values may result in a result closer to the prompt", ) scheduler: SAMPLER_NAME_VALUES = Field(default="euler", description="The scheduler to use" ) - model: str = Field(default="", description="The model to use (currently ignored)") + unet: UNetField = Field(default=None, description="UNet submodel") control: Union[ControlField, list[ControlField]] = Field(default=None, description="The control to use") - # seamless: bool = Field(default=False, description="Whether or not to generate an image that can tile without seams", ) - # seamless_axes: str = Field(default="", description="The axes to tile the image on, 'x' and/or 'y'") + #seamless: bool = Field(default=False, description="Whether or not to generate an image that can tile without seams", ) + #seamless_axes: str = Field(default="", description="The axes to tile the image on, 'x' and/or 'y'") # fmt: on + @validator("cfg_scale") + def ge_one(cls, v): + """validate that all cfg_scale values are >= 1""" + if isinstance(v, list): + for i in v: + if i < 1: + raise ValueError('cfg_scale must be greater than 1') + else: + if v < 1: + raise ValueError('cfg_scale must be greater than 1') + return v + # Schema customisation class Config(InvocationConfig): schema_extra = { "ui": { - "tags": ["latents", "image"], + "tags": ["latents"], "type_hints": { "model": "model", "control": "control", + # "cfg_scale": "float", + "cfg_scale": "number" } }, } @@ -204,73 +143,83 @@ class TextToLatentsInvocation(BaseInvocation): source_node_id=source_node_id, ) - def get_model(self, model_manager: ModelManager) -> StableDiffusionGeneratorPipeline: - model_info = choose_model(model_manager, self.model) - model_name = model_info['model_name'] - model_hash = model_info['hash'] - model: StableDiffusionGeneratorPipeline = model_info['model'] - model.scheduler = get_scheduler( - model=model, - scheduler_name=self.scheduler - ) - - # if isinstance(model, DiffusionPipeline): - # for component in [model.unet, model.vae]: - # configure_model_padding(component, - # self.seamless, - # self.seamless_axes - # ) - # else: - # configure_model_padding(model, - # self.seamless, - # self.seamless_axes - # ) - - return model - - - def get_conditioning_data(self, context: InvocationContext, model: StableDiffusionGeneratorPipeline) -> ConditioningData: + def get_conditioning_data(self, context: InvocationContext, scheduler) -> ConditioningData: c, extra_conditioning_info = context.services.latents.get(self.positive_conditioning.conditioning_name) uc, _ = context.services.latents.get(self.negative_conditioning.conditioning_name) conditioning_data = ConditioningData( - uc, - c, - self.cfg_scale, - extra_conditioning_info, + unconditioned_embeddings=uc, + text_embeddings=c, + guidance_scale=self.cfg_scale, + extra=extra_conditioning_info, postprocessing_settings=PostprocessingSettings( threshold=0.0,#threshold, warmup=0.2,#warmup, h_symmetry_time_pct=None,#h_symmetry_time_pct, v_symmetry_time_pct=None#v_symmetry_time_pct, ), - ).add_scheduler_args_if_applicable(model.scheduler, eta=0.0)#ddim_eta) + ) + + conditioning_data = conditioning_data.add_scheduler_args_if_applicable( + scheduler, + + # for ddim scheduler + eta=0.0, #ddim_eta + + # for ancestral and sde schedulers + generator=torch.Generator(device=uc.device).manual_seed(0), + ) return conditioning_data - def prep_control_data(self, - context: InvocationContext, - model: StableDiffusionGeneratorPipeline, # really only need model for dtype and device - control_input: List[ControlField], - latents_shape: List[int], - do_classifier_free_guidance: bool = True, - ) -> List[ControlNetData]: + def create_pipeline(self, unet, scheduler) -> StableDiffusionGeneratorPipeline: + # TODO: + #configure_model_padding( + # unet, + # self.seamless, + # self.seamless_axes, + #) + + class FakeVae: + class FakeVaeConfig: + def __init__(self): + self.block_out_channels = [0] + + def __init__(self): + self.config = FakeVae.FakeVaeConfig() + + return StableDiffusionGeneratorPipeline( + vae=FakeVae(), # TODO: oh... + text_encoder=None, + tokenizer=None, + unet=unet, + scheduler=scheduler, + safety_checker=None, + feature_extractor=None, + requires_safety_checker=False, + precision="float16" if unet.dtype == torch.float16 else "float32", + ) + + def prep_control_data( + self, + context: InvocationContext, + model: StableDiffusionGeneratorPipeline, # really only need model for dtype and device + control_input: List[ControlField], + latents_shape: List[int], + do_classifier_free_guidance: bool = True, + ) -> List[ControlNetData]: + # assuming fixed dimensional scaling of 8:1 for image:latents control_height_resize = latents_shape[2] * 8 control_width_resize = latents_shape[3] * 8 if control_input is None: - # print("control input is None") control_list = None elif isinstance(control_input, list) and len(control_input) == 0: - # print("control input is empty list") control_list = None elif isinstance(control_input, ControlField): - # print("control input is ControlField") control_list = [control_input] elif isinstance(control_input, list) and len(control_input) > 0 and isinstance(control_input[0], ControlField): - # print("control input is list[ControlField]") control_list = control_input else: - # print("input control is unrecognized:", type(self.control)) control_list = None if (control_list is None): control_data = None @@ -297,8 +246,7 @@ class TextToLatentsInvocation(BaseInvocation): torch_dtype=model.unet.dtype).to(model.device) control_models.append(control_model) control_image_field = control_info.image - input_image = context.services.images.get_pil_image(control_image_field.image_type, - control_image_field.image_name) + input_image = context.services.images.get_pil_image(control_image_field.image_name) # self.image.image_type, self.image.image_name # FIXME: still need to test with different widths, heights, devices, dtypes # and add in batch_size, num_images_per_prompt? @@ -313,12 +261,15 @@ class TextToLatentsInvocation(BaseInvocation): # num_images_per_prompt=num_images_per_prompt, device=control_model.device, dtype=control_model.dtype, + control_mode=control_info.control_mode, ) control_item = ControlNetData(model=control_model, image_tensor=control_image, weight=control_info.control_weight, begin_step_percent=control_info.begin_step_percent, - end_step_percent=control_info.end_step_percent) + end_step_percent=control_info.end_step_percent, + control_mode=control_info.control_mode, + ) control_data.append(control_item) # MultiControlNetModel has been refactored out, just need list[ControlNetData] return control_data @@ -333,23 +284,37 @@ class TextToLatentsInvocation(BaseInvocation): def step_callback(state: PipelineIntermediateState): self.dispatch_progress(context, source_node_id, state) - model = self.get_model(context.services.model_manager) - conditioning_data = self.get_conditioning_data(context, model) + unet_info = context.services.model_manager.get_model(**self.unet.unet.dict()) + with unet_info as unet: - print("type of control input: ", type(self.control)) - control_data = self.prep_control_data(model=model, context=context, control_input=self.control, - latents_shape=noise.shape, - do_classifier_free_guidance=(self.cfg_scale >= 1.0)) + scheduler = get_scheduler( + context=context, + scheduler_info=self.unet.scheduler, + scheduler_name=self.scheduler, + ) + + pipeline = self.create_pipeline(unet, scheduler) + conditioning_data = self.get_conditioning_data(context, scheduler) - # TODO: Verify the noise is the right size - result_latents, result_attention_map_saver = model.latents_from_embeddings( - latents=torch.zeros_like(noise, dtype=torch_dtype(model.device)), - noise=noise, - num_inference_steps=self.steps, - conditioning_data=conditioning_data, - control_data=control_data, # list[ControlNetData] - callback=step_callback, - ) + loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.unet.loras] + + control_data = self.prep_control_data( + model=pipeline, context=context, control_input=self.control, + latents_shape=noise.shape, + # do_classifier_free_guidance=(self.cfg_scale >= 1.0)) + do_classifier_free_guidance=True, + ) + + with ModelPatcher.apply_lora_unet(pipeline.unet, loras): + # TODO: Verify the noise is the right size + result_latents, result_attention_map_saver = pipeline.latents_from_embeddings( + latents=torch.zeros_like(noise, dtype=torch_dtype(unet.device)), + noise=noise, + num_inference_steps=self.steps, + conditioning_data=conditioning_data, + control_data=control_data, # list[ControlNetData] + callback=step_callback, + ) # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 torch.cuda.empty_cache() @@ -358,7 +323,6 @@ class TextToLatentsInvocation(BaseInvocation): context.services.latents.save(name, result_latents) return build_latents_output(latents_name=name, latents=result_latents) - class LatentsToLatentsInvocation(TextToLatentsInvocation): """Generates latents using latents as base image.""" @@ -366,7 +330,7 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): # Inputs latents: Optional[LatentsField] = Field(description="The latents to use as a base image") - strength: float = Field(default=0.5, description="The strength of the latents to use") + strength: float = Field(default=0.7, ge=0, le=1, description="The strength of the latents to use") # Schema customisation class Config(InvocationConfig): @@ -376,6 +340,7 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): "type_hints": { "model": "model", "control": "control", + "cfg_scale": "number", } }, } @@ -391,31 +356,51 @@ class LatentsToLatentsInvocation(TextToLatentsInvocation): def step_callback(state: PipelineIntermediateState): self.dispatch_progress(context, source_node_id, state) - model = self.get_model(context.services.model_manager) - conditioning_data = self.get_conditioning_data(context, model) - - print("type of control input: ", type(self.control)) - control_data = self.prep_control_data(model=model, context=context, control_input=self.control, - latents_shape=noise.shape, - do_classifier_free_guidance=(self.cfg_scale >= 1.0)) - - # TODO: Verify the noise is the right size - - initial_latents = latent if self.strength < 1.0 else torch.zeros_like( - latent, device=model.device, dtype=latent.dtype + unet_info = context.services.model_manager.get_model( + **self.unet.unet.dict(), ) - timesteps, _ = model.get_img2img_timesteps(self.steps, self.strength) + with unet_info as unet: - result_latents, result_attention_map_saver = model.latents_from_embeddings( - latents=initial_latents, - timesteps=timesteps, - noise=noise, - num_inference_steps=self.steps, - conditioning_data=conditioning_data, - control_data=control_data, # list[ControlNetData] - callback=step_callback - ) + scheduler = get_scheduler( + context=context, + scheduler_info=self.unet.scheduler, + scheduler_name=self.scheduler, + ) + + pipeline = self.create_pipeline(unet, scheduler) + conditioning_data = self.get_conditioning_data(context, scheduler) + + control_data = self.prep_control_data( + model=pipeline, context=context, control_input=self.control, + latents_shape=noise.shape, + # do_classifier_free_guidance=(self.cfg_scale >= 1.0)) + do_classifier_free_guidance=True, + ) + + # TODO: Verify the noise is the right size + initial_latents = latent if self.strength < 1.0 else torch.zeros_like( + latent, device=unet.device, dtype=latent.dtype + ) + + timesteps, _ = pipeline.get_img2img_timesteps( + self.steps, + self.strength, + device=unet.device, + ) + + loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.unet.loras] + + with ModelPatcher.apply_lora_unet(pipeline.unet, loras): + result_latents, result_attention_map_saver = pipeline.latents_from_embeddings( + latents=initial_latents, + timesteps=timesteps, + noise=noise, + num_inference_steps=self.steps, + conditioning_data=conditioning_data, + control_data=control_data, # list[ControlNetData] + callback=step_callback + ) # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 torch.cuda.empty_cache() @@ -433,16 +418,14 @@ class LatentsToImageInvocation(BaseInvocation): # Inputs latents: Optional[LatentsField] = Field(description="The latents to generate an image from") - model: str = Field(default="", description="The model to use") + vae: VaeField = Field(default=None, description="Vae submodel") + tiled: bool = Field(default=False, description="Decode latents by overlaping tiles(less memory consumption)") # Schema customisation class Config(InvocationConfig): schema_extra = { "ui": { "tags": ["latents", "image"], - "type_hints": { - "model": "model" - } }, } @@ -450,40 +433,45 @@ class LatentsToImageInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> ImageOutput: latents = context.services.latents.get(self.latents.latents_name) - # TODO: this only really needs the vae - model_info = choose_model(context.services.model_manager, self.model) - model: StableDiffusionGeneratorPipeline = model_info['model'] + vae_info = context.services.model_manager.get_model( + **self.vae.vae.dict(), + ) - with torch.inference_mode(): - np_image = model.decode_latents(latents) - image = model.numpy_to_pil(np_image)[0] - - # what happened to metadata? - # metadata = context.services.metadata.build_metadata( - # session_id=context.graph_execution_state_id, node=self + with vae_info as vae: + if self.tiled or context.services.configuration.tiled_decode: + vae.enable_tiling() + else: + vae.disable_tiling() + # clear memory as vae decode can request a lot torch.cuda.empty_cache() - # new (post Image service refactor) way of using services to save image - # and gnenerate unique image_name - image_dto = context.services.images.create( - image=image, - image_type=ImageType.RESULT, - image_category=ImageCategory.GENERAL, - session_id=context.graph_execution_state_id, - node_id=self.id, - is_intermediate=self.is_intermediate - ) + with torch.inference_mode(): + # copied from diffusers pipeline + latents = latents / vae.config.scaling_factor + image = vae.decode(latents, return_dict=False)[0] + image = (image / 2 + 0.5).clamp(0, 1) # denormalize + # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16 + np_image = image.cpu().permute(0, 2, 3, 1).float().numpy() - return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), - width=image_dto.width, - height=image_dto.height, - ) + image = VaeImageProcessor.numpy_to_pil(np_image)[0] + torch.cuda.empty_cache() + + image_dto = context.services.images.create( + image=image, + image_origin=ResourceOrigin.INTERNAL, + image_category=ImageCategory.GENERAL, + node_id=self.id, + session_id=context.graph_execution_state_id, + is_intermediate=self.is_intermediate + ) + + return ImageOutput( + image=ImageField(image_name=image_dto.image_name), + width=image_dto.width, + height=image_dto.height, + ) LATENTS_INTERPOLATION_MODE = Literal[ "nearest", "linear", "bilinear", "bicubic", "trilinear", "area", "nearest-exact" @@ -559,14 +547,14 @@ class ImageToLatentsInvocation(BaseInvocation): # Inputs image: Union[ImageField, None] = Field(description="The image to encode") - model: str = Field(default="", description="The model to use") + vae: VaeField = Field(default=None, description="Vae submodel") + tiled: bool = Field(default=False, description="Encode latents by overlaping tiles(less memory consumption)") # Schema customisation class Config(InvocationConfig): schema_extra = { "ui": { "tags": ["latents", "image"], - "type_hints": {"model": "model"}, }, } @@ -575,24 +563,32 @@ class ImageToLatentsInvocation(BaseInvocation): # image = context.services.images.get( # self.image.image_type, self.image.image_name # ) - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name + image = context.services.images.get_pil_image(self.image.image_name) + + #vae_info = context.services.model_manager.get_model(**self.vae.vae.dict()) + vae_info = context.services.model_manager.get_model( + **self.vae.vae.dict(), ) - # TODO: this only really needs the vae - model_info = choose_model(context.services.model_manager, self.model) - model: StableDiffusionGeneratorPipeline = model_info["model"] - image_tensor = image_resized_to_grid_as_tensor(image.convert("RGB")) - if image_tensor.dim() == 3: image_tensor = einops.rearrange(image_tensor, "c h w -> 1 c h w") - latents = model.non_noised_latents_from_image( - image_tensor, - device=model._model_group.device_for(model.unet), - dtype=model.unet.dtype, - ) + with vae_info as vae: + if self.tiled: + vae.enable_tiling() + else: + vae.disable_tiling() + + # non_noised_latents_from_image + image_tensor = image_tensor.to(device=vae.device, dtype=vae.dtype) + with torch.inference_mode(): + image_tensor_dist = vae.encode(image_tensor).latent_dist + latents = image_tensor_dist.sample().to( + dtype=vae.dtype + ) # FIXME: uses torch.randn. make reproducible! + + latents = 0.18215 * latents name = f"{context.graph_execution_state_id}__{self.id}" # context.services.latents.set(name, latents) diff --git a/invokeai/app/invocations/model.py b/invokeai/app/invocations/model.py new file mode 100644 index 0000000000..760fa08a12 --- /dev/null +++ b/invokeai/app/invocations/model.py @@ -0,0 +1,223 @@ +from typing import Literal, Optional, Union, List +from pydantic import BaseModel, Field +import copy + +from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext, InvocationConfig + +from ...backend.util.devices import choose_torch_device, torch_dtype +from ...backend.model_management import BaseModelType, ModelType, SubModelType + +class ModelInfo(BaseModel): + model_name: str = Field(description="Info to load submodel") + base_model: BaseModelType = Field(description="Base model") + model_type: ModelType = Field(description="Info to load submodel") + submodel: Optional[SubModelType] = Field(description="Info to load submodel") + +class LoraInfo(ModelInfo): + weight: float = Field(description="Lora's weight which to use when apply to model") + +class UNetField(BaseModel): + unet: ModelInfo = Field(description="Info to load unet submodel") + scheduler: ModelInfo = Field(description="Info to load scheduler submodel") + loras: List[LoraInfo] = Field(description="Loras to apply on model loading") + +class ClipField(BaseModel): + tokenizer: ModelInfo = Field(description="Info to load tokenizer submodel") + text_encoder: ModelInfo = Field(description="Info to load text_encoder submodel") + loras: List[LoraInfo] = Field(description="Loras to apply on model loading") + +class VaeField(BaseModel): + # TODO: better naming? + vae: ModelInfo = Field(description="Info to load vae submodel") + + +class ModelLoaderOutput(BaseInvocationOutput): + """Model loader output""" + + #fmt: off + type: Literal["model_loader_output"] = "model_loader_output" + + unet: UNetField = Field(default=None, description="UNet submodel") + clip: ClipField = Field(default=None, description="Tokenizer and text_encoder submodels") + vae: VaeField = Field(default=None, description="Vae submodel") + #fmt: on + + +class PipelineModelField(BaseModel): + """Pipeline model field""" + + model_name: str = Field(description="Name of the model") + base_model: BaseModelType = Field(description="Base model") + + +class PipelineModelLoaderInvocation(BaseInvocation): + """Loads a pipeline model, outputting its submodels.""" + + type: Literal["pipeline_model_loader"] = "pipeline_model_loader" + + model: PipelineModelField = Field(description="The model to load") + # TODO: precision? + + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["model", "loader"], + "type_hints": { + "model": "model" + } + }, + } + + def invoke(self, context: InvocationContext) -> ModelLoaderOutput: + + base_model = self.model.base_model + model_name = self.model.model_name + model_type = ModelType.Main + + # TODO: not found exceptions + if not context.services.model_manager.model_exists( + model_name=model_name, + base_model=base_model, + model_type=model_type, + ): + raise Exception(f"Unknown {base_model} {model_type} model: {model_name}") + + """ + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.Tokenizer, + ): + raise Exception( + f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted" + ) + + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.TextEncoder, + ): + raise Exception( + f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted" + ) + + if not context.services.model_manager.model_exists( + model_name=self.model_name, + model_type=SDModelType.Diffusers, + submodel=SDModelType.UNet, + ): + raise Exception( + f"Failed to find unet submodel from {self.model_name}! Check if model corrupted" + ) + """ + + + return ModelLoaderOutput( + unet=UNetField( + unet=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.UNet, + ), + scheduler=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Scheduler, + ), + loras=[], + ), + clip=ClipField( + tokenizer=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Tokenizer, + ), + text_encoder=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.TextEncoder, + ), + loras=[], + ), + vae=VaeField( + vae=ModelInfo( + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=SubModelType.Vae, + ), + ) + ) + +class LoraLoaderOutput(BaseInvocationOutput): + """Model loader output""" + + #fmt: off + type: Literal["lora_loader_output"] = "lora_loader_output" + + unet: Optional[UNetField] = Field(default=None, description="UNet submodel") + clip: Optional[ClipField] = Field(default=None, description="Tokenizer and text_encoder submodels") + #fmt: on + +class LoraLoaderInvocation(BaseInvocation): + """Apply selected lora to unet and text_encoder.""" + + type: Literal["lora_loader"] = "lora_loader" + + lora_name: str = Field(description="Lora model name") + weight: float = Field(default=0.75, description="With what weight to apply lora") + + unet: Optional[UNetField] = Field(description="UNet model for applying lora") + clip: Optional[ClipField] = Field(description="Clip model for applying lora") + + def invoke(self, context: InvocationContext) -> LoraLoaderOutput: + + # TODO: ui rewrite + base_model = BaseModelType.StableDiffusion1 + + if not context.services.model_manager.model_exists( + base_model=base_model, + model_name=self.lora_name, + model_type=ModelType.Lora, + ): + raise Exception(f"Unkown lora name: {self.lora_name}!") + + if self.unet is not None and any(lora.model_name == self.lora_name for lora in self.unet.loras): + raise Exception(f"Lora \"{self.lora_name}\" already applied to unet") + + if self.clip is not None and any(lora.model_name == self.lora_name for lora in self.clip.loras): + raise Exception(f"Lora \"{self.lora_name}\" already applied to clip") + + output = LoraLoaderOutput() + + if self.unet is not None: + output.unet = copy.deepcopy(self.unet) + output.unet.loras.append( + LoraInfo( + base_model=base_model, + model_name=self.lora_name, + model_type=ModelType.Lora, + submodel=None, + weight=self.weight, + ) + ) + + if self.clip is not None: + output.clip = copy.deepcopy(self.clip) + output.clip.loras.append( + LoraInfo( + base_model=base_model, + model_name=self.lora_name, + model_type=ModelType.Lora, + submodel=None, + weight=self.weight, + ) + ) + + return output + diff --git a/invokeai/app/invocations/noise.py b/invokeai/app/invocations/noise.py new file mode 100644 index 0000000000..c5866f3608 --- /dev/null +++ b/invokeai/app/invocations/noise.py @@ -0,0 +1,134 @@ +# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654) & the InvokeAI Team + +import math +from typing import Literal + +from pydantic import Field, validator +import torch +from invokeai.app.invocations.latent import LatentsField + +from invokeai.app.util.misc import SEED_MAX, get_random_seed +from ...backend.util.devices import choose_torch_device, torch_dtype +from .baseinvocation import ( + BaseInvocation, + BaseInvocationOutput, + InvocationConfig, + InvocationContext, +) + +""" +Utilities +""" + + +def get_noise( + width: int, + height: int, + device: torch.device, + seed: int = 0, + latent_channels: int = 4, + downsampling_factor: int = 8, + use_cpu: bool = True, + perlin: float = 0.0, +): + """Generate noise for a given image size.""" + noise_device_type = "cpu" if (use_cpu or device.type == "mps") else device.type + + # limit noise to only the diffusion image channels, not the mask channels + input_channels = min(latent_channels, 4) + generator = torch.Generator(device=noise_device_type).manual_seed(seed) + + noise_tensor = torch.randn( + [ + 1, + input_channels, + height // downsampling_factor, + width // downsampling_factor, + ], + dtype=torch_dtype(device), + device=noise_device_type, + generator=generator, + ).to(device) + + return noise_tensor + + +""" +Nodes +""" + + +class NoiseOutput(BaseInvocationOutput): + """Invocation noise output""" + + # fmt: off + type: Literal["noise_output"] = "noise_output" + + # Inputs + noise: LatentsField = Field(default=None, description="The output noise") + width: int = Field(description="The width of the noise in pixels") + height: int = Field(description="The height of the noise in pixels") + # fmt: on + + +def build_noise_output(latents_name: str, latents: torch.Tensor): + return NoiseOutput( + noise=LatentsField(latents_name=latents_name), + width=latents.size()[3] * 8, + height=latents.size()[2] * 8, + ) + + +class NoiseInvocation(BaseInvocation): + """Generates latent noise.""" + + type: Literal["noise"] = "noise" + + # Inputs + seed: int = Field( + ge=0, + le=SEED_MAX, + description="The seed to use", + default_factory=get_random_seed, + ) + width: int = Field( + default=512, + multiple_of=8, + gt=0, + description="The width of the resulting noise", + ) + height: int = Field( + default=512, + multiple_of=8, + gt=0, + description="The height of the resulting noise", + ) + use_cpu: bool = Field( + default=True, + description="Use CPU for noise generation (for reproducible results across platforms)", + ) + + # Schema customisation + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["latents", "noise"], + }, + } + + @validator("seed", pre=True) + def modulo_seed(cls, v): + """Returns the seed modulo SEED_MAX to ensure it is within the valid range.""" + return v % SEED_MAX + + def invoke(self, context: InvocationContext) -> NoiseOutput: + noise = get_noise( + width=self.width, + height=self.height, + device=choose_torch_device(), + seed=self.seed, + use_cpu=self.use_cpu, + ) + name = f"{context.graph_execution_state_id}__{self.id}" + context.services.latents.save(name, noise) + return build_noise_output(latents_name=name, latents=noise) diff --git a/invokeai/app/invocations/param_easing.py b/invokeai/app/invocations/param_easing.py new file mode 100644 index 0000000000..e79763a35e --- /dev/null +++ b/invokeai/app/invocations/param_easing.py @@ -0,0 +1,236 @@ +import io +from typing import Literal, Optional, Any + +# from PIL.Image import Image +import PIL.Image +from matplotlib.ticker import MaxNLocator +from matplotlib.figure import Figure + +from pydantic import BaseModel, Field +import numpy as np +import matplotlib.pyplot as plt + +from easing_functions import ( + LinearInOut, + QuadEaseInOut, QuadEaseIn, QuadEaseOut, + CubicEaseInOut, CubicEaseIn, CubicEaseOut, + QuarticEaseInOut, QuarticEaseIn, QuarticEaseOut, + QuinticEaseInOut, QuinticEaseIn, QuinticEaseOut, + SineEaseInOut, SineEaseIn, SineEaseOut, + CircularEaseIn, CircularEaseInOut, CircularEaseOut, + ExponentialEaseInOut, ExponentialEaseIn, ExponentialEaseOut, + ElasticEaseIn, ElasticEaseInOut, ElasticEaseOut, + BackEaseIn, BackEaseInOut, BackEaseOut, + BounceEaseIn, BounceEaseInOut, BounceEaseOut) + +from .baseinvocation import ( + BaseInvocation, + BaseInvocationOutput, + InvocationContext, + InvocationConfig, +) +from ...backend.util.logging import InvokeAILogger +from .collections import FloatCollectionOutput + + +class FloatLinearRangeInvocation(BaseInvocation): + """Creates a range""" + + type: Literal["float_range"] = "float_range" + + # Inputs + start: float = Field(default=5, description="The first value of the range") + stop: float = Field(default=10, description="The last value of the range") + steps: int = Field(default=30, description="number of values to interpolate over (including start and stop)") + + def invoke(self, context: InvocationContext) -> FloatCollectionOutput: + param_list = list(np.linspace(self.start, self.stop, self.steps)) + return FloatCollectionOutput( + collection=param_list + ) + + +EASING_FUNCTIONS_MAP = { + "Linear": LinearInOut, + "QuadIn": QuadEaseIn, + "QuadOut": QuadEaseOut, + "QuadInOut": QuadEaseInOut, + "CubicIn": CubicEaseIn, + "CubicOut": CubicEaseOut, + "CubicInOut": CubicEaseInOut, + "QuarticIn": QuarticEaseIn, + "QuarticOut": QuarticEaseOut, + "QuarticInOut": QuarticEaseInOut, + "QuinticIn": QuinticEaseIn, + "QuinticOut": QuinticEaseOut, + "QuinticInOut": QuinticEaseInOut, + "SineIn": SineEaseIn, + "SineOut": SineEaseOut, + "SineInOut": SineEaseInOut, + "CircularIn": CircularEaseIn, + "CircularOut": CircularEaseOut, + "CircularInOut": CircularEaseInOut, + "ExponentialIn": ExponentialEaseIn, + "ExponentialOut": ExponentialEaseOut, + "ExponentialInOut": ExponentialEaseInOut, + "ElasticIn": ElasticEaseIn, + "ElasticOut": ElasticEaseOut, + "ElasticInOut": ElasticEaseInOut, + "BackIn": BackEaseIn, + "BackOut": BackEaseOut, + "BackInOut": BackEaseInOut, + "BounceIn": BounceEaseIn, + "BounceOut": BounceEaseOut, + "BounceInOut": BounceEaseInOut, +} + +EASING_FUNCTION_KEYS: Any = Literal[ + tuple(list(EASING_FUNCTIONS_MAP.keys())) +] + + +# actually I think for now could just use CollectionOutput (which is list[Any] +class StepParamEasingInvocation(BaseInvocation): + """Experimental per-step parameter easing for denoising steps""" + + type: Literal["step_param_easing"] = "step_param_easing" + + # Inputs + # fmt: off + easing: EASING_FUNCTION_KEYS = Field(default="Linear", description="The easing function to use") + num_steps: int = Field(default=20, description="number of denoising steps") + start_value: float = Field(default=0.0, description="easing starting value") + end_value: float = Field(default=1.0, description="easing ending value") + start_step_percent: float = Field(default=0.0, description="fraction of steps at which to start easing") + end_step_percent: float = Field(default=1.0, description="fraction of steps after which to end easing") + # if None, then start_value is used prior to easing start + pre_start_value: Optional[float] = Field(default=None, description="value before easing start") + # if None, then end value is used prior to easing end + post_end_value: Optional[float] = Field(default=None, description="value after easing end") + mirror: bool = Field(default=False, description="include mirror of easing function") + # FIXME: add alt_mirror option (alternative to default or mirror), or remove entirely + # alt_mirror: bool = Field(default=False, description="alternative mirroring by dual easing") + show_easing_plot: bool = Field(default=False, description="show easing plot") + # fmt: on + + + def invoke(self, context: InvocationContext) -> FloatCollectionOutput: + log_diagnostics = False + # convert from start_step_percent to nearest step <= (steps * start_step_percent) + # start_step = int(np.floor(self.num_steps * self.start_step_percent)) + start_step = int(np.round(self.num_steps * self.start_step_percent)) + # convert from end_step_percent to nearest step >= (steps * end_step_percent) + # end_step = int(np.ceil((self.num_steps - 1) * self.end_step_percent)) + end_step = int(np.round((self.num_steps - 1) * self.end_step_percent)) + + # end_step = int(np.ceil(self.num_steps * self.end_step_percent)) + num_easing_steps = end_step - start_step + 1 + + # num_presteps = max(start_step - 1, 0) + num_presteps = start_step + num_poststeps = self.num_steps - (num_presteps + num_easing_steps) + prelist = list(num_presteps * [self.pre_start_value]) + postlist = list(num_poststeps * [self.post_end_value]) + + if log_diagnostics: + context.services.logger.debug("start_step: " + str(start_step)) + context.services.logger.debug("end_step: " + str(end_step)) + context.services.logger.debug("num_easing_steps: " + str(num_easing_steps)) + context.services.logger.debug("num_presteps: " + str(num_presteps)) + context.services.logger.debug("num_poststeps: " + str(num_poststeps)) + context.services.logger.debug("prelist size: " + str(len(prelist))) + context.services.logger.debug("postlist size: " + str(len(postlist))) + context.services.logger.debug("prelist: " + str(prelist)) + context.services.logger.debug("postlist: " + str(postlist)) + + easing_class = EASING_FUNCTIONS_MAP[self.easing] + if log_diagnostics: + context.services.logger.debug("easing class: " + str(easing_class)) + easing_list = list() + if self.mirror: # "expected" mirroring + # if number of steps is even, squeeze duration down to (number_of_steps)/2 + # and create reverse copy of list to append + # if number of steps is odd, squeeze duration down to ceil(number_of_steps/2) + # and create reverse copy of list[1:end-1] + # but if even then number_of_steps/2 === ceil(number_of_steps/2), so can just use ceil always + + base_easing_duration = int(np.ceil(num_easing_steps/2.0)) + if log_diagnostics: context.services.logger.debug("base easing duration: " + str(base_easing_duration)) + even_num_steps = (num_easing_steps % 2 == 0) # even number of steps + easing_function = easing_class(start=self.start_value, + end=self.end_value, + duration=base_easing_duration - 1) + base_easing_vals = list() + for step_index in range(base_easing_duration): + easing_val = easing_function.ease(step_index) + base_easing_vals.append(easing_val) + if log_diagnostics: + context.services.logger.debug("step_index: " + str(step_index) + ", easing_val: " + str(easing_val)) + if even_num_steps: + mirror_easing_vals = list(reversed(base_easing_vals)) + else: + mirror_easing_vals = list(reversed(base_easing_vals[0:-1])) + if log_diagnostics: + context.services.logger.debug("base easing vals: " + str(base_easing_vals)) + context.services.logger.debug("mirror easing vals: " + str(mirror_easing_vals)) + easing_list = base_easing_vals + mirror_easing_vals + + # FIXME: add alt_mirror option (alternative to default or mirror), or remove entirely + # elif self.alt_mirror: # function mirroring (unintuitive behavior (at least to me)) + # # half_ease_duration = round(num_easing_steps - 1 / 2) + # half_ease_duration = round((num_easing_steps - 1) / 2) + # easing_function = easing_class(start=self.start_value, + # end=self.end_value, + # duration=half_ease_duration, + # ) + # + # mirror_function = easing_class(start=self.end_value, + # end=self.start_value, + # duration=half_ease_duration, + # ) + # for step_index in range(num_easing_steps): + # if step_index <= half_ease_duration: + # step_val = easing_function.ease(step_index) + # else: + # step_val = mirror_function.ease(step_index - half_ease_duration) + # easing_list.append(step_val) + # if log_diagnostics: logger.debug(step_index, step_val) + # + + else: # no mirroring (default) + easing_function = easing_class(start=self.start_value, + end=self.end_value, + duration=num_easing_steps - 1) + for step_index in range(num_easing_steps): + step_val = easing_function.ease(step_index) + easing_list.append(step_val) + if log_diagnostics: + context.services.logger.debug("step_index: " + str(step_index) + ", easing_val: " + str(step_val)) + + if log_diagnostics: + context.services.logger.debug("prelist size: " + str(len(prelist))) + context.services.logger.debug("easing_list size: " + str(len(easing_list))) + context.services.logger.debug("postlist size: " + str(len(postlist))) + + param_list = prelist + easing_list + postlist + + if self.show_easing_plot: + plt.figure() + plt.xlabel("Step") + plt.ylabel("Param Value") + plt.title("Per-Step Values Based On Easing: " + self.easing) + plt.bar(range(len(param_list)), param_list) + # plt.plot(param_list) + ax = plt.gca() + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + buf = io.BytesIO() + plt.savefig(buf, format='png') + buf.seek(0) + im = PIL.Image.open(buf) + im.show() + buf.close() + + # output array of size steps, each entry list[i] is param value for step i + return FloatCollectionOutput( + collection=param_list + ) diff --git a/invokeai/app/invocations/prompt.py b/invokeai/app/invocations/prompt.py index 0c7e3069df..9af87e1ed4 100644 --- a/invokeai/app/invocations/prompt.py +++ b/invokeai/app/invocations/prompt.py @@ -2,8 +2,8 @@ from typing import Literal from pydantic.fields import Field -from .baseinvocation import BaseInvocationOutput - +from .baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext +from dynamicprompts.generators import RandomPromptGenerator, CombinatorialPromptGenerator class PromptOutput(BaseInvocationOutput): """Base class for invocations that output a prompt""" @@ -20,3 +20,38 @@ class PromptOutput(BaseInvocationOutput): 'prompt', ] } + + +class PromptCollectionOutput(BaseInvocationOutput): + """Base class for invocations that output a collection of prompts""" + + # fmt: off + type: Literal["prompt_collection_output"] = "prompt_collection_output" + + prompt_collection: list[str] = Field(description="The output prompt collection") + count: int = Field(description="The size of the prompt collection") + # fmt: on + + class Config: + schema_extra = {"required": ["type", "prompt_collection", "count"]} + + +class DynamicPromptInvocation(BaseInvocation): + """Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator""" + + type: Literal["dynamic_prompt"] = "dynamic_prompt" + prompt: str = Field(description="The prompt to parse with dynamicprompts") + max_prompts: int = Field(default=1, description="The number of prompts to generate") + combinatorial: bool = Field( + default=False, description="Whether to use the combinatorial generator" + ) + + def invoke(self, context: InvocationContext) -> PromptCollectionOutput: + if self.combinatorial: + generator = CombinatorialPromptGenerator() + prompts = generator.generate(self.prompt, max_prompts=self.max_prompts) + else: + generator = RandomPromptGenerator() + prompts = generator.generate(self.prompt, num_images=self.max_prompts) + + return PromptCollectionOutput(prompt_collection=prompts, count=len(prompts)) diff --git a/invokeai/app/invocations/reconstruct.py b/invokeai/app/invocations/reconstruct.py index db71e4201d..4185de3fd3 100644 --- a/invokeai/app/invocations/reconstruct.py +++ b/invokeai/app/invocations/reconstruct.py @@ -2,7 +2,7 @@ from typing import Literal, Union from pydantic import Field -from invokeai.app.models.image import ImageCategory, ImageField, ImageType +from invokeai.app.models.image import ImageCategory, ImageField, ResourceOrigin from .baseinvocation import BaseInvocation, InvocationContext, InvocationConfig from .image import ImageOutput @@ -28,9 +28,7 @@ class RestoreFaceInvocation(BaseInvocation): } def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) results = context.services.restoration.upscale_and_reconstruct( image_list=[[image, 0]], upscale=None, @@ -43,7 +41,7 @@ class RestoreFaceInvocation(BaseInvocation): # TODO: can this return multiple results? image_dto = context.services.images.create( image=results[0][0], - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -51,10 +49,7 @@ class RestoreFaceInvocation(BaseInvocation): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) diff --git a/invokeai/app/invocations/upscale.py b/invokeai/app/invocations/upscale.py index 90c9e4bf4f..42f85fd18d 100644 --- a/invokeai/app/invocations/upscale.py +++ b/invokeai/app/invocations/upscale.py @@ -4,7 +4,7 @@ from typing import Literal, Union from pydantic import Field -from invokeai.app.models.image import ImageCategory, ImageField, ImageType +from invokeai.app.models.image import ImageCategory, ImageField, ResourceOrigin from .baseinvocation import BaseInvocation, InvocationContext, InvocationConfig from .image import ImageOutput @@ -30,9 +30,7 @@ class UpscaleInvocation(BaseInvocation): } def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.services.images.get_pil_image( - self.image.image_type, self.image.image_name - ) + image = context.services.images.get_pil_image(self.image.image_name) results = context.services.restoration.upscale_and_reconstruct( image_list=[[image, 0]], upscale=(self.level, self.strength), @@ -45,7 +43,7 @@ class UpscaleInvocation(BaseInvocation): # TODO: can this return multiple results? image_dto = context.services.images.create( image=results[0][0], - image_type=ImageType.RESULT, + image_origin=ResourceOrigin.INTERNAL, image_category=ImageCategory.GENERAL, node_id=self.id, session_id=context.graph_execution_state_id, @@ -53,10 +51,7 @@ class UpscaleInvocation(BaseInvocation): ) return ImageOutput( - image=ImageField( - image_name=image_dto.image_name, - image_type=image_dto.image_type, - ), + image=ImageField(image_name=image_dto.image_name), width=image_dto.width, height=image_dto.height, ) diff --git a/invokeai/app/invocations/util/choose_model.py b/invokeai/app/invocations/util/choose_model.py deleted file mode 100644 index 4c5ddca00d..0000000000 --- a/invokeai/app/invocations/util/choose_model.py +++ /dev/null @@ -1,14 +0,0 @@ -from invokeai.backend.model_management.model_manager import ModelManager - - -def choose_model(model_manager: ModelManager, model_name: str): - """Returns the default model if the `model_name` not a valid model, else returns the selected model.""" - logger = model_manager.logger - if model_name and not model_manager.valid_model(model_name): - default_model_name = model_manager.default_model() - logger.warning(f"\'{model_name}\' is not a valid model name. Using default model \'{default_model_name}\' instead.") - model = model_manager.get_model() - else: - model = model_manager.get_model(model_name) - - return model diff --git a/invokeai/app/models/image.py b/invokeai/app/models/image.py index 46b50145aa..988a3e1447 100644 --- a/invokeai/app/models/image.py +++ b/invokeai/app/models/image.py @@ -5,30 +5,52 @@ from pydantic import BaseModel, Field from invokeai.app.util.metaenum import MetaEnum -class ImageType(str, Enum, metaclass=MetaEnum): - """The type of an image.""" +class ResourceOrigin(str, Enum, metaclass=MetaEnum): + """The origin of a resource (eg image). - RESULT = "results" - UPLOAD = "uploads" + - INTERNAL: The resource was created by the application. + - EXTERNAL: The resource was not created by the application. + This may be a user-initiated upload, or an internal application upload (eg Canvas init image). + """ + + INTERNAL = "internal" + """The resource was created by the application.""" + EXTERNAL = "external" + """The resource was not created by the application. + This may be a user-initiated upload, or an internal application upload (eg Canvas init image). + """ -class InvalidImageTypeException(ValueError): - """Raised when a provided value is not a valid ImageType. +class InvalidOriginException(ValueError): + """Raised when a provided value is not a valid ResourceOrigin. Subclasses `ValueError`. """ - def __init__(self, message="Invalid image type."): + def __init__(self, message="Invalid resource origin."): super().__init__(message) class ImageCategory(str, Enum, metaclass=MetaEnum): - """The category of an image. Use ImageCategory.OTHER for non-default categories.""" + """The category of an image. + + - GENERAL: The image is an output, init image, or otherwise an image without a specialized purpose. + - MASK: The image is a mask image. + - CONTROL: The image is a ControlNet control image. + - USER: The image is a user-provide image. + - OTHER: The image is some other type of image with a specialized purpose. To be used by external nodes. + """ GENERAL = "general" - CONTROL = "control" + """GENERAL: The image is an output, init image, or otherwise an image without a specialized purpose.""" MASK = "mask" + """MASK: The image is a mask image.""" + CONTROL = "control" + """CONTROL: The image is a ControlNet control image.""" + USER = "user" + """USER: The image is a user-provide image.""" OTHER = "other" + """OTHER: The image is some other type of image with a specialized purpose. To be used by external nodes.""" class InvalidImageCategoryException(ValueError): @@ -44,13 +66,10 @@ class InvalidImageCategoryException(ValueError): class ImageField(BaseModel): """An image field used for passing image objects between invocations""" - image_type: ImageType = Field( - default=ImageType.RESULT, description="The type of the image" - ) image_name: Optional[str] = Field(default=None, description="The name of the image") class Config: - schema_extra = {"required": ["image_type", "image_name"]} + schema_extra = {"required": ["image_name"]} class ColorField(BaseModel): @@ -61,3 +80,11 @@ class ColorField(BaseModel): def tuple(self) -> Tuple[int, int, int, int]: return (self.r, self.g, self.b, self.a) + + +class ProgressImage(BaseModel): + """The progress image sent intermittently during processing""" + + width: int = Field(description="The effective width of the image in pixels") + height: int = Field(description="The effective height of the image in pixels") + dataURL: str = Field(description="The image data as a b64 data URL") diff --git a/invokeai/app/models/metadata.py b/invokeai/app/models/metadata.py index ac87405423..8d90ca0bc8 100644 --- a/invokeai/app/models/metadata.py +++ b/invokeai/app/models/metadata.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Union, List from pydantic import BaseModel, Extra, Field, StrictFloat, StrictInt, StrictStr @@ -47,7 +47,9 @@ class ImageMetadata(BaseModel): default=None, description="The seed used for noise generation." ) """The seed used for noise generation""" - cfg_scale: Optional[StrictFloat] = Field( + # cfg_scale: Optional[StrictFloat] = Field( + # cfg_scale: Union[float, list[float]] = Field( + cfg_scale: Union[StrictFloat, List[StrictFloat]] = Field( default=None, description="The classifier-free guidance scale." ) """The classifier-free guidance scale""" diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py new file mode 100644 index 0000000000..7aff41860c --- /dev/null +++ b/invokeai/app/services/board_image_record_storage.py @@ -0,0 +1,254 @@ +from abc import ABC, abstractmethod +import sqlite3 +import threading +from typing import Union, cast +from invokeai.app.services.board_record_storage import BoardRecord + +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.image_record import ( + ImageRecord, + deserialize_image_record, +) + + +class BoardImageRecordStorageBase(ABC): + """Abstract base class for the one-to-many board-image relationship record storage.""" + + @abstractmethod + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + pass + + @abstractmethod + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + pass + + @abstractmethod + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageRecord]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_board_for_image( + self, + image_name: str, + ) -> Union[str, None]: + """Gets an image's board id, if it has one.""" + pass + + @abstractmethod + def get_image_count_for_board( + self, + board_id: str, + ) -> int: + """Gets the number of images for a board.""" + pass + + +class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `board_images` junction table.""" + + # Create the `board_images` junction table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS board_images ( + board_id TEXT NOT NULL, + image_name TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Soft delete, currently unused + deleted_at DATETIME, + -- enforce one-to-many relationship between boards and images using PK + -- (we can extend this to many-to-many later) + PRIMARY KEY (image_name), + FOREIGN KEY (board_id) REFERENCES boards (board_id) ON DELETE CASCADE, + FOREIGN KEY (image_name) REFERENCES images (image_name) ON DELETE CASCADE + ); + """ + ) + + # Add index for board id + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_board_images_board_id ON board_images (board_id); + """ + ) + + # Add index for board id, sorted by created_at + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_board_images_board_id_created_at ON board_images (board_id, created_at); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_board_images_updated_at + AFTER UPDATE + ON board_images FOR EACH ROW + BEGIN + UPDATE board_images SET updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') + WHERE board_id = old.board_id AND image_name = old.image_name; + END; + """ + ) + + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT INTO board_images (board_id, image_name) + VALUES (?, ?) + ON CONFLICT (image_name) DO UPDATE SET board_id = ?; + """, + (board_id, image_name, board_id), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM board_images + WHERE board_id = ? AND image_name = ?; + """, + (board_id, image_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def get_images_for_board( + self, + board_id: str, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[ImageRecord]: + # TODO: this isn't paginated yet? + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT images.* + FROM board_images + INNER JOIN images ON board_images.image_name = images.image_name + WHERE board_images.board_id = ? + ORDER BY board_images.updated_at DESC; + """, + (board_id,), + ) + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + images = list(map(lambda r: deserialize_image_record(dict(r)), result)) + + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM images WHERE 1=1; + """ + ) + count = cast(int, self._cursor.fetchone()[0]) + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + return OffsetPaginatedResults( + items=images, offset=offset, limit=limit, total=count + ) + + def get_board_for_image( + self, + image_name: str, + ) -> Union[str, None]: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT board_id + FROM board_images + WHERE image_name = ?; + """, + (image_name,), + ) + result = self._cursor.fetchone() + if result is None: + return None + return cast(str, result[0]) + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def get_image_count_for_board(self, board_id: str) -> int: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT COUNT(*) FROM board_images WHERE board_id = ?; + """, + (board_id,), + ) + count = cast(int, self._cursor.fetchone()[0]) + return count + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py new file mode 100644 index 0000000000..072effbfae --- /dev/null +++ b/invokeai/app/services/board_images.py @@ -0,0 +1,142 @@ +from abc import ABC, abstractmethod +from logging import Logger +from typing import List, Union +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase +from invokeai.app.services.board_record_storage import ( + BoardRecord, + BoardRecordStorageBase, +) + +from invokeai.app.services.image_record_storage import ( + ImageRecordStorageBase, + OffsetPaginatedResults, +) +from invokeai.app.services.models.board_record import BoardDTO +from invokeai.app.services.models.image_record import ImageDTO, image_record_to_dto +from invokeai.app.services.urls import UrlServiceBase + + +class BoardImagesServiceABC(ABC): + """High-level service for board-image relationship management.""" + + @abstractmethod + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Adds an image to a board.""" + pass + + @abstractmethod + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + """Removes an image from a board.""" + pass + + @abstractmethod + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageDTO]: + """Gets images for a board.""" + pass + + @abstractmethod + def get_board_for_image( + self, + image_name: str, + ) -> Union[str, None]: + """Gets an image's board id, if it has one.""" + pass + + +class BoardImagesServiceDependencies: + """Service dependencies for the BoardImagesService.""" + + board_image_records: BoardImageRecordStorageBase + board_records: BoardRecordStorageBase + image_records: ImageRecordStorageBase + urls: UrlServiceBase + logger: Logger + + def __init__( + self, + board_image_record_storage: BoardImageRecordStorageBase, + image_record_storage: ImageRecordStorageBase, + board_record_storage: BoardRecordStorageBase, + url: UrlServiceBase, + logger: Logger, + ): + self.board_image_records = board_image_record_storage + self.image_records = image_record_storage + self.board_records = board_record_storage + self.urls = url + self.logger = logger + + +class BoardImagesService(BoardImagesServiceABC): + _services: BoardImagesServiceDependencies + + def __init__(self, services: BoardImagesServiceDependencies): + self._services = services + + def add_image_to_board( + self, + board_id: str, + image_name: str, + ) -> None: + self._services.board_image_records.add_image_to_board(board_id, image_name) + + def remove_image_from_board( + self, + board_id: str, + image_name: str, + ) -> None: + self._services.board_image_records.remove_image_from_board(board_id, image_name) + + def get_images_for_board( + self, + board_id: str, + ) -> OffsetPaginatedResults[ImageDTO]: + image_records = self._services.board_image_records.get_images_for_board( + board_id + ) + image_dtos = list( + map( + lambda r: image_record_to_dto( + r, + self._services.urls.get_image_url(r.image_name), + self._services.urls.get_image_url(r.image_name, True), + board_id, + ), + image_records.items, + ) + ) + return OffsetPaginatedResults[ImageDTO]( + items=image_dtos, + offset=image_records.offset, + limit=image_records.limit, + total=image_records.total, + ) + + def get_board_for_image( + self, + image_name: str, + ) -> Union[str, None]: + board_id = self._services.board_image_records.get_board_for_image(image_name) + return board_id + + +def board_record_to_dto( + board_record: BoardRecord, cover_image_name: str | None, image_count: int +) -> BoardDTO: + """Converts a board record to a board DTO.""" + return BoardDTO( + **board_record.dict(exclude={'cover_image_name'}), + cover_image_name=cover_image_name, + image_count=image_count, + ) diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py new file mode 100644 index 0000000000..15ea9cc5a7 --- /dev/null +++ b/invokeai/app/services/board_record_storage.py @@ -0,0 +1,329 @@ +from abc import ABC, abstractmethod +from typing import Optional, cast +import sqlite3 +import threading +from typing import Optional, Union +import uuid +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import ( + BoardRecord, + deserialize_board_record, +) + +from pydantic import BaseModel, Field, Extra + + +class BoardChanges(BaseModel, extra=Extra.forbid): + board_name: Optional[str] = Field(description="The board's new name.") + cover_image_name: Optional[str] = Field( + description="The name of the board's new cover image." + ) + + +class BoardRecordNotFoundException(Exception): + """Raised when an board record is not found.""" + + def __init__(self, message="Board record not found"): + super().__init__(message) + + +class BoardRecordSaveException(Exception): + """Raised when an board record cannot be saved.""" + + def __init__(self, message="Board record not saved"): + super().__init__(message) + + +class BoardRecordDeleteException(Exception): + """Raised when an board record cannot be deleted.""" + + def __init__(self, message="Board record not deleted"): + super().__init__(message) + + +class BoardRecordStorageBase(ABC): + """Low-level service responsible for interfacing with the board record store.""" + + @abstractmethod + def delete(self, board_id: str) -> None: + """Deletes a board record.""" + pass + + @abstractmethod + def save( + self, + board_name: str, + ) -> BoardRecord: + """Saves a board record.""" + pass + + @abstractmethod + def get( + self, + board_id: str, + ) -> BoardRecord: + """Gets a board record.""" + pass + + @abstractmethod + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardRecord: + """Updates a board record.""" + pass + + @abstractmethod + def get_many( + self, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + """Gets many board records.""" + pass + + @abstractmethod + def get_all( + self, + ) -> list[BoardRecord]: + """Gets all board records.""" + pass + + +class SqliteBoardRecordStorage(BoardRecordStorageBase): + _filename: str + _conn: sqlite3.Connection + _cursor: sqlite3.Cursor + _lock: threading.Lock + + def __init__(self, filename: str) -> None: + super().__init__() + self._filename = filename + self._conn = sqlite3.connect(filename, check_same_thread=False) + # Enable row factory to get rows as dictionaries (must be done before making the cursor!) + self._conn.row_factory = sqlite3.Row + self._cursor = self._conn.cursor() + self._lock = threading.Lock() + + try: + self._lock.acquire() + # Enable foreign keys + self._conn.execute("PRAGMA foreign_keys = ON;") + self._create_tables() + self._conn.commit() + finally: + self._lock.release() + + def _create_tables(self) -> None: + """Creates the `boards` table and `board_images` junction table.""" + + # Create the `boards` table. + self._cursor.execute( + """--sql + CREATE TABLE IF NOT EXISTS boards ( + board_id TEXT NOT NULL PRIMARY KEY, + board_name TEXT NOT NULL, + cover_image_name TEXT, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Updated via trigger + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + -- Soft delete, currently unused + deleted_at DATETIME, + FOREIGN KEY (cover_image_name) REFERENCES images (image_name) ON DELETE SET NULL + ); + """ + ) + + self._cursor.execute( + """--sql + CREATE INDEX IF NOT EXISTS idx_boards_created_at ON boards (created_at); + """ + ) + + # Add trigger for `updated_at`. + self._cursor.execute( + """--sql + CREATE TRIGGER IF NOT EXISTS tg_boards_updated_at + AFTER UPDATE + ON boards FOR EACH ROW + BEGIN + UPDATE boards SET updated_at = current_timestamp + WHERE board_id = old.board_id; + END; + """ + ) + + def delete(self, board_id: str) -> None: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + DELETE FROM boards + WHERE board_id = ?; + """, + (board_id,), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordDeleteException from e + except Exception as e: + self._conn.rollback() + raise BoardRecordDeleteException from e + finally: + self._lock.release() + + def save( + self, + board_name: str, + ) -> BoardRecord: + try: + board_id = str(uuid.uuid4()) + self._lock.acquire() + self._cursor.execute( + """--sql + INSERT OR IGNORE INTO boards (board_id, board_name) + VALUES (?, ?); + """, + (board_id, board_name), + ) + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + return self.get(board_id) + + def get( + self, + board_id: str, + ) -> BoardRecord: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT * + FROM boards + WHERE board_id = ?; + """, + (board_id,), + ) + + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordNotFoundException from e + finally: + self._lock.release() + if result is None: + raise BoardRecordNotFoundException + return BoardRecord(**dict(result)) + + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardRecord: + try: + self._lock.acquire() + + # Change the name of a board + if changes.board_name is not None: + self._cursor.execute( + f"""--sql + UPDATE boards + SET board_name = ? + WHERE board_id = ?; + """, + (changes.board_name, board_id), + ) + + # Change the cover image of a board + if changes.cover_image_name is not None: + self._cursor.execute( + f"""--sql + UPDATE boards + SET cover_image_name = ? + WHERE board_id = ?; + """, + (changes.cover_image_name, board_id), + ) + + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise BoardRecordSaveException from e + finally: + self._lock.release() + return self.get(board_id) + + def get_many( + self, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardRecord]: + try: + self._lock.acquire() + + # Get all the boards + self._cursor.execute( + """--sql + SELECT * + FROM boards + ORDER BY created_at DESC + LIMIT ? OFFSET ?; + """, + (limit, offset), + ) + + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) + + # Get the total number of boards + self._cursor.execute( + """--sql + SELECT COUNT(*) + FROM boards + WHERE 1=1; + """ + ) + + count = cast(int, self._cursor.fetchone()[0]) + + return OffsetPaginatedResults[BoardRecord]( + items=boards, offset=offset, limit=limit, total=count + ) + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() + + def get_all( + self, + ) -> list[BoardRecord]: + try: + self._lock.acquire() + + # Get all the boards + self._cursor.execute( + """--sql + SELECT * + FROM boards + ORDER BY created_at DESC + """ + ) + + result = cast(list[sqlite3.Row], self._cursor.fetchall()) + boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) + + return boards + + except sqlite3.Error as e: + self._conn.rollback() + raise e + finally: + self._lock.release() diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py new file mode 100644 index 0000000000..9361322e6c --- /dev/null +++ b/invokeai/app/services/boards.py @@ -0,0 +1,185 @@ +from abc import ABC, abstractmethod + +from logging import Logger +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase +from invokeai.app.services.board_images import board_record_to_dto + +from invokeai.app.services.board_record_storage import ( + BoardChanges, + BoardRecordStorageBase, +) +from invokeai.app.services.image_record_storage import ( + ImageRecordStorageBase, + OffsetPaginatedResults, +) +from invokeai.app.services.models.board_record import BoardDTO +from invokeai.app.services.urls import UrlServiceBase + + +class BoardServiceABC(ABC): + """High-level service for board management.""" + + @abstractmethod + def create( + self, + board_name: str, + ) -> BoardDTO: + """Creates a board.""" + pass + + @abstractmethod + def get_dto( + self, + board_id: str, + ) -> BoardDTO: + """Gets a board.""" + pass + + @abstractmethod + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardDTO: + """Updates a board.""" + pass + + @abstractmethod + def delete( + self, + board_id: str, + ) -> None: + """Deletes a board.""" + pass + + @abstractmethod + def get_many( + self, + offset: int = 0, + limit: int = 10, + ) -> OffsetPaginatedResults[BoardDTO]: + """Gets many boards.""" + pass + + @abstractmethod + def get_all( + self, + ) -> list[BoardDTO]: + """Gets all boards.""" + pass + + +class BoardServiceDependencies: + """Service dependencies for the BoardService.""" + + board_image_records: BoardImageRecordStorageBase + board_records: BoardRecordStorageBase + image_records: ImageRecordStorageBase + urls: UrlServiceBase + logger: Logger + + def __init__( + self, + board_image_record_storage: BoardImageRecordStorageBase, + image_record_storage: ImageRecordStorageBase, + board_record_storage: BoardRecordStorageBase, + url: UrlServiceBase, + logger: Logger, + ): + self.board_image_records = board_image_record_storage + self.image_records = image_record_storage + self.board_records = board_record_storage + self.urls = url + self.logger = logger + + +class BoardService(BoardServiceABC): + _services: BoardServiceDependencies + + def __init__(self, services: BoardServiceDependencies): + self._services = services + + def create( + self, + board_name: str, + ) -> BoardDTO: + board_record = self._services.board_records.save(board_name) + return board_record_to_dto(board_record, None, 0) + + def get_dto(self, board_id: str) -> BoardDTO: + board_record = self._services.board_records.get(board_id) + cover_image = self._services.image_records.get_most_recent_image_for_board( + board_record.board_id + ) + if cover_image: + cover_image_name = cover_image.image_name + else: + cover_image_name = None + image_count = self._services.board_image_records.get_image_count_for_board( + board_id + ) + return board_record_to_dto(board_record, cover_image_name, image_count) + + def update( + self, + board_id: str, + changes: BoardChanges, + ) -> BoardDTO: + board_record = self._services.board_records.update(board_id, changes) + cover_image = self._services.image_records.get_most_recent_image_for_board( + board_record.board_id + ) + if cover_image: + cover_image_name = cover_image.image_name + else: + cover_image_name = None + + image_count = self._services.board_image_records.get_image_count_for_board( + board_id + ) + return board_record_to_dto(board_record, cover_image_name, image_count) + + def delete(self, board_id: str) -> None: + self._services.board_records.delete(board_id) + + def get_many( + self, offset: int = 0, limit: int = 10 + ) -> OffsetPaginatedResults[BoardDTO]: + board_records = self._services.board_records.get_many(offset, limit) + board_dtos = [] + for r in board_records.items: + cover_image = self._services.image_records.get_most_recent_image_for_board( + r.board_id + ) + if cover_image: + cover_image_name = cover_image.image_name + else: + cover_image_name = None + + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append(board_record_to_dto(r, cover_image_name, image_count)) + + return OffsetPaginatedResults[BoardDTO]( + items=board_dtos, offset=offset, limit=limit, total=len(board_dtos) + ) + + def get_all(self) -> list[BoardDTO]: + board_records = self._services.board_records.get_all() + board_dtos = [] + for r in board_records: + cover_image = self._services.image_records.get_most_recent_image_for_board( + r.board_id + ) + if cover_image: + cover_image_name = cover_image.image_name + else: + cover_image_name = None + + image_count = self._services.board_image_records.get_image_count_for_board( + r.board_id + ) + board_dtos.append(board_record_to_dto(r, cover_image_name, image_count)) + + return board_dtos \ No newline at end of file diff --git a/invokeai/app/services/config.py b/invokeai/app/services/config.py index 49e0b6bed4..e0f1ceeb25 100644 --- a/invokeai/app/services/config.py +++ b/invokeai/app/services/config.py @@ -15,10 +15,7 @@ InvokeAI: conf_path: configs/models.yaml legacy_conf_dir: configs/stable-diffusion outdir: outputs - embedding_dir: embeddings - lora_dir: loras - autoconvert_dir: null - gfpgan_model_dir: models/gfpgan/GFPGANv1.4.pth + autoimport_dir: null Models: model: stable-diffusion-1.5 embeddings: true @@ -51,18 +48,32 @@ in INVOKEAI_ROOT. You can replace supersede this by providing any OmegaConf dictionary object initialization time: omegaconf = OmegaConf.load('/tmp/init.yaml') - conf = InvokeAIAppConfig(conf=omegaconf) + conf = InvokeAIAppConfig() + conf.parse_args(conf=omegaconf) -By default, InvokeAIAppConfig will parse the contents of `sys.argv` at -initialization time. You may pass a list of strings in the optional +InvokeAIAppConfig.parse_args() will parse the contents of `sys.argv` +at initialization time. You may pass a list of strings in the optional `argv` argument to use instead of the system argv: - conf = InvokeAIAppConfig(arg=['--xformers_enabled']) + conf.parse_args(argv=['--xformers_enabled']) -It is also possible to set a value at initialization time. This value -has highest priority. +It is also possible to set a value at initialization time. However, if +you call parse_args() it may be overwritten. conf = InvokeAIAppConfig(xformers_enabled=True) + conf.parse_args(argv=['--no-xformers']) + conf.xformers_enabled + # False + + +To avoid this, use `get_config()` to retrieve the application-wide +configuration object. This will retain any properties set at object +creation time: + + conf = InvokeAIAppConfig.get_config(xformers_enabled=True) + conf.parse_args(argv=['--no-xformers']) + conf.xformers_enabled + # True Any setting can be overwritten by setting an environment variable of form: "INVOKEAI_", as in: @@ -76,18 +87,23 @@ Order of precedence (from highest): 4) config file options 5) pydantic defaults -Typical usage: +Typical usage at the top level file: from invokeai.app.services.config import InvokeAIAppConfig - from invokeai.invocations.generate import TextToImageInvocation # get global configuration and print its nsfw_checker value - conf = InvokeAIAppConfig() + conf = InvokeAIAppConfig.get_config() + conf.parse_args() + print(conf.nsfw_checker) + +Typical usage in a backend module: + + from invokeai.app.services.config import InvokeAIAppConfig + + # get global configuration and print its nsfw_checker value + conf = InvokeAIAppConfig.get_config() print(conf.nsfw_checker) - # get the text2image invocation and print its step value - text2image = TextToImageInvocation() - print(text2image.steps) Computed properties: @@ -103,10 +119,11 @@ a Path object: lora_path - path to the LoRA directory In most cases, you will want to create a single InvokeAIAppConfig -object for the entire application. The get_invokeai_config() function +object for the entire application. The InvokeAIAppConfig.get_config() function does this: - config = get_invokeai_config() + config = InvokeAIAppConfig.get_config() + config.parse_args() # read values from the command line/config file print(config.root) # Subclassing @@ -140,24 +157,23 @@ two configs are kept in separate sections of the config file: legacy_conf_dir: configs/stable-diffusion outdir: outputs ... + ''' +from __future__ import annotations import argparse import pydoc -import typing import os import sys from argparse import ArgumentParser from omegaconf import OmegaConf, DictConfig from pathlib import Path from pydantic import BaseSettings, Field, parse_obj_as -from typing import Any, ClassVar, Dict, List, Literal, Type, Union, get_origin, get_type_hints, get_args +from typing import ClassVar, Dict, List, Literal, Union, get_origin, get_type_hints, get_args INIT_FILE = Path('invokeai.yaml') +DB_FILE = Path('invokeai.db') LEGACY_INIT_FILE = Path('invokeai.init') -# This global stores a singleton InvokeAIAppConfig configuration object -global_config = None - class InvokeAISettings(BaseSettings): ''' Runtime configuration settings in which default values are @@ -168,7 +184,7 @@ class InvokeAISettings(BaseSettings): def parse_args(self, argv: list=sys.argv[1:]): parser = self.get_parser() - opt, _ = parser.parse_known_args(argv) + opt = parser.parse_args(argv) for name in self.__fields__: if name not in self._excluded(): setattr(self, name, getattr(opt,name)) @@ -330,6 +346,9 @@ the command-line client (recommended for experts only), or can be changed by editing the file "INVOKEAI_ROOT/invokeai.yaml" or by setting environment variables INVOKEAI_. ''' + singleton_config: ClassVar[InvokeAIAppConfig] = None + singleton_init: ClassVar[Dict] = None + #fmt: off type: Literal["InvokeAI"] = "InvokeAI" host : str = Field(default="127.0.0.1", description="IP address to bind to", category='Web Server') @@ -348,54 +367,71 @@ setting environment variables INVOKEAI_. always_use_cpu : bool = Field(default=False, description="If true, use the CPU for rendering even if a GPU is available.", category='Memory/Performance') free_gpu_mem : bool = Field(default=False, description="If true, purge model from GPU after each generation.", category='Memory/Performance') - max_loaded_models : int = Field(default=2, gt=0, description="Maximum number of models to keep in memory for rapid switching", category='Memory/Performance') + max_loaded_models : int = Field(default=3, gt=0, description="Maximum number of models to keep in memory for rapid switching", category='Memory/Performance') precision : Literal[tuple(['auto','float16','float32','autocast'])] = Field(default='float16',description='Floating point precision', category='Memory/Performance') sequential_guidance : bool = Field(default=False, description="Whether to calculate guidance in serial instead of in parallel, lowering memory requirements", category='Memory/Performance') xformers_enabled : bool = Field(default=True, description="Enable/disable memory-efficient attention", category='Memory/Performance') - + tiled_decode : bool = Field(default=False, description="Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty)", category='Memory/Performance') root : Path = Field(default=_find_root(), description='InvokeAI runtime root directory', category='Paths') - autoconvert_dir : Path = Field(default=None, description='Path to a directory of ckpt files to be converted into diffusers and imported on startup.', category='Paths') + autoimport_dir : Path = Field(default='autoimport/main', description='Path to a directory of models files to be imported on startup.', category='Paths') + lora_dir : Path = Field(default='autoimport/lora', description='Path to a directory of LoRA/LyCORIS models to be imported on startup.', category='Paths') + embedding_dir : Path = Field(default='autoimport/embedding', description='Path to a directory of Textual Inversion embeddings to be imported on startup.', category='Paths') + controlnet_dir : Path = Field(default='autoimport/controlnet', description='Path to a directory of ControlNet embeddings to be imported on startup.', category='Paths') conf_path : Path = Field(default='configs/models.yaml', description='Path to models definition file', category='Paths') - embedding_dir : Path = Field(default='embeddings', description='Path to InvokeAI textual inversion aembeddings directory', category='Paths') - gfpgan_model_dir : Path = Field(default="./models/gfpgan/GFPGANv1.4.pth", description='Path to GFPGAN models directory.', category='Paths') + models_dir : Path = Field(default='models', description='Path to the models directory', category='Paths') legacy_conf_dir : Path = Field(default='configs/stable-diffusion', description='Path to directory of legacy checkpoint config files', category='Paths') - lora_dir : Path = Field(default='loras', description='Path to InvokeAI LoRA model directory', category='Paths') + db_dir : Path = Field(default='databases', description='Path to InvokeAI databases directory', category='Paths') outdir : Path = Field(default='outputs', description='Default folder for output images', category='Paths') from_file : Path = Field(default=None, description='Take command input from the indicated file (command-line client only)', category='Paths') use_memory_db : bool = Field(default=False, description='Use in-memory database for storing image metadata', category='Paths') - + model : str = Field(default='stable-diffusion-1.5', description='Initial model name', category='Models') - embeddings : bool = Field(default=True, description='Load contents of embeddings directory', category='Models') + + log_handlers : List[str] = Field(default=["console"], description='Log handler. Valid options are "console", "file=", "syslog=path|address:host:port", "http="', category="Logging") + # note - would be better to read the log_format values from logging.py, but this creates circular dependencies issues + log_format : Literal[tuple(['plain','color','syslog','legacy'])] = Field(default="color", description='Log format. Use "plain" for text-only, "color" for colorized output, "legacy" for 2.3-style logging and "syslog" for syslog-style', category="Logging") + log_level : Literal[tuple(["debug","info","warning","error","critical"])] = Field(default="debug", description="Emit logging messages at this level or higher", category="Logging") #fmt: on - def __init__(self, conf: DictConfig = None, argv: List[str]=None, **kwargs): + def parse_args(self, argv: List[str]=None, conf: DictConfig = None, clobber=False): ''' - Initialize InvokeAIAppconfig. + Update settings with contents of init file, environment, and + command-line settings. :param conf: alternate Omegaconf dictionary object :param argv: aternate sys.argv list - :param **kwargs: attributes to initialize with + :param clobber: ovewrite any initialization parameters passed during initialization ''' - super().__init__(**kwargs) - # Set the runtime root directory. We parse command-line switches here # in order to pick up the --root_dir option. - self.parse_args(argv) + super().parse_args(argv) if conf is None: try: conf = OmegaConf.load(self.root_dir / INIT_FILE) except: pass InvokeAISettings.initconf = conf - + # parse args again in order to pick up settings in configuration file - self.parse_args(argv) + super().parse_args(argv) - # restore initialization values - hints = get_type_hints(self) - for k in kwargs: - setattr(self,k,parse_obj_as(hints[k],kwargs[k])) + if self.singleton_init and not clobber: + hints = get_type_hints(self.__class__) + for k in self.singleton_init: + setattr(self,k,parse_obj_as(hints[k],self.singleton_init[k])) + @classmethod + def get_config(cls,**kwargs)->InvokeAIAppConfig: + ''' + This returns a singleton InvokeAIAppConfig configuration object. + ''' + if cls.singleton_config is None \ + or type(cls.singleton_config)!=cls \ + or (kwargs and cls.singleton_init != kwargs): + cls.singleton_config = cls(**kwargs) + cls.singleton_init = kwargs + return cls.singleton_config + @property def root_path(self)->Path: ''' @@ -416,6 +452,13 @@ setting environment variables INVOKEAI_. def _resolve(self,partial_path:Path)->Path: return (self.root_path / partial_path).resolve() + @property + def init_file_path(self)->Path: + ''' + Path to invokeai.yaml + ''' + return self._resolve(INIT_FILE) + @property def output_path(self)->Path: ''' @@ -423,6 +466,13 @@ setting environment variables INVOKEAI_. ''' return self._resolve(self.outdir) + @property + def db_path(self)->Path: + ''' + Path to the invokeai.db file. + ''' + return self._resolve(self.db_dir) / DB_FILE + @property def model_conf_path(self)->Path: ''' @@ -438,32 +488,11 @@ setting environment variables INVOKEAI_. return self._resolve(self.legacy_conf_dir) @property - def cache_dir(self)->Path: - ''' - Path to the global cache directory for HuggingFace hub-managed models - ''' - return self.models_dir / "hub" - - @property - def models_dir(self)->Path: + def models_path(self)->Path: ''' Path to the models directory ''' - return self._resolve("models") - - @property - def embedding_path(self)->Path: - ''' - Path to the textual inversion embeddings directory. - ''' - return self._resolve(self.embedding_dir) if self.embedding_dir else None - - @property - def lora_path(self)->Path: - ''' - Path to the LoRA models directory. - ''' - return self._resolve(self.lora_dir) if self.lora_dir else None + return self._resolve(self.models_dir) @property def autoconvert_path(self)->Path: @@ -472,13 +501,6 @@ setting environment variables INVOKEAI_. ''' return self._resolve(self.autoconvert_dir) if self.autoconvert_dir else None - @property - def gfpgan_model_path(self)->Path: - ''' - Path to the GFPGAN model. - ''' - return self._resolve(self.gfpgan_model_dir) if self.gfpgan_model_dir else None - # the following methods support legacy calls leftover from the Globals era @property def full_precision(self)->bool: @@ -513,11 +535,8 @@ class PagingArgumentParser(argparse.ArgumentParser): text = self.format_help() pydoc.pager(text) -def get_invokeai_config(cls:Type[InvokeAISettings]=InvokeAIAppConfig,**kwargs)->InvokeAIAppConfig: +def get_invokeai_config(**kwargs)->InvokeAIAppConfig: ''' - This returns a singleton InvokeAIAppConfig configuration object. + Legacy function which returns InvokeAIAppConfig.get_config() ''' - global global_config - if global_config is None or type(global_config)!=cls: - global_config = cls(**kwargs) - return global_config + return InvokeAIAppConfig.get_config(**kwargs) diff --git a/invokeai/app/services/default_graphs.py b/invokeai/app/services/default_graphs.py index 5eda5e957d..92263751b7 100644 --- a/invokeai/app/services/default_graphs.py +++ b/invokeai/app/services/default_graphs.py @@ -1,4 +1,5 @@ -from ..invocations.latent import LatentsToImageInvocation, NoiseInvocation, TextToLatentsInvocation +from ..invocations.latent import LatentsToImageInvocation, TextToLatentsInvocation +from ..invocations.noise import NoiseInvocation from ..invocations.compel import CompelInvocation from ..invocations.params import ParamIntInvocation from .graph import Edge, EdgeConnection, ExposedNodeInput, ExposedNodeOutput, Graph, LibraryGraph diff --git a/invokeai/app/services/events.py b/invokeai/app/services/events.py index a3e7cdd5dc..e578a24006 100644 --- a/invokeai/app/services/events.py +++ b/invokeai/app/services/events.py @@ -1,9 +1,10 @@ # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) -from typing import Any, Optional -from invokeai.app.api.models.images import ProgressImage +from typing import Any +from invokeai.app.models.image import ProgressImage from invokeai.app.util.misc import get_timestamp - +from invokeai.app.services.model_manager_service import BaseModelType, ModelType, SubModelType, ModelInfo +from invokeai.app.models.exceptions import CanceledException class EventServiceBase: session_event: str = "session_event" @@ -101,3 +102,53 @@ class EventServiceBase: graph_execution_state_id=graph_execution_state_id, ), ) + + def emit_model_load_started ( + self, + graph_execution_state_id: str, + node: dict, + source_node_id: str, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + submodel: SubModelType, + ) -> None: + """Emitted when a model is requested""" + self.__emit_session_event( + event_name="model_load_started", + payload=dict( + graph_execution_state_id=graph_execution_state_id, + node=node, + source_node_id=source_node_id, + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=submodel, + ), + ) + + def emit_model_load_completed( + self, + graph_execution_state_id: str, + node: dict, + source_node_id: str, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + submodel: SubModelType, + model_info: ModelInfo, + ) -> None: + """Emitted when a model is correctly loaded (returns model info)""" + self.__emit_session_event( + event_name="model_load_completed", + payload=dict( + graph_execution_state_id=graph_execution_state_id, + node=node, + source_node_id=source_node_id, + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=submodel, + model_info=model_info, + ), + ) diff --git a/invokeai/app/services/graph.py b/invokeai/app/services/graph.py index 60e196faa1..e3cd3d47ce 100644 --- a/invokeai/app/services/graph.py +++ b/invokeai/app/services/graph.py @@ -65,7 +65,6 @@ from typing import Optional, Union, List, get_args def is_union_subtype(t1, t2): t1_args = get_args(t1) t2_args = get_args(t2) - if not t1_args: # t1 is a single type return t1 in t2_args @@ -86,7 +85,6 @@ def is_list_or_contains_list(t): for arg in t_args: if get_origin(arg) is list: return True - return False @@ -393,7 +391,7 @@ class Graph(BaseModel): from_node = self.get_node(edge.source.node_id) to_node = self.get_node(edge.destination.node_id) except NodeNotFoundError: - raise InvalidEdgeError("One or both nodes don't exist") + raise InvalidEdgeError("One or both nodes don't exist: {edge.source.node_id} -> {edge.destination.node_id}") # Validate that an edge to this node+field doesn't already exist input_edges = self._get_input_edges(edge.destination.node_id, edge.destination.field) @@ -404,41 +402,41 @@ class Graph(BaseModel): g = self.nx_graph_flat() g.add_edge(edge.source.node_id, edge.destination.node_id) if not nx.is_directed_acyclic_graph(g): - raise InvalidEdgeError(f'Edge creates a cycle in the graph') + raise InvalidEdgeError(f'Edge creates a cycle in the graph: {edge.source.node_id} -> {edge.destination.node_id}') # Validate that the field types are compatible if not are_connections_compatible( from_node, edge.source.field, to_node, edge.destination.field ): - raise InvalidEdgeError(f'Fields are incompatible') + raise InvalidEdgeError(f'Fields are incompatible: cannot connect {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}') # Validate if iterator output type matches iterator input type (if this edge results in both being set) if isinstance(to_node, IterateInvocation) and edge.destination.field == "collection": if not self._is_iterator_connection_valid( edge.destination.node_id, new_input=edge.source ): - raise InvalidEdgeError(f'Iterator input type does not match iterator output type') + raise InvalidEdgeError(f'Iterator input type does not match iterator output type: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}') # Validate if iterator input type matches output type (if this edge results in both being set) if isinstance(from_node, IterateInvocation) and edge.source.field == "item": if not self._is_iterator_connection_valid( edge.source.node_id, new_output=edge.destination ): - raise InvalidEdgeError(f'Iterator output type does not match iterator input type') + raise InvalidEdgeError(f'Iterator output type does not match iterator input type:, {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}') # Validate if collector input type matches output type (if this edge results in both being set) if isinstance(to_node, CollectInvocation) and edge.destination.field == "item": if not self._is_collector_connection_valid( edge.destination.node_id, new_input=edge.source ): - raise InvalidEdgeError(f'Collector output type does not match collector input type') + raise InvalidEdgeError(f'Collector output type does not match collector input type: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}') # Validate if collector output type matches input type (if this edge results in both being set) if isinstance(from_node, CollectInvocation) and edge.source.field == "collection": if not self._is_collector_connection_valid( edge.source.node_id, new_output=edge.destination ): - raise InvalidEdgeError(f'Collector input type does not match collector output type') + raise InvalidEdgeError(f'Collector input type does not match collector output type: {edge.source.node_id}.{edge.source.field} to {edge.destination.node_id}.{edge.destination.field}') def has_node(self, node_path: str) -> bool: @@ -859,11 +857,9 @@ class GraphExecutionState(BaseModel): if next_node is None: prepared_id = self._prepare() - # TODO: prepare multiple nodes at once? - # while prepared_id is not None and not isinstance(self.graph.nodes[prepared_id], IterateInvocation): - # prepared_id = self._prepare() - - if prepared_id is not None: + # Prepare as many nodes as we can + while prepared_id is not None: + prepared_id = self._prepare() next_node = self._get_next_node() # Get values from edges @@ -1010,14 +1006,30 @@ class GraphExecutionState(BaseModel): # Get flattened source graph g = self.graph.nx_graph_flat() - # Find next unprepared node where all source nodes are executed + # Find next node that: + # - was not already prepared + # - is not an iterate node whose inputs have not been executed + # - does not have an unexecuted iterate ancestor sorted_nodes = nx.topological_sort(g) next_node_id = next( ( n for n in sorted_nodes + # exclude nodes that have already been prepared if n not in self.source_prepared_mapping - and all((e[0] in self.executed for e in g.in_edges(n))) + # exclude iterate nodes whose inputs have not been executed + and not ( + isinstance(self.graph.get_node(n), IterateInvocation) # `n` is an iterate node... + and not all((e[0] in self.executed for e in g.in_edges(n))) # ...that has unexecuted inputs + ) + # exclude nodes who have unexecuted iterate ancestors + and not any( + ( + isinstance(self.graph.get_node(a), IterateInvocation) # `a` is an iterate ancestor of `n`... + and a not in self.executed # ...that is not executed + for a in nx.ancestors(g, n) # for all ancestors `a` of node `n` + ) + ) ), None, ) @@ -1114,9 +1126,22 @@ class GraphExecutionState(BaseModel): ) def _get_next_node(self) -> Optional[BaseInvocation]: + """Gets the deepest node that is ready to be executed""" g = self.execution_graph.nx_graph() - sorted_nodes = nx.topological_sort(g) - next_node = next((n for n in sorted_nodes if n not in self.executed), None) + + # Depth-first search with pre-order traversal is a depth-first topological sort + sorted_nodes = nx.dfs_preorder_nodes(g) + + next_node = next( + ( + n + for n in sorted_nodes + if n not in self.executed # the node must not already be executed... + and all((e[0] in self.executed for e in g.in_edges(n))) # ...and all its inputs must be executed + ), + None, + ) + if next_node is None: return None diff --git a/invokeai/app/services/image_file_storage.py b/invokeai/app/services/image_file_storage.py index 46070b3bf2..f30499ea26 100644 --- a/invokeai/app/services/image_file_storage.py +++ b/invokeai/app/services/image_file_storage.py @@ -1,5 +1,4 @@ # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team -import os from abc import ABC, abstractmethod from pathlib import Path from queue import Queue @@ -9,7 +8,7 @@ from PIL.Image import Image as PILImageType from PIL import Image, PngImagePlugin from send2trash import send2trash -from invokeai.app.models.image import ImageType +from invokeai.app.models.image import ResourceOrigin from invokeai.app.models.metadata import ImageMetadata from invokeai.app.util.thumbnails import get_thumbnail_name, make_thumbnail @@ -40,14 +39,12 @@ class ImageFileStorageBase(ABC): """Low-level service responsible for storing and retrieving image files.""" @abstractmethod - def get(self, image_type: ImageType, image_name: str) -> PILImageType: + def get(self, image_name: str) -> PILImageType: """Retrieves an image as PIL Image.""" pass @abstractmethod - def get_path( - self, image_type: ImageType, image_name: str, thumbnail: bool = False - ) -> str: + def get_path(self, image_name: str, thumbnail: bool = False) -> str: """Gets the internal path to an image or thumbnail.""" pass @@ -62,7 +59,6 @@ class ImageFileStorageBase(ABC): def save( self, image: PILImageType, - image_type: ImageType, image_name: str, metadata: Optional[ImageMetadata] = None, thumbnail_size: int = 256, @@ -71,7 +67,7 @@ class ImageFileStorageBase(ABC): pass @abstractmethod - def delete(self, image_type: ImageType, image_name: str) -> None: + def delete(self, image_name: str) -> None: """Deletes an image and its thumbnail (if one exists).""" pass @@ -79,31 +75,28 @@ class ImageFileStorageBase(ABC): class DiskImageFileStorage(ImageFileStorageBase): """Stores images on disk""" - __output_folder: str + __output_folder: Path __cache_ids: Queue # TODO: this is an incredibly naive cache - __cache: Dict[str, PILImageType] + __cache: Dict[Path, PILImageType] __max_cache_size: int - def __init__(self, output_folder: str): - self.__output_folder = output_folder + def __init__(self, output_folder: str | Path): self.__cache = dict() self.__cache_ids = Queue() self.__max_cache_size = 10 # TODO: get this from config - Path(output_folder).mkdir(parents=True, exist_ok=True) + self.__output_folder: Path = ( + output_folder if isinstance(output_folder, Path) else Path(output_folder) + ) + self.__thumbnails_folder = self.__output_folder / "thumbnails" - # TODO: don't hard-code. get/save/delete should maybe take subpath? - for image_type in ImageType: - Path(os.path.join(output_folder, image_type)).mkdir( - parents=True, exist_ok=True - ) - Path(os.path.join(output_folder, image_type, "thumbnails")).mkdir( - parents=True, exist_ok=True - ) + # Validate required output folders at launch + self.__validate_storage_folders() - def get(self, image_type: ImageType, image_name: str) -> PILImageType: + def get(self, image_name: str) -> PILImageType: try: - image_path = self.get_path(image_type, image_name) + image_path = self.get_path(image_name) + cache_item = self.__get_cache(image_path) if cache_item: return cache_item @@ -117,13 +110,13 @@ class DiskImageFileStorage(ImageFileStorageBase): def save( self, image: PILImageType, - image_type: ImageType, image_name: str, metadata: Optional[ImageMetadata] = None, thumbnail_size: int = 256, ) -> None: try: - image_path = self.get_path(image_type, image_name) + self.__validate_storage_folders() + image_path = self.get_path(image_name) if metadata is not None: pnginfo = PngImagePlugin.PngInfo() @@ -133,7 +126,7 @@ class DiskImageFileStorage(ImageFileStorageBase): image.save(image_path, "PNG") thumbnail_name = get_thumbnail_name(image_name) - thumbnail_path = self.get_path(image_type, thumbnail_name, thumbnail=True) + thumbnail_path = self.get_path(thumbnail_name, thumbnail=True) thumbnail_image = make_thumbnail(image, thumbnail_size) thumbnail_image.save(thumbnail_path) @@ -142,20 +135,19 @@ class DiskImageFileStorage(ImageFileStorageBase): except Exception as e: raise ImageFileSaveException from e - def delete(self, image_type: ImageType, image_name: str) -> None: + def delete(self, image_name: str) -> None: try: - basename = os.path.basename(image_name) - image_path = self.get_path(image_type, basename) + image_path = self.get_path(image_name) - if os.path.exists(image_path): + if image_path.exists(): send2trash(image_path) if image_path in self.__cache: del self.__cache[image_path] thumbnail_name = get_thumbnail_name(image_name) - thumbnail_path = self.get_path(image_type, thumbnail_name, True) + thumbnail_path = self.get_path(thumbnail_name, True) - if os.path.exists(thumbnail_path): + if thumbnail_path.exists(): send2trash(thumbnail_path) if thumbnail_path in self.__cache: del self.__cache[thumbnail_path] @@ -163,36 +155,30 @@ class DiskImageFileStorage(ImageFileStorageBase): raise ImageFileDeleteException from e # TODO: make this a bit more flexible for e.g. cloud storage - def get_path( - self, image_type: ImageType, image_name: str, thumbnail: bool = False - ) -> str: - # strip out any relative path shenanigans - basename = os.path.basename(image_name) + def get_path(self, image_name: str, thumbnail: bool = False) -> Path: + path = self.__output_folder / image_name if thumbnail: - thumbnail_name = get_thumbnail_name(basename) - path = os.path.join( - self.__output_folder, image_type, "thumbnails", thumbnail_name - ) - else: - path = os.path.join(self.__output_folder, image_type, basename) + thumbnail_name = get_thumbnail_name(image_name) + path = self.__thumbnails_folder / thumbnail_name - abspath = os.path.abspath(path) + return path - return abspath - - def validate_path(self, path: str) -> bool: + def validate_path(self, path: str | Path) -> bool: """Validates the path given for an image or thumbnail.""" - try: - os.stat(path) - return True - except: - return False + path = path if isinstance(path, Path) else Path(path) + return path.exists() - def __get_cache(self, image_name: str) -> PILImageType | None: + def __validate_storage_folders(self) -> None: + """Checks if the required output folders exist and create them if they don't""" + folders: list[Path] = [self.__output_folder, self.__thumbnails_folder] + for folder in folders: + folder.mkdir(parents=True, exist_ok=True) + + def __get_cache(self, image_name: Path) -> PILImageType | None: return None if image_name not in self.__cache else self.__cache[image_name] - def __set_cache(self, image_name: str, image: PILImageType): + def __set_cache(self, image_name: Path, image: PILImageType): if not image_name in self.__cache: self.__cache[image_name] = image self.__cache_ids.put( diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 188a411a6b..066e6f8d5f 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -1,21 +1,36 @@ from abc import ABC, abstractmethod from datetime import datetime -from typing import Optional, cast +from typing import Generic, Optional, TypeVar, cast import sqlite3 import threading from typing import Optional, Union +from pydantic import BaseModel, Field +from pydantic.generics import GenericModel + from invokeai.app.models.metadata import ImageMetadata from invokeai.app.models.image import ( ImageCategory, - ImageType, + ResourceOrigin, ) from invokeai.app.services.models.image_record import ( ImageRecord, ImageRecordChanges, deserialize_image_record, ) -from invokeai.app.services.item_storage import PaginatedResults + +T = TypeVar("T", bound=BaseModel) + + +class OffsetPaginatedResults(GenericModel, Generic[T]): + """Offset-paginated results""" + + # fmt: off + items: list[T] = Field(description="Items") + offset: int = Field(description="Offset from which to retrieve items") + limit: int = Field(description="Limit of items to get") + total: int = Field(description="Total number of items in result") + # fmt: on # TODO: Should these excpetions subclass existing python exceptions? @@ -46,7 +61,7 @@ class ImageRecordStorageBase(ABC): # TODO: Implement an `update()` method @abstractmethod - def get(self, image_type: ImageType, image_name: str) -> ImageRecord: + def get(self, image_name: str) -> ImageRecord: """Gets an image record.""" pass @@ -54,7 +69,6 @@ class ImageRecordStorageBase(ABC): def update( self, image_name: str, - image_type: ImageType, changes: ImageRecordChanges, ) -> None: """Updates an image record.""" @@ -63,26 +77,33 @@ class ImageRecordStorageBase(ABC): @abstractmethod def get_many( self, - image_type: ImageType, - image_category: ImageCategory, - page: int = 0, - per_page: int = 10, - ) -> PaginatedResults[ImageRecord]: + offset: int = 0, + limit: int = 10, + image_origin: Optional[ResourceOrigin] = None, + categories: Optional[list[ImageCategory]] = None, + is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, + ) -> OffsetPaginatedResults[ImageRecord]: """Gets a page of image records.""" pass # TODO: The database has a nullable `deleted_at` column, currently unused. # Should we implement soft deletes? Would need coordination with ImageFileStorage. @abstractmethod - def delete(self, image_type: ImageType, image_name: str) -> None: + def delete(self, image_name: str) -> None: """Deletes an image record.""" pass + @abstractmethod + def delete_many(self, image_names: list[str]) -> None: + """Deletes many image records.""" + pass + @abstractmethod def save( self, image_name: str, - image_type: ImageType, + image_origin: ResourceOrigin, image_category: ImageCategory, width: int, height: int, @@ -94,6 +115,11 @@ class ImageRecordStorageBase(ABC): """Saves an image record.""" pass + @abstractmethod + def get_most_recent_image_for_board(self, board_id: str) -> ImageRecord | None: + """Gets the most recent image for a board.""" + pass + class SqliteImageRecordStorage(ImageRecordStorageBase): _filename: str @@ -103,7 +129,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): def __init__(self, filename: str) -> None: super().__init__() - self._filename = filename self._conn = sqlite3.connect(filename, check_same_thread=False) # Enable row factory to get rows as dictionaries (must be done before making the cursor!) @@ -121,7 +146,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): self._lock.release() def _create_tables(self) -> None: - """Creates the tables for the `images` database.""" + """Creates the `images` table.""" # Create the `images` table. self._cursor.execute( @@ -129,7 +154,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): CREATE TABLE IF NOT EXISTS images ( image_name TEXT NOT NULL PRIMARY KEY, -- This is an enum in python, unrestricted string here for flexibility - image_type TEXT NOT NULL, + image_origin TEXT NOT NULL, -- This is an enum in python, unrestricted string here for flexibility image_category TEXT NOT NULL, width INTEGER NOT NULL, @@ -138,9 +163,10 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): node_id TEXT, metadata TEXT, is_intermediate BOOLEAN DEFAULT FALSE, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + board_id TEXT, + created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Updated via trigger - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Soft delete, currently unused deleted_at DATETIME ); @@ -155,7 +181,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): ) self._cursor.execute( """--sql - CREATE INDEX IF NOT EXISTS idx_images_image_type ON images(image_type); + CREATE INDEX IF NOT EXISTS idx_images_image_origin ON images(image_origin); """ ) self._cursor.execute( @@ -176,13 +202,13 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): AFTER UPDATE ON images FOR EACH ROW BEGIN - UPDATE images SET updated_at = current_timestamp + UPDATE images SET updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE image_name = old.image_name; END; """ ) - def get(self, image_type: ImageType, image_name: str) -> Union[ImageRecord, None]: + def get(self, image_name: str) -> Union[ImageRecord, None]: try: self._lock.acquire() @@ -209,7 +235,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): def update( self, image_name: str, - image_type: ImageType, changes: ImageRecordChanges, ) -> None: try: @@ -224,7 +249,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): """, (changes.image_category, image_name), ) - + # Change the session associated with the image if changes.session_id is not None: self._cursor.execute( @@ -235,6 +260,18 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): """, (changes.session_id, image_name), ) + + # Change the image's `is_intermediate`` flag + if changes.is_intermediate is not None: + self._cursor.execute( + f"""--sql + UPDATE images + SET is_intermediate = ? + WHERE image_name = ?; + """, + (changes.is_intermediate, image_name), + ) + self._conn.commit() except sqlite3.Error as e: self._conn.rollback() @@ -244,50 +281,99 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): def get_many( self, - image_type: ImageType, - image_category: ImageCategory, - page: int = 0, - per_page: int = 10, - ) -> PaginatedResults[ImageRecord]: + offset: int = 0, + limit: int = 10, + image_origin: Optional[ResourceOrigin] = None, + categories: Optional[list[ImageCategory]] = None, + is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, + ) -> OffsetPaginatedResults[ImageRecord]: try: self._lock.acquire() - self._cursor.execute( - f"""--sql - SELECT * FROM images - WHERE image_type = ? AND image_category = ? - ORDER BY created_at DESC - LIMIT ? OFFSET ?; - """, - (image_type.value, image_category.value, per_page, page * per_page), - ) + # Manually build two queries - one for the count, one for the records + count_query = """--sql + SELECT COUNT(*) + FROM images + LEFT JOIN board_images ON board_images.image_name = images.image_name + WHERE 1=1 + """ + images_query = """--sql + SELECT images.* + FROM images + LEFT JOIN board_images ON board_images.image_name = images.image_name + WHERE 1=1 + """ + + query_conditions = "" + query_params = [] + + if image_origin is not None: + query_conditions += """--sql + AND images.image_origin = ? + """ + query_params.append(image_origin.value) + + if categories is not None: + # Convert the enum values to unique list of strings + category_strings = list(map(lambda c: c.value, set(categories))) + # Create the correct length of placeholders + placeholders = ",".join("?" * len(category_strings)) + + query_conditions += f"""--sql + AND images.image_category IN ( {placeholders} ) + """ + + # Unpack the included categories into the query params + for c in category_strings: + query_params.append(c) + + if is_intermediate is not None: + query_conditions += """--sql + AND images.is_intermediate = ? + """ + + query_params.append(is_intermediate) + + if board_id is not None: + query_conditions += """--sql + AND board_images.board_id = ? + """ + + query_params.append(board_id) + + query_pagination = """--sql + ORDER BY images.created_at DESC LIMIT ? OFFSET ? + """ + + # Final images query with pagination + images_query += query_conditions + query_pagination + ";" + # Add all the parameters + images_params = query_params.copy() + images_params.append(limit) + images_params.append(offset) + # Build the list of images, deserializing each row + self._cursor.execute(images_query, images_params) result = cast(list[sqlite3.Row], self._cursor.fetchall()) - images = list(map(lambda r: deserialize_image_record(dict(r)), result)) - self._cursor.execute( - """--sql - SELECT count(*) FROM images - WHERE image_type = ? AND image_category = ? - """, - (image_type.value, image_category.value), - ) - - count = self._cursor.fetchone()[0] + # Set up and execute the count query, without pagination + count_query += query_conditions + ";" + count_params = query_params.copy() + self._cursor.execute(count_query, count_params) + count = cast(int, self._cursor.fetchone()[0]) except sqlite3.Error as e: self._conn.rollback() raise e finally: self._lock.release() - pageCount = int(count / per_page) + 1 - - return PaginatedResults( - items=images, page=page, pages=pageCount, per_page=per_page, total=count + return OffsetPaginatedResults( + items=images, offset=offset, limit=limit, total=count ) - def delete(self, image_type: ImageType, image_name: str) -> None: + def delete(self, image_name: str) -> None: try: self._lock.acquire() self._cursor.execute( @@ -304,10 +390,29 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): finally: self._lock.release() + def delete_many(self, image_names: list[str]) -> None: + try: + placeholders = ",".join("?" for _ in image_names) + + self._lock.acquire() + + # Construct the SQLite query with the placeholders + query = f"DELETE FROM images WHERE image_name IN ({placeholders})" + + # Execute the query with the list of IDs as parameters + self._cursor.execute(query, image_names) + + self._conn.commit() + except sqlite3.Error as e: + self._conn.rollback() + raise ImageRecordDeleteException from e + finally: + self._lock.release() + def save( self, image_name: str, - image_type: ImageType, + image_origin: ResourceOrigin, image_category: ImageCategory, session_id: Optional[str], width: int, @@ -325,7 +430,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): """--sql INSERT OR IGNORE INTO images ( image_name, - image_type, + image_origin, image_category, width, height, @@ -338,7 +443,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): """, ( image_name, - image_type.value, + image_origin.value, image_category.value, width, height, @@ -367,3 +472,28 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): raise ImageRecordSaveException from e finally: self._lock.release() + + def get_most_recent_image_for_board( + self, board_id: str + ) -> Union[ImageRecord, None]: + try: + self._lock.acquire() + self._cursor.execute( + """--sql + SELECT images.* + FROM images + JOIN board_images ON images.image_name = board_images.image_name + WHERE board_images.board_id = ? + ORDER BY images.created_at DESC + LIMIT 1; + """, + (board_id,), + ) + + result = cast(Union[sqlite3.Row, None], self._cursor.fetchone()) + finally: + self._lock.release() + if result is None: + return None + + return deserialize_image_record(dict(result)) diff --git a/invokeai/app/services/images.py b/invokeai/app/services/images.py index d0f7236fe2..aeb5e520d8 100644 --- a/invokeai/app/services/images.py +++ b/invokeai/app/services/images.py @@ -1,21 +1,22 @@ from abc import ABC, abstractmethod from logging import Logger from typing import Optional, TYPE_CHECKING, Union -import uuid from PIL.Image import Image as PILImageType from invokeai.app.models.image import ( ImageCategory, - ImageType, + ResourceOrigin, InvalidImageCategoryException, - InvalidImageTypeException, + InvalidOriginException, ) from invokeai.app.models.metadata import ImageMetadata +from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.image_record_storage import ( ImageRecordDeleteException, ImageRecordNotFoundException, ImageRecordSaveException, ImageRecordStorageBase, + OffsetPaginatedResults, ) from invokeai.app.services.models.image_record import ( ImageRecord, @@ -31,6 +32,7 @@ from invokeai.app.services.image_file_storage import ( ) from invokeai.app.services.item_storage import ItemStorageABC, PaginatedResults from invokeai.app.services.metadata import MetadataServiceBase +from invokeai.app.services.resource_name import NameServiceBase from invokeai.app.services.urls import UrlServiceBase if TYPE_CHECKING: @@ -44,11 +46,11 @@ class ImageServiceABC(ABC): def create( self, image: PILImageType, - image_type: ImageType, + image_origin: ResourceOrigin, image_category: ImageCategory, node_id: Optional[str] = None, session_id: Optional[str] = None, - intermediate: bool = False, + is_intermediate: bool = False, ) -> ImageDTO: """Creates an image, storing the file and its metadata.""" pass @@ -56,7 +58,6 @@ class ImageServiceABC(ABC): @abstractmethod def update( self, - image_type: ImageType, image_name: str, changes: ImageRecordChanges, ) -> ImageDTO: @@ -64,22 +65,22 @@ class ImageServiceABC(ABC): pass @abstractmethod - def get_pil_image(self, image_type: ImageType, image_name: str) -> PILImageType: + def get_pil_image(self, image_name: str) -> PILImageType: """Gets an image as a PIL image.""" pass @abstractmethod - def get_record(self, image_type: ImageType, image_name: str) -> ImageRecord: + def get_record(self, image_name: str) -> ImageRecord: """Gets an image record.""" pass @abstractmethod - def get_dto(self, image_type: ImageType, image_name: str) -> ImageDTO: + def get_dto(self, image_name: str) -> ImageDTO: """Gets an image DTO.""" pass @abstractmethod - def get_path(self, image_type: ImageType, image_name: str) -> str: + def get_path(self, image_name: str, thumbnail: bool = False) -> str: """Gets an image's path.""" pass @@ -89,98 +90,89 @@ class ImageServiceABC(ABC): pass @abstractmethod - def get_url( - self, image_type: ImageType, image_name: str, thumbnail: bool = False - ) -> str: + def get_url(self, image_name: str, thumbnail: bool = False) -> str: """Gets an image's or thumbnail's URL.""" pass @abstractmethod def get_many( self, - image_type: ImageType, - image_category: ImageCategory, - page: int = 0, - per_page: int = 10, - ) -> PaginatedResults[ImageDTO]: + offset: int = 0, + limit: int = 10, + image_origin: Optional[ResourceOrigin] = None, + categories: Optional[list[ImageCategory]] = None, + is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, + ) -> OffsetPaginatedResults[ImageDTO]: """Gets a paginated list of image DTOs.""" pass @abstractmethod - def delete(self, image_type: ImageType, image_name: str): + def delete(self, image_name: str): """Deletes an image.""" pass + @abstractmethod + def delete_images_on_board(self, board_id: str): + """Deletes all images on a board.""" + pass + class ImageServiceDependencies: """Service dependencies for the ImageService.""" - records: ImageRecordStorageBase - files: ImageFileStorageBase + image_records: ImageRecordStorageBase + image_files: ImageFileStorageBase + board_image_records: BoardImageRecordStorageBase metadata: MetadataServiceBase urls: UrlServiceBase logger: Logger + names: NameServiceBase graph_execution_manager: ItemStorageABC["GraphExecutionState"] def __init__( self, image_record_storage: ImageRecordStorageBase, image_file_storage: ImageFileStorageBase, + board_image_record_storage: BoardImageRecordStorageBase, metadata: MetadataServiceBase, url: UrlServiceBase, logger: Logger, + names: NameServiceBase, graph_execution_manager: ItemStorageABC["GraphExecutionState"], ): - self.records = image_record_storage - self.files = image_file_storage + self.image_records = image_record_storage + self.image_files = image_file_storage + self.board_image_records = board_image_record_storage self.metadata = metadata self.urls = url self.logger = logger + self.names = names self.graph_execution_manager = graph_execution_manager class ImageService(ImageServiceABC): _services: ImageServiceDependencies - def __init__( - self, - image_record_storage: ImageRecordStorageBase, - image_file_storage: ImageFileStorageBase, - metadata: MetadataServiceBase, - url: UrlServiceBase, - logger: Logger, - graph_execution_manager: ItemStorageABC["GraphExecutionState"], - ): - self._services = ImageServiceDependencies( - image_record_storage=image_record_storage, - image_file_storage=image_file_storage, - metadata=metadata, - url=url, - logger=logger, - graph_execution_manager=graph_execution_manager, - ) + def __init__(self, services: ImageServiceDependencies): + self._services = services def create( self, image: PILImageType, - image_type: ImageType, + image_origin: ResourceOrigin, image_category: ImageCategory, node_id: Optional[str] = None, session_id: Optional[str] = None, is_intermediate: bool = False, ) -> ImageDTO: - if image_type not in ImageType: - raise InvalidImageTypeException + if image_origin not in ResourceOrigin: + raise InvalidOriginException if image_category not in ImageCategory: raise InvalidImageCategoryException - image_name = self._create_image_name( - image_type=image_type, - image_category=image_category, - node_id=node_id, - session_id=session_id, - ) + image_name = self._services.names.create_image_name() metadata = self._get_metadata(session_id, node_id) @@ -188,10 +180,10 @@ class ImageService(ImageServiceABC): try: # TODO: Consider using a transaction here to ensure consistency between storage and database - created_at = self._services.records.save( + self._services.image_records.save( # Non-nullable fields image_name=image_name, - image_type=image_type, + image_origin=image_origin, image_category=image_category, width=width, height=height, @@ -203,38 +195,15 @@ class ImageService(ImageServiceABC): metadata=metadata, ) - self._services.files.save( - image_type=image_type, + self._services.image_files.save( image_name=image_name, image=image, metadata=metadata, ) - image_url = self._services.urls.get_image_url(image_type, image_name) - thumbnail_url = self._services.urls.get_image_url( - image_type, image_name, True - ) + image_dto = self.get_dto(image_name) - return ImageDTO( - # Non-nullable fields - image_name=image_name, - image_type=image_type, - image_category=image_category, - width=width, - height=height, - # Nullable fields - node_id=node_id, - session_id=session_id, - metadata=metadata, - # Meta fields - created_at=created_at, - updated_at=created_at, # this is always the same as the created_at at this time - deleted_at=None, - is_intermediate=is_intermediate, - # Extra non-nullable fields for DTO - image_url=image_url, - thumbnail_url=thumbnail_url, - ) + return image_dto except ImageRecordSaveException: self._services.logger.error("Failed to save image record") raise @@ -247,24 +216,22 @@ class ImageService(ImageServiceABC): def update( self, - image_type: ImageType, image_name: str, changes: ImageRecordChanges, ) -> ImageDTO: try: - self._services.records.update(image_name, image_type, changes) - return self.get_dto(image_type, image_name) + self._services.image_records.update(image_name, changes) + return self.get_dto(image_name) except ImageRecordSaveException: self._services.logger.error("Failed to update image record") raise except Exception as e: self._services.logger.error("Problem updating image record") raise e - - def get_pil_image(self, image_type: ImageType, image_name: str) -> PILImageType: + def get_pil_image(self, image_name: str) -> PILImageType: try: - return self._services.files.get(image_type, image_name) + return self._services.image_files.get(image_name) except ImageFileNotFoundException: self._services.logger.error("Failed to get image file") raise @@ -272,9 +239,9 @@ class ImageService(ImageServiceABC): self._services.logger.error("Problem getting image file") raise e - def get_record(self, image_type: ImageType, image_name: str) -> ImageRecord: + def get_record(self, image_name: str) -> ImageRecord: try: - return self._services.records.get(image_type, image_name) + return self._services.image_records.get(image_name) except ImageRecordNotFoundException: self._services.logger.error("Image record not found") raise @@ -282,14 +249,15 @@ class ImageService(ImageServiceABC): self._services.logger.error("Problem getting image record") raise e - def get_dto(self, image_type: ImageType, image_name: str) -> ImageDTO: + def get_dto(self, image_name: str) -> ImageDTO: try: - image_record = self._services.records.get(image_type, image_name) + image_record = self._services.image_records.get(image_name) image_dto = image_record_to_dto( image_record, - self._services.urls.get_image_url(image_type, image_name), - self._services.urls.get_image_url(image_type, image_name, True), + self._services.urls.get_image_url(image_name), + self._services.urls.get_image_url(image_name, True), + self._services.board_image_records.get_board_for_image(image_name), ) return image_dto @@ -300,74 +268,74 @@ class ImageService(ImageServiceABC): self._services.logger.error("Problem getting image DTO") raise e - def get_path( - self, image_type: ImageType, image_name: str, thumbnail: bool = False - ) -> str: + def get_path(self, image_name: str, thumbnail: bool = False) -> str: try: - return self._services.files.get_path(image_type, image_name, thumbnail) + return self._services.image_files.get_path(image_name, thumbnail) except Exception as e: self._services.logger.error("Problem getting image path") raise e def validate_path(self, path: str) -> bool: try: - return self._services.files.validate_path(path) + return self._services.image_files.validate_path(path) except Exception as e: self._services.logger.error("Problem validating image path") raise e - def get_url( - self, image_type: ImageType, image_name: str, thumbnail: bool = False - ) -> str: + def get_url(self, image_name: str, thumbnail: bool = False) -> str: try: - return self._services.urls.get_image_url(image_type, image_name, thumbnail) + return self._services.urls.get_image_url(image_name, thumbnail) except Exception as e: self._services.logger.error("Problem getting image path") raise e def get_many( self, - image_type: ImageType, - image_category: ImageCategory, - page: int = 0, - per_page: int = 10, - ) -> PaginatedResults[ImageDTO]: + offset: int = 0, + limit: int = 10, + image_origin: Optional[ResourceOrigin] = None, + categories: Optional[list[ImageCategory]] = None, + is_intermediate: Optional[bool] = None, + board_id: Optional[str] = None, + ) -> OffsetPaginatedResults[ImageDTO]: try: - results = self._services.records.get_many( - image_type, - image_category, - page, - per_page, + results = self._services.image_records.get_many( + offset, + limit, + image_origin, + categories, + is_intermediate, + board_id, ) image_dtos = list( map( lambda r: image_record_to_dto( r, - self._services.urls.get_image_url(image_type, r.image_name), - self._services.urls.get_image_url( - image_type, r.image_name, True + self._services.urls.get_image_url(r.image_name), + self._services.urls.get_image_url(r.image_name, True), + self._services.board_image_records.get_board_for_image( + r.image_name ), ), results.items, ) ) - return PaginatedResults[ImageDTO]( + return OffsetPaginatedResults[ImageDTO]( items=image_dtos, - page=results.page, - pages=results.pages, - per_page=results.per_page, + offset=results.offset, + limit=results.limit, total=results.total, ) except Exception as e: self._services.logger.error("Problem getting paginated image DTOs") raise e - def delete(self, image_type: ImageType, image_name: str): + def delete(self, image_name: str): try: - self._services.files.delete(image_type, image_name) - self._services.records.delete(image_type, image_name) + self._services.image_files.delete(image_name) + self._services.image_records.delete(image_name) except ImageRecordDeleteException: self._services.logger.error(f"Failed to delete image record") raise @@ -378,20 +346,27 @@ class ImageService(ImageServiceABC): self._services.logger.error("Problem deleting image record and file") raise e - def _create_image_name( - self, - image_type: ImageType, - image_category: ImageCategory, - node_id: Optional[str] = None, - session_id: Optional[str] = None, - ) -> str: - """Create a unique image name.""" - uuid_str = str(uuid.uuid4()) - - if node_id is not None and session_id is not None: - return f"{image_type.value}_{image_category.value}_{session_id}_{node_id}_{uuid_str}.png" - - return f"{image_type.value}_{image_category.value}_{uuid_str}.png" + def delete_images_on_board(self, board_id: str): + try: + images = self._services.board_image_records.get_images_for_board(board_id) + image_name_list = list( + map( + lambda r: r.image_name, + images.items, + ) + ) + for image_name in image_name_list: + self._services.image_files.delete(image_name) + self._services.image_records.delete_many(image_name_list) + except ImageRecordDeleteException: + self._services.logger.error(f"Failed to delete image records") + raise + except ImageFileDeleteException: + self._services.logger.error(f"Failed to delete image files") + raise + except Exception as e: + self._services.logger.error("Problem deleting image records and files") + raise e def _get_metadata( self, session_id: Optional[str] = None, node_id: Optional[str] = None diff --git a/invokeai/app/services/invocation_services.py b/invokeai/app/services/invocation_services.py index 1f910253e5..10d1d91920 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -4,7 +4,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from logging import Logger - from invokeai.app.services.images import ImageService + from invokeai.app.services.board_images import BoardImagesServiceABC + from invokeai.app.services.boards import BoardServiceABC + from invokeai.app.services.images import ImageServiceABC from invokeai.backend import ModelManager from invokeai.app.services.events import EventServiceBase from invokeai.app.services.latent_storage import LatentsStorageBase @@ -26,9 +28,9 @@ class InvocationServices: model_manager: "ModelManager" restoration: "RestorationServices" configuration: "InvokeAISettings" - images: "ImageService" - - # NOTE: we must forward-declare any types that include invocations, since invocations can use services + images: "ImageServiceABC" + boards: "BoardServiceABC" + board_images: "BoardImagesServiceABC" graph_library: "ItemStorageABC"["LibraryGraph"] graph_execution_manager: "ItemStorageABC"["GraphExecutionState"] processor: "InvocationProcessorABC" @@ -39,7 +41,9 @@ class InvocationServices: events: "EventServiceBase", logger: "Logger", latents: "LatentsStorageBase", - images: "ImageService", + images: "ImageServiceABC", + boards: "BoardServiceABC", + board_images: "BoardImagesServiceABC", queue: "InvocationQueueABC", graph_library: "ItemStorageABC"["LibraryGraph"], graph_execution_manager: "ItemStorageABC"["GraphExecutionState"], @@ -52,9 +56,12 @@ class InvocationServices: self.logger = logger self.latents = latents self.images = images + self.boards = boards + self.board_images = board_images self.queue = queue self.graph_library = graph_library self.graph_execution_manager = graph_execution_manager self.processor = processor self.restoration = restoration self.configuration = configuration + self.boards = boards diff --git a/invokeai/app/services/invoker.py b/invokeai/app/services/invoker.py index a7c9ae444d..f12ba79c15 100644 --- a/invokeai/app/services/invoker.py +++ b/invokeai/app/services/invoker.py @@ -22,7 +22,8 @@ class Invoker: def invoke( self, graph_execution_state: GraphExecutionState, invoke_all: bool = False ) -> str | None: - """Determines the next node to invoke and returns the id of the invoked node, or None if there are no nodes to execute""" + """Determines the next node to invoke and enqueues it, preparing if needed. + Returns the id of the queued node, or `None` if there are no nodes left to enqueue.""" # Get the next invocation invocation = graph_execution_state.next() diff --git a/invokeai/app/services/latent_storage.py b/invokeai/app/services/latent_storage.py index 519c254087..17d35d7c33 100644 --- a/invokeai/app/services/latent_storage.py +++ b/invokeai/app/services/latent_storage.py @@ -1,6 +1,5 @@ # Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654) -import os from abc import ABC, abstractmethod from pathlib import Path from queue import Queue @@ -70,24 +69,26 @@ class ForwardCacheLatentsStorage(LatentsStorageBase): class DiskLatentsStorage(LatentsStorageBase): """Stores latents in a folder on disk without caching""" - __output_folder: str + __output_folder: str | Path - def __init__(self, output_folder: str): - self.__output_folder = output_folder - Path(output_folder).mkdir(parents=True, exist_ok=True) + def __init__(self, output_folder: str | Path): + self.__output_folder = output_folder if isinstance(output_folder, Path) else Path(output_folder) + self.__output_folder.mkdir(parents=True, exist_ok=True) def get(self, name: str) -> torch.Tensor: latent_path = self.get_path(name) return torch.load(latent_path) def save(self, name: str, data: torch.Tensor) -> None: + self.__output_folder.mkdir(parents=True, exist_ok=True) latent_path = self.get_path(name) torch.save(data, latent_path) def delete(self, name: str) -> None: latent_path = self.get_path(name) - os.remove(latent_path) + latent_path.unlink() + - def get_path(self, name: str) -> str: - return os.path.join(self.__output_folder, name) + def get_path(self, name: str) -> Path: + return self.__output_folder / name \ No newline at end of file diff --git a/invokeai/app/services/model_manager_initializer.py b/invokeai/app/services/model_manager_initializer.py deleted file mode 100644 index 3d30577c55..0000000000 --- a/invokeai/app/services/model_manager_initializer.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import sys -import torch -from argparse import Namespace -from omegaconf import OmegaConf -from pathlib import Path -from typing import types - -import invokeai.version -from .config import InvokeAISettings -from ...backend import ModelManager -from ...backend.util import choose_precision, choose_torch_device - -# TODO: Replace with an abstract class base ModelManagerBase -def get_model_manager(config: InvokeAISettings, logger: types.ModuleType) -> ModelManager: - model_config = config.model_conf_path - if not model_config.exists(): - report_model_error( - config, FileNotFoundError(f"The file {model_config} could not be found."), logger - ) - - logger.info(f"{invokeai.version.__app_name__}, version {invokeai.version.__version__}") - logger.info(f'InvokeAI runtime directory is "{config.root}"') - - # these two lines prevent a horrible warning message from appearing - # when the frozen CLIP tokenizer is imported - import transformers # type: ignore - - transformers.logging.set_verbosity_error() - import diffusers - - diffusers.logging.set_verbosity_error() - embedding_path = config.embedding_path - - # migrate legacy models - ModelManager.migrate_models() - - # creating the model manager - try: - device = torch.device(choose_torch_device()) - precision = 'float16' if config.precision=='float16' \ - else 'float32' if config.precision=='float32' \ - else choose_precision(device) - - model_manager = ModelManager( - OmegaConf.load(config.model_conf_path), - precision=precision, - device_type=device, - max_loaded_models=config.max_loaded_models, - embedding_path = embedding_path, - logger = logger, - ) - except (FileNotFoundError, TypeError, AssertionError) as e: - report_model_error(config, e, logger) - except (IOError, KeyError) as e: - logger.error(f"{e}. Aborting.") - sys.exit(-1) - - # try to autoconvert new models - # autoimport new .ckpt files - if config.autoconvert_path: - model_manager.heuristic_import( - config.autoconvert_path, - ) - return model_manager - -def report_model_error(opt: Namespace, e: Exception, logger: types.ModuleType): - logger.error(f'An error occurred while attempting to initialize the model: "{str(e)}"') - logger.error( - "This can be caused by a missing or corrupted models file, and can sometimes be fixed by (re)installing the models." - ) - yes_to_all = os.environ.get("INVOKE_MODEL_RECONFIGURE") - if yes_to_all: - logger.warning( - "Reconfiguration is being forced by environment variable INVOKE_MODEL_RECONFIGURE" - ) - else: - response = input( - "Do you want to run invokeai-configure script to select and/or reinstall models? [y] " - ) - if response.startswith(("n", "N")): - return - - logger.info("invokeai-configure is launching....\n") - - # Match arguments that were set on the CLI - # only the arguments accepted by the configuration script are parsed - root_dir = ["--root", opt.root_dir] if opt.root_dir is not None else [] - config = ["--config", opt.conf] if opt.conf is not None else [] - sys.argv = ["invokeai-configure"] - sys.argv.extend(root_dir) - sys.argv.extend(config.to_dict()) - if yes_to_all is not None: - for arg in yes_to_all.split(): - sys.argv.append(arg) - - from invokeai.frontend.install import invokeai_configure - - invokeai_configure() - # TODO: Figure out how to restart - # print('** InvokeAI will now restart') - # sys.argv = previous_args - # main() # would rather do a os.exec(), but doesn't exist? - # sys.exit(0) diff --git a/invokeai/app/services/model_manager_service.py b/invokeai/app/services/model_manager_service.py new file mode 100644 index 0000000000..8b46b17ad0 --- /dev/null +++ b/invokeai/app/services/model_manager_service.py @@ -0,0 +1,363 @@ +# Copyright (c) 2023 Lincoln D. Stein and the InvokeAI Team + +from __future__ import annotations + +import torch +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Optional, Union, Callable, List, Tuple, types, TYPE_CHECKING +from dataclasses import dataclass + +from invokeai.backend.model_management.model_manager import ( + ModelManager, + BaseModelType, + ModelType, + SubModelType, + ModelInfo, +) +from invokeai.app.models.exceptions import CanceledException +from .config import InvokeAIAppConfig +from ...backend.util import choose_precision, choose_torch_device + +if TYPE_CHECKING: + from ..invocations.baseinvocation import BaseInvocation, InvocationContext + + +class ModelManagerServiceBase(ABC): + """Responsible for managing models on disk and in memory""" + + @abstractmethod + def __init__( + self, + config: InvokeAIAppConfig, + logger: types.ModuleType, + ): + """ + Initialize with the path to the models.yaml config file. + Optional parameters are the torch device type, precision, max_models, + and sequential_offload boolean. Note that the default device + type and precision are set up for a CUDA system running at half precision. + """ + pass + + @abstractmethod + def get_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + submodel: Optional[SubModelType] = None, + node: Optional[BaseInvocation] = None, + context: Optional[InvocationContext] = None, + ) -> ModelInfo: + """Retrieve the indicated model with name and type. + submodel can be used to get a part (such as the vae) + of a diffusers pipeline.""" + pass + + @property + @abstractmethod + def logger(self): + pass + + @abstractmethod + def model_exists( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ) -> bool: + pass + + @abstractmethod + def model_info(self, model_name: str, base_model: BaseModelType, model_type: ModelType) -> dict: + """ + Given a model name returns a dict-like (OmegaConf) object describing it. + """ + pass + + @abstractmethod + def model_names(self) -> List[Tuple[str, BaseModelType, ModelType]]: + """ + Returns a list of all the model names known. + """ + pass + + @abstractmethod + def list_models(self, base_model: Optional[BaseModelType] = None, model_type: Optional[ModelType] = None) -> dict: + """ + Return a dict of models in the format: + { model_type1: + { model_name1: {'status': 'active'|'cached'|'not loaded', + 'model_name' : name, + 'model_type' : SDModelType, + 'description': description, + 'format': 'folder'|'safetensors'|'ckpt' + }, + model_name2: { etc } + }, + model_type2: + { model_name_n: etc + } + """ + pass + + + @abstractmethod + def add_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + model_attributes: dict, + clobber: bool = False + ) -> None: + """ + Update the named model with a dictionary of attributes. Will fail with an + assertion error if the name already exists. Pass clobber=True to overwrite. + On a successful update, the config will be changed in memory. Will fail + with an assertion error if provided attributes are incorrect or + the model name is missing. Call commit() to write changes to disk. + """ + pass + + @abstractmethod + def del_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ): + """ + Delete the named model from configuration. If delete_files is true, + then the underlying weight file or diffusers directory will be deleted + as well. Call commit() to write to disk. + """ + pass + + @abstractmethod + def commit(self, conf_file: Path = None) -> None: + """ + Write current configuration out to the indicated file. + If no conf_file is provided, then replaces the + original file/database used to initialize the object. + """ + pass + +# simple implementation +class ModelManagerService(ModelManagerServiceBase): + """Responsible for managing models on disk and in memory""" + def __init__( + self, + config: InvokeAIAppConfig, + logger: types.ModuleType, + ): + """ + Initialize with the path to the models.yaml config file. + Optional parameters are the torch device type, precision, max_models, + and sequential_offload boolean. Note that the default device + type and precision are set up for a CUDA system running at half precision. + """ + if config.model_conf_path and config.model_conf_path.exists(): + config_file = config.model_conf_path + else: + config_file = config.root_dir / "configs/models.yaml" + if not config_file.exists(): + raise IOError(f"The file {config_file} could not be found.") + + logger.debug(f'config file={config_file}') + + device = torch.device(choose_torch_device()) + precision = config.precision + if precision == "auto": + precision = choose_precision(device) + dtype = torch.float32 if precision == 'float32' else torch.float16 + + # this is transitional backward compatibility + # support for the deprecated `max_loaded_models` + # configuration value. If present, then the + # cache size is set to 2.5 GB times + # the number of max_loaded_models. Otherwise + # use new `max_cache_size` config setting + max_cache_size = config.max_cache_size \ + if hasattr(config,'max_cache_size') \ + else config.max_loaded_models * 2.5 + + sequential_offload = config.sequential_guidance + + self.mgr = ModelManager( + config=config_file, + device_type=device, + precision=dtype, + max_cache_size=max_cache_size, + sequential_offload=sequential_offload, + logger=logger, + ) + logger.info('Model manager service initialized') + + def get_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + submodel: Optional[SubModelType] = None, + node: Optional[BaseInvocation] = None, + context: Optional[InvocationContext] = None, + ) -> ModelInfo: + """ + Retrieve the indicated model. submodel can be used to get a + part (such as the vae) of a diffusers mode. + """ + + # if we are called from within a node, then we get to emit + # load start and complete events + if node and context: + self._emit_load_event( + node=node, + context=context, + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=submodel, + ) + + model_info = self.mgr.get_model( + model_name, + base_model, + model_type, + submodel, + ) + + if node and context: + self._emit_load_event( + node=node, + context=context, + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=submodel, + model_info=model_info + ) + + return model_info + + def model_exists( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ) -> bool: + """ + Given a model name, returns True if it is a valid + identifier. + """ + return self.mgr.model_exists( + model_name, + base_model, + model_type, + ) + + def model_info(self, model_name: str, base_model: BaseModelType, model_type: ModelType) -> dict: + """ + Given a model name returns a dict-like (OmegaConf) object describing it. + """ + return self.mgr.model_info(model_name, base_model, model_type) + + def model_names(self) -> List[Tuple[str, BaseModelType, ModelType]]: + """ + Returns a list of all the model names known. + """ + return self.mgr.model_names() + + def list_models( + self, + base_model: Optional[BaseModelType] = None, + model_type: Optional[ModelType] = None + ) -> list[dict]: + # ) -> dict: + """ + Return a list of models. + """ + return self.mgr.list_models(base_model, model_type) + + def add_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + model_attributes: dict, + clobber: bool = False, + )->None: + """ + Update the named model with a dictionary of attributes. Will fail with an + assertion error if the name already exists. Pass clobber=True to overwrite. + On a successful update, the config will be changed in memory. Will fail + with an assertion error if provided attributes are incorrect or + the model name is missing. Call commit() to write changes to disk. + """ + return self.mgr.add_model(model_name, base_model, model_type, model_attributes, clobber) + + + def del_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ): + """ + Delete the named model from configuration. If delete_files is true, + then the underlying weight file or diffusers directory will be deleted + as well. Call commit() to write to disk. + """ + self.mgr.del_model(model_name, base_model, model_type) + + + def commit(self, conf_file: Optional[Path]=None): + """ + Write current configuration out to the indicated file. + If no conf_file is provided, then replaces the + original file/database used to initialize the object. + """ + return self.mgr.commit(conf_file) + + def _emit_load_event( + self, + node, + context, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + submodel: SubModelType, + model_info: Optional[ModelInfo] = None, + ): + if context.services.queue.is_canceled(context.graph_execution_state_id): + raise CanceledException() + graph_execution_state = context.services.graph_execution_manager.get(context.graph_execution_state_id) + source_node_id = graph_execution_state.prepared_source_mapping[node.id] + if model_info: + context.services.events.emit_model_load_completed( + graph_execution_state_id=context.graph_execution_state_id, + node=node.dict(), + source_node_id=source_node_id, + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=submodel, + model_info=model_info + ) + else: + context.services.events.emit_model_load_started( + graph_execution_state_id=context.graph_execution_state_id, + node=node.dict(), + source_node_id=source_node_id, + model_name=model_name, + base_model=base_model, + model_type=model_type, + submodel=submodel, + ) + + + @property + def logger(self): + return self.mgr.logger + diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py new file mode 100644 index 0000000000..bf5401b209 --- /dev/null +++ b/invokeai/app/services/models/board_record.py @@ -0,0 +1,62 @@ +from typing import Optional, Union +from datetime import datetime +from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr +from invokeai.app.util.misc import get_iso_timestamp + + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + board_id: str = Field(description="The unique ID of the board.") + """The unique ID of the board.""" + board_name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + """The updated timestamp of the image.""" + deleted_at: Union[datetime, str, None] = Field( + description="The deleted timestamp of the board." + ) + """The updated timestamp of the image.""" + cover_image_name: Optional[str] = Field( + description="The name of the cover image of the board." + ) + """The name of the cover image of the board.""" + + +class BoardDTO(BoardRecord): + """Deserialized board record with cover image URL and image count.""" + + cover_image_name: Optional[str] = Field( + description="The name of the board's cover image." + ) + """The URL of the thumbnail of the most recent image in the board.""" + image_count: int = Field(description="The number of images in the board.") + """The number of images in the board.""" + + +def deserialize_board_record(board_dict: dict) -> BoardRecord: + """Deserializes a board record.""" + + # Retrieve all the values, setting "reasonable" defaults if they are not present. + + board_id = board_dict.get("board_id", "unknown") + board_name = board_dict.get("board_name", "unknown") + cover_image_name = board_dict.get("cover_image_name", "unknown") + created_at = board_dict.get("created_at", get_iso_timestamp()) + updated_at = board_dict.get("updated_at", get_iso_timestamp()) + deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + + return BoardRecord( + board_id=board_id, + board_name=board_name, + cover_image_name=cover_image_name, + created_at=created_at, + updated_at=updated_at, + deleted_at=deleted_at, + ) diff --git a/invokeai/app/services/models/image_record.py b/invokeai/app/services/models/image_record.py index 26e4929be2..cc02016cf9 100644 --- a/invokeai/app/services/models/image_record.py +++ b/invokeai/app/services/models/image_record.py @@ -1,7 +1,7 @@ import datetime from typing import Optional, Union -from pydantic import BaseModel, Extra, Field, StrictStr -from invokeai.app.models.image import ImageCategory, ImageType +from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr +from invokeai.app.models.image import ImageCategory, ResourceOrigin from invokeai.app.models.metadata import ImageMetadata from invokeai.app.util.misc import get_iso_timestamp @@ -11,8 +11,8 @@ class ImageRecord(BaseModel): image_name: str = Field(description="The unique name of the image.") """The unique name of the image.""" - image_type: ImageType = Field(description="The type of the image.") - """The type of the image.""" + image_origin: ResourceOrigin = Field(description="The type of the image.") + """The origin of the image.""" image_category: ImageCategory = Field(description="The category of the image.") """The category of the image.""" width: int = Field(description="The width of the image in px.") @@ -56,6 +56,7 @@ class ImageRecordChanges(BaseModel, extra=Extra.forbid): Only limited changes are valid: - `image_category`: change the category of an image - `session_id`: change the session associated with an image + - `is_intermediate`: change the image's `is_intermediate` flag """ image_category: Optional[ImageCategory] = Field( @@ -67,6 +68,10 @@ class ImageRecordChanges(BaseModel, extra=Extra.forbid): description="The image's new session ID.", ) """The image's new session ID.""" + is_intermediate: Optional[StrictBool] = Field( + default=None, description="The image's new `is_intermediate` flag." + ) + """The image's new `is_intermediate` flag.""" class ImageUrlsDTO(BaseModel): @@ -74,8 +79,6 @@ class ImageUrlsDTO(BaseModel): image_name: str = Field(description="The unique name of the image.") """The unique name of the image.""" - image_type: ImageType = Field(description="The type of the image.") - """The type of the image.""" image_url: str = Field(description="The URL of the image.") """The URL of the image.""" thumbnail_url: str = Field(description="The URL of the image's thumbnail.") @@ -83,19 +86,24 @@ class ImageUrlsDTO(BaseModel): class ImageDTO(ImageRecord, ImageUrlsDTO): - """Deserialized image record, enriched for the frontend with URLs.""" + """Deserialized image record, enriched for the frontend.""" + board_id: Union[str, None] = Field( + description="The id of the board the image belongs to, if one exists." + ) + """The id of the board the image belongs to, if one exists.""" pass def image_record_to_dto( - image_record: ImageRecord, image_url: str, thumbnail_url: str + image_record: ImageRecord, image_url: str, thumbnail_url: str, board_id: Union[str, None] ) -> ImageDTO: """Converts an image record to an image DTO.""" return ImageDTO( **image_record.dict(), image_url=image_url, thumbnail_url=thumbnail_url, + board_id=board_id, ) @@ -105,7 +113,9 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: # Retrieve all the values, setting "reasonable" defaults if they are not present. image_name = image_dict.get("image_name", "unknown") - image_type = ImageType(image_dict.get("image_type", ImageType.RESULT.value)) + image_origin = ResourceOrigin( + image_dict.get("image_origin", ResourceOrigin.INTERNAL.value) + ) image_category = ImageCategory( image_dict.get("image_category", ImageCategory.GENERAL.value) ) @@ -127,7 +137,7 @@ def deserialize_image_record(image_dict: dict) -> ImageRecord: return ImageRecord( image_name=image_name, - image_type=image_type, + image_origin=image_origin, image_category=image_category, width=width, height=height, diff --git a/invokeai/app/services/resource_name.py b/invokeai/app/services/resource_name.py new file mode 100644 index 0000000000..dd5a76cfc0 --- /dev/null +++ b/invokeai/app/services/resource_name.py @@ -0,0 +1,30 @@ +from abc import ABC, abstractmethod +from enum import Enum, EnumMeta +import uuid + + +class ResourceType(str, Enum, metaclass=EnumMeta): + """Enum for resource types.""" + + IMAGE = "image" + LATENT = "latent" + + +class NameServiceBase(ABC): + """Low-level service responsible for naming resources (images, latents, etc).""" + + # TODO: Add customizable naming schemes + @abstractmethod + def create_image_name(self) -> str: + """Creates a name for an image.""" + pass + + +class SimpleNameService(NameServiceBase): + """Creates image names from UUIDs.""" + + # TODO: Add customizable naming schemes + def create_image_name(self) -> str: + uuid_str = str(uuid.uuid4()) + filename = f"{uuid_str}.png" + return filename diff --git a/invokeai/app/services/restoration_services.py b/invokeai/app/services/restoration_services.py index 7bd264444e..5ff0195ca5 100644 --- a/invokeai/app/services/restoration_services.py +++ b/invokeai/app/services/restoration_services.py @@ -16,13 +16,14 @@ class RestorationServices: gfpgan, codeformer, esrgan = None, None, None if args.restore or args.esrgan: restoration = Restoration() - if args.restore: + # TODO: redo for new model structure + if False and args.restore: gfpgan, codeformer = restoration.load_face_restore_models( args.gfpgan_model_path ) else: logger.info("Face restoration disabled") - if args.esrgan: + if False and args.esrgan: esrgan = restoration.load_esrgan(args.esrgan_bg_tile) else: logger.info("Upscaling disabled") diff --git a/invokeai/app/services/sqlite.py b/invokeai/app/services/sqlite.py index fd089014bb..a62fff88a1 100644 --- a/invokeai/app/services/sqlite.py +++ b/invokeai/app/services/sqlite.py @@ -26,7 +26,6 @@ class SqliteItemStorage(ItemStorageABC, Generic[T]): self._table_name = table_name self._id_field = id_field # TODO: validate that T has this field self._lock = Lock() - self._conn = sqlite3.connect( self._filename, check_same_thread=False ) # TODO: figure out a better threading solution diff --git a/invokeai/app/services/urls.py b/invokeai/app/services/urls.py index 2716da60ad..5920e9e6c1 100644 --- a/invokeai/app/services/urls.py +++ b/invokeai/app/services/urls.py @@ -1,17 +1,12 @@ import os from abc import ABC, abstractmethod -from invokeai.app.models.image import ImageType -from invokeai.app.util.thumbnails import get_thumbnail_name - class UrlServiceBase(ABC): """Responsible for building URLs for resources.""" @abstractmethod - def get_image_url( - self, image_type: ImageType, image_name: str, thumbnail: bool = False - ) -> str: + def get_image_url(self, image_name: str, thumbnail: bool = False) -> str: """Gets the URL for an image or thumbnail.""" pass @@ -20,15 +15,11 @@ class LocalUrlService(UrlServiceBase): def __init__(self, base_url: str = "api/v1"): self._base_url = base_url - def get_image_url( - self, image_type: ImageType, image_name: str, thumbnail: bool = False - ) -> str: + def get_image_url(self, image_name: str, thumbnail: bool = False) -> str: image_basename = os.path.basename(image_name) # These paths are determined by the routes in invokeai/app/api/routers/images.py if thumbnail: - return ( - f"{self._base_url}/images/{image_type.value}/{image_basename}/thumbnail" - ) + return f"{self._base_url}/images/{image_basename}/thumbnail" - return f"{self._base_url}/images/{image_type.value}/{image_basename}" + return f"{self._base_url}/images/{image_basename}" diff --git a/invokeai/app/util/step_callback.py b/invokeai/app/util/step_callback.py index 963e770406..b4b9a25909 100644 --- a/invokeai/app/util/step_callback.py +++ b/invokeai/app/util/step_callback.py @@ -1,5 +1,5 @@ -from invokeai.app.api.models.images import ProgressImage from invokeai.app.models.exceptions import CanceledException +from invokeai.app.models.image import ProgressImage from ..invocations.baseinvocation import InvocationContext from ...backend.util.util import image_to_dataURL from ...backend.generator.base import Generator diff --git a/invokeai/backend/__init__.py b/invokeai/backend/__init__.py index 0e1b6d3a0d..ff8b4bc8c5 100644 --- a/invokeai/backend/__init__.py +++ b/invokeai/backend/__init__.py @@ -5,9 +5,11 @@ from .generator import ( InvokeAIGeneratorBasicParams, InvokeAIGenerator, InvokeAIGeneratorOutput, - Txt2Img, Img2Img, Inpaint ) -from .model_management import ModelManager, SDModelComponent +from .model_management import ( + ModelManager, ModelCache, BaseModelType, + ModelType, SubModelType, ModelInfo + ) from .safety_checker import SafetyChecker diff --git a/invokeai/backend/config/model_install_backend.py b/invokeai/backend/config/model_install_backend.py deleted file mode 100644 index cb76f955bc..0000000000 --- a/invokeai/backend/config/model_install_backend.py +++ /dev/null @@ -1,465 +0,0 @@ -""" -Utility (backend) functions used by model_install.py -""" -import os -import re -import shutil -import sys -import warnings -from pathlib import Path -from tempfile import TemporaryFile -from typing import List - -import requests -from diffusers import AutoencoderKL -from huggingface_hub import hf_hub_url -from omegaconf import OmegaConf -from omegaconf.dictconfig import DictConfig -from tqdm import tqdm - -import invokeai.configs as configs - -from invokeai.app.services.config import get_invokeai_config -from ..model_management import ModelManager -from ..stable_diffusion import StableDiffusionGeneratorPipeline - - -warnings.filterwarnings("ignore") - -# --------------------------globals----------------------- -config = get_invokeai_config() -Model_dir = "models" -Weights_dir = "ldm/stable-diffusion-v1/" - -# the initial "configs" dir is now bundled in the `invokeai.configs` package -Dataset_path = Path(configs.__path__[0]) / "INITIAL_MODELS.yaml" - -# initial models omegaconf -Datasets = None - -Config_preamble = """ -# This file describes the alternative machine learning models -# available to InvokeAI script. -# -# To add a new model, follow the examples below. Each -# model requires a model config file, a weights file, -# and the width and height of the images it -# was trained on. -""" - - -def default_config_file(): - return config.model_conf_path - - -def sd_configs(): - return config.legacy_conf_path - -def initial_models(): - global Datasets - if Datasets: - return Datasets - return (Datasets := OmegaConf.load(Dataset_path)) - - -def install_requested_models( - install_initial_models: List[str] = None, - remove_models: List[str] = None, - scan_directory: Path = None, - external_models: List[str] = None, - scan_at_startup: bool = False, - precision: str = "float16", - purge_deleted: bool = False, - config_file_path: Path = None, -): - """ - Entry point for installing/deleting starter models, or installing external models. - """ - config_file_path = config_file_path or default_config_file() - if not config_file_path.exists(): - open(config_file_path, "w") - - model_manager = ModelManager(OmegaConf.load(config_file_path), precision=precision) - - if remove_models and len(remove_models) > 0: - print("== DELETING UNCHECKED STARTER MODELS ==") - for model in remove_models: - print(f"{model}...") - model_manager.del_model(model, delete_files=purge_deleted) - model_manager.commit(config_file_path) - - if install_initial_models and len(install_initial_models) > 0: - print("== INSTALLING SELECTED STARTER MODELS ==") - successfully_downloaded = download_weight_datasets( - models=install_initial_models, - access_token=None, - precision=precision, - ) # FIX: for historical reasons, we don't use model manager here - update_config_file(successfully_downloaded, config_file_path) - if len(successfully_downloaded) < len(install_initial_models): - print("** Some of the model downloads were not successful") - - # due to above, we have to reload the model manager because conf file - # was changed behind its back - model_manager = ModelManager(OmegaConf.load(config_file_path), precision=precision) - - external_models = external_models or list() - if scan_directory: - external_models.append(str(scan_directory)) - - if len(external_models) > 0: - print("== INSTALLING EXTERNAL MODELS ==") - for path_url_or_repo in external_models: - try: - model_manager.heuristic_import( - path_url_or_repo, - commit_to_conf=config_file_path, - ) - except KeyboardInterrupt: - sys.exit(-1) - except Exception: - pass - - if scan_at_startup and scan_directory.is_dir(): - argument = "--autoconvert" - print('** The global initfile is no longer supported; rewrite to support new yaml format **') - initfile = Path(config.root, 'invokeai.init') - replacement = Path(config.root, f"invokeai.init.new") - directory = str(scan_directory).replace("\\", "/") - with open(initfile, "r") as input: - with open(replacement, "w") as output: - while line := input.readline(): - if not line.startswith(argument): - output.writelines([line]) - output.writelines([f"{argument} {directory}"]) - os.replace(replacement, initfile) - - -# ------------------------------------- -def yes_or_no(prompt: str, default_yes=True): - default = "y" if default_yes else "n" - response = input(f"{prompt} [{default}] ") or default - if default_yes: - return response[0] not in ("n", "N") - else: - return response[0] in ("y", "Y") - - -# ------------------------------------- -def get_root(root: str = None) -> str: - if root: - return root - elif os.environ.get("INVOKEAI_ROOT"): - return os.environ.get("INVOKEAI_ROOT") - else: - return config.root - - -# --------------------------------------------- -def recommended_datasets() -> dict: - datasets = dict() - for ds in initial_models().keys(): - if initial_models()[ds].get("recommended", False): - datasets[ds] = True - return datasets - - -# --------------------------------------------- -def default_dataset() -> dict: - datasets = dict() - for ds in initial_models().keys(): - if initial_models()[ds].get("default", False): - datasets[ds] = True - return datasets - - -# --------------------------------------------- -def all_datasets() -> dict: - datasets = dict() - for ds in initial_models().keys(): - datasets[ds] = True - return datasets - - -# --------------------------------------------- -# look for legacy model.ckpt in models directory and offer to -# normalize its name -def migrate_models_ckpt(): - model_path = os.path.join(config.root, Model_dir, Weights_dir) - if not os.path.exists(os.path.join(model_path, "model.ckpt")): - return - new_name = initial_models()["stable-diffusion-1.4"]["file"] - print( - 'The Stable Diffusion v4.1 "model.ckpt" is already installed. The name will be changed to {new_name} to avoid confusion.' - ) - print(f"model.ckpt => {new_name}") - os.replace( - os.path.join(model_path, "model.ckpt"), os.path.join(model_path, new_name) - ) - - -# --------------------------------------------- -def download_weight_datasets( - models: List[str], access_token: str, precision: str = "float32" -): - migrate_models_ckpt() - successful = dict() - for mod in models: - print(f"Downloading {mod}:") - successful[mod] = _download_repo_or_file( - initial_models()[mod], access_token, precision=precision - ) - return successful - - -def _download_repo_or_file( - mconfig: DictConfig, access_token: str, precision: str = "float32" -) -> Path: - path = None - if mconfig["format"] == "ckpt": - path = _download_ckpt_weights(mconfig, access_token) - else: - path = _download_diffusion_weights(mconfig, access_token, precision=precision) - if "vae" in mconfig and "repo_id" in mconfig["vae"]: - _download_diffusion_weights( - mconfig["vae"], access_token, precision=precision - ) - return path - - -def _download_ckpt_weights(mconfig: DictConfig, access_token: str) -> Path: - repo_id = mconfig["repo_id"] - filename = mconfig["file"] - cache_dir = os.path.join(config.root, Model_dir, Weights_dir) - return hf_download_with_resume( - repo_id=repo_id, - model_dir=cache_dir, - model_name=filename, - access_token=access_token, - ) - - -# --------------------------------------------- -def download_from_hf( - model_class: object, model_name: str, **kwargs -): - path = config.cache_dir - model = model_class.from_pretrained( - model_name, - cache_dir=path, - resume_download=True, - **kwargs, - ) - model_name = "--".join(("models", *model_name.split("/"))) - return path / model_name if model else None - - -def _download_diffusion_weights( - mconfig: DictConfig, access_token: str, precision: str = "float32" -): - repo_id = mconfig["repo_id"] - model_class = ( - StableDiffusionGeneratorPipeline - if mconfig.get("format", None) == "diffusers" - else AutoencoderKL - ) - extra_arg_list = [{"revision": "fp16"}, {}] if precision == "float16" else [{}] - path = None - for extra_args in extra_arg_list: - try: - path = download_from_hf( - model_class, - repo_id, - safety_checker=None, - **extra_args, - ) - except OSError as e: - if str(e).startswith("fp16 is not a valid"): - pass - else: - print(f"An unexpected error occurred while downloading the model: {e})") - if path: - break - return path - - -# --------------------------------------------- -def hf_download_with_resume( - repo_id: str, model_dir: str, model_name: str, access_token: str = None -) -> Path: - model_dest = Path(os.path.join(model_dir, model_name)) - os.makedirs(model_dir, exist_ok=True) - - url = hf_hub_url(repo_id, model_name) - - header = {"Authorization": f"Bearer {access_token}"} if access_token else {} - open_mode = "wb" - exist_size = 0 - - if os.path.exists(model_dest): - exist_size = os.path.getsize(model_dest) - header["Range"] = f"bytes={exist_size}-" - open_mode = "ab" - - resp = requests.get(url, headers=header, stream=True) - total = int(resp.headers.get("content-length", 0)) - - if ( - resp.status_code == 416 - ): # "range not satisfiable", which means nothing to return - print(f"* {model_name}: complete file found. Skipping.") - return model_dest - elif resp.status_code != 200: - print(f"** An error occurred during downloading {model_name}: {resp.reason}") - elif exist_size > 0: - print(f"* {model_name}: partial file found. Resuming...") - else: - print(f"* {model_name}: Downloading...") - - try: - if total < 2000: - print(f"*** ERROR DOWNLOADING {model_name}: {resp.text}") - return None - - with open(model_dest, open_mode) as file, tqdm( - desc=model_name, - initial=exist_size, - total=total + exist_size, - unit="iB", - unit_scale=True, - unit_divisor=1000, - ) as bar: - for data in resp.iter_content(chunk_size=1024): - size = file.write(data) - bar.update(size) - except Exception as e: - print(f"An error occurred while downloading {model_name}: {str(e)}") - return None - return model_dest - - -# --------------------------------------------- -def update_config_file(successfully_downloaded: dict, config_file: Path): - config_file = ( - Path(config_file) if config_file is not None else default_config_file() - ) - - # In some cases (incomplete setup, etc), the default configs directory might be missing. - # Create it if it doesn't exist. - # this check is ignored if opt.config_file is specified - user is assumed to know what they - # are doing if they are passing a custom config file from elsewhere. - if config_file is default_config_file() and not config_file.parent.exists(): - configs_src = Dataset_path.parent - configs_dest = default_config_file().parent - shutil.copytree(configs_src, configs_dest, dirs_exist_ok=True) - - yaml = new_config_file_contents(successfully_downloaded, config_file) - - try: - backup = None - if os.path.exists(config_file): - print( - f"** {config_file.name} exists. Renaming to {config_file.stem}.yaml.orig" - ) - backup = config_file.with_suffix(".yaml.orig") - ## Ugh. Windows is unable to overwrite an existing backup file, raises a WinError 183 - if sys.platform == "win32" and backup.is_file(): - backup.unlink() - config_file.rename(backup) - - with TemporaryFile() as tmp: - tmp.write(Config_preamble.encode()) - tmp.write(yaml.encode()) - - with open(str(config_file.expanduser().resolve()), "wb") as new_config: - tmp.seek(0) - new_config.write(tmp.read()) - - except Exception as e: - print(f"**Error creating config file {config_file}: {str(e)} **") - if backup is not None: - print("restoring previous config file") - ## workaround, for WinError 183, see above - if sys.platform == "win32" and config_file.is_file(): - config_file.unlink() - backup.rename(config_file) - return - - print(f"Successfully created new configuration file {config_file}") - - -# --------------------------------------------- -def new_config_file_contents( - successfully_downloaded: dict, - config_file: Path, -) -> str: - if config_file.exists(): - conf = OmegaConf.load(str(config_file.expanduser().resolve())) - else: - conf = OmegaConf.create() - - default_selected = None - for model in successfully_downloaded: - # a bit hacky - what we are doing here is seeing whether a checkpoint - # version of the model was previously defined, and whether the current - # model is a diffusers (indicated with a path) - if conf.get(model) and Path(successfully_downloaded[model]).is_dir(): - delete_weights(model, conf[model]) - - stanza = {} - mod = initial_models()[model] - stanza["description"] = mod["description"] - stanza["repo_id"] = mod["repo_id"] - stanza["format"] = mod["format"] - # diffusers don't need width and height (probably .ckpt doesn't either) - # so we no longer require these in INITIAL_MODELS.yaml - if "width" in mod: - stanza["width"] = mod["width"] - if "height" in mod: - stanza["height"] = mod["height"] - if "file" in mod: - stanza["weights"] = os.path.relpath( - successfully_downloaded[model], start=config.root - ) - stanza["config"] = os.path.normpath( - os.path.join(sd_configs(), mod["config"]) - ) - if "vae" in mod: - if "file" in mod["vae"]: - stanza["vae"] = os.path.normpath( - os.path.join(Model_dir, Weights_dir, mod["vae"]["file"]) - ) - else: - stanza["vae"] = mod["vae"] - if mod.get("default", False): - stanza["default"] = True - default_selected = True - - conf[model] = stanza - - # if no default model was chosen, then we select the first - # one in the list - if not default_selected: - conf[list(successfully_downloaded.keys())[0]]["default"] = True - - return OmegaConf.to_yaml(conf) - - -# --------------------------------------------- -def delete_weights(model_name: str, conf_stanza: dict): - if not (weights := conf_stanza.get("weights")): - return - if re.match("/VAE/", conf_stanza.get("config")): - return - - print( - f"\n** The checkpoint version of {model_name} is superseded by the diffusers version. Deleting the original file {weights}?" - ) - - weights = Path(weights) - if not weights.is_absolute(): - weights = Path(config.root) / weights - try: - weights.unlink() - except OSError as e: - print(str(e)) diff --git a/invokeai/backend/generator/__init__.py b/invokeai/backend/generator/__init__.py index 9d6263453a..8a7f1c9167 100644 --- a/invokeai/backend/generator/__init__.py +++ b/invokeai/backend/generator/__init__.py @@ -5,7 +5,6 @@ from .base import ( InvokeAIGenerator, InvokeAIGeneratorBasicParams, InvokeAIGeneratorOutput, - Txt2Img, Img2Img, Inpaint, Generator, diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index 6f2f33e6af..462b1a4f4b 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -29,7 +29,6 @@ import invokeai.backend.util.logging as logger from ..image_util import configure_model_padding from ..util.util import rand_perlin_2d from ..safety_checker import SafetyChecker -from ..prompting.conditioning import get_uc_and_c_and_ec from ..stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline from ..stable_diffusion.schedulers import SCHEDULER_MAP @@ -81,13 +80,15 @@ class InvokeAIGenerator(metaclass=ABCMeta): self.params=params self.kwargs = kwargs - def generate(self, - prompt: str='', - callback: Optional[Callable]=None, - step_callback: Optional[Callable]=None, - iterations: int=1, - **keyword_args, - )->Iterator[InvokeAIGeneratorOutput]: + def generate( + self, + conditioning: tuple, + scheduler, + callback: Optional[Callable]=None, + step_callback: Optional[Callable]=None, + iterations: int=1, + **keyword_args, + )->Iterator[InvokeAIGeneratorOutput]: ''' Return an iterator across the indicated number of generations. Each time the iterator is called it will return an InvokeAIGeneratorOutput @@ -113,54 +114,46 @@ class InvokeAIGenerator(metaclass=ABCMeta): generator_args.update(keyword_args) model_info = self.model_info - model_name = model_info['model_name'] - model:StableDiffusionGeneratorPipeline = model_info['model'] - model_hash = model_info['hash'] - scheduler: Scheduler = self.get_scheduler( - model=model, - scheduler_name=generator_args.get('scheduler') - ) + model_name = model_info.name + model_hash = model_info.hash + with model_info.context as model: + gen_class = self._generator_class() + generator = gen_class(model, self.params.precision, **self.kwargs) + if self.params.variation_amount > 0: + generator.set_variation(generator_args.get('seed'), + generator_args.get('variation_amount'), + generator_args.get('with_variations') + ) - # get conditioning from prompt via Compel package - uc, c, extra_conditioning_info = get_uc_and_c_and_ec(prompt, model=model) - - gen_class = self._generator_class() - generator = gen_class(model, self.params.precision, **self.kwargs) - if self.params.variation_amount > 0: - generator.set_variation(generator_args.get('seed'), - generator_args.get('variation_amount'), - generator_args.get('with_variations') - ) - - if isinstance(model, DiffusionPipeline): - for component in [model.unet, model.vae]: - configure_model_padding(component, + if isinstance(model, DiffusionPipeline): + for component in [model.unet, model.vae]: + configure_model_padding(component, + generator_args.get('seamless',False), + generator_args.get('seamless_axes') + ) + else: + configure_model_padding(model, generator_args.get('seamless',False), generator_args.get('seamless_axes') ) - else: - configure_model_padding(model, - generator_args.get('seamless',False), - generator_args.get('seamless_axes') - ) - iteration_count = range(iterations) if iterations else itertools.count(start=0, step=1) - for i in iteration_count: - results = generator.generate(prompt, - conditioning=(uc, c, extra_conditioning_info), - step_callback=step_callback, - sampler=scheduler, - **generator_args, - ) - output = InvokeAIGeneratorOutput( - image=results[0][0], - seed=results[0][1], - attention_maps_images=results[0][2], - model_hash = model_hash, - params=Namespace(model_name=model_name,**generator_args), - ) - if callback: - callback(output) + iteration_count = range(iterations) if iterations else itertools.count(start=0, step=1) + for i in iteration_count: + results = generator.generate( + conditioning=conditioning, + step_callback=step_callback, + sampler=scheduler, + **generator_args, + ) + output = InvokeAIGeneratorOutput( + image=results[0][0], + seed=results[0][1], + attention_maps_images=results[0][2], + model_hash = model_hash, + params=Namespace(model_name=model_name,**generator_args), + ) + if callback: + callback(output) yield output @classmethod @@ -173,20 +166,6 @@ class InvokeAIGenerator(metaclass=ABCMeta): def load_generator(self, model: StableDiffusionGeneratorPipeline, generator_class: Type[Generator]): return generator_class(model, self.params.precision) - def get_scheduler(self, scheduler_name:str, model: StableDiffusionGeneratorPipeline)->Scheduler: - scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP['ddim']) - - scheduler_config = model.scheduler.config - if "_backup" in scheduler_config: - scheduler_config = scheduler_config["_backup"] - scheduler_config = {**scheduler_config, **scheduler_extra_config, "_backup": scheduler_config} - scheduler = scheduler_class.from_config(scheduler_config) - - # hack copied over from generate.py - if not hasattr(scheduler, 'uses_inpainting_model'): - scheduler.uses_inpainting_model = lambda: False - return scheduler - @classmethod def _generator_class(cls)->Type[Generator]: ''' @@ -196,13 +175,6 @@ class InvokeAIGenerator(metaclass=ABCMeta): ''' return Generator -# ------------------------------------ -class Txt2Img(InvokeAIGenerator): - @classmethod - def _generator_class(cls): - from .txt2img import Txt2Img - return Txt2Img - # ------------------------------------ class Img2Img(InvokeAIGenerator): def generate(self, @@ -256,25 +228,6 @@ class Inpaint(Img2Img): from .inpaint import Inpaint return Inpaint -# ------------------------------------ -class Embiggen(Txt2Img): - def generate( - self, - embiggen: list=None, - embiggen_tiles: list = None, - strength: float=0.75, - **kwargs)->Iterator[InvokeAIGeneratorOutput]: - return super().generate(embiggen=embiggen, - embiggen_tiles=embiggen_tiles, - strength=strength, - **kwargs) - - @classmethod - def _generator_class(cls): - from .embiggen import Embiggen - return Embiggen - - class Generator: downsampling_factor: int latent_channels: int @@ -285,7 +238,7 @@ class Generator: self.model = model self.precision = precision self.seed = None - self.latent_channels = model.channels + self.latent_channels = model.unet.config.in_channels self.downsampling_factor = downsampling # BUG: should come from model or config self.safety_checker = None self.perlin = 0.0 @@ -296,7 +249,7 @@ class Generator: self.free_gpu_mem = None # this is going to be overridden in img2img.py, txt2img.py and inpaint.py - def get_make_image(self, prompt, **kwargs): + def get_make_image(self, **kwargs): """ Returns a function returning an image derived from the prompt and the initial image Return value depends on the seed at the time you call it @@ -312,7 +265,6 @@ class Generator: def generate( self, - prompt, width, height, sampler, @@ -337,7 +289,6 @@ class Generator: saver.get_stacked_maps_image() ) make_image = self.get_make_image( - prompt, sampler=sampler, init_image=init_image, width=width, diff --git a/invokeai/backend/generator/embiggen.py b/invokeai/backend/generator/embiggen.py deleted file mode 100644 index 6eae5732b0..0000000000 --- a/invokeai/backend/generator/embiggen.py +++ /dev/null @@ -1,559 +0,0 @@ -""" -invokeai.backend.generator.embiggen descends from .generator -and generates with .generator.img2img -""" - -import numpy as np -import torch -from PIL import Image -from tqdm import trange - -import invokeai.backend.util.logging as logger - -from .base import Generator -from .img2img import Img2Img - -class Embiggen(Generator): - def __init__(self, model, precision): - super().__init__(model, precision) - self.init_latent = None - - # Replace generate because Embiggen doesn't need/use most of what it does normallly - def generate( - self, - prompt, - iterations=1, - seed=None, - image_callback=None, - step_callback=None, - **kwargs, - ): - make_image = self.get_make_image(prompt, step_callback=step_callback, **kwargs) - results = [] - seed = seed if seed else self.new_seed() - - # Noise will be generated by the Img2Img generator when called - for _ in trange(iterations, desc="Generating"): - # make_image will call Img2Img which will do the equivalent of get_noise itself - image = make_image() - results.append([image, seed]) - if image_callback is not None: - image_callback(image, seed, prompt_in=prompt) - seed = self.new_seed() - return results - - @torch.no_grad() - def get_make_image( - self, - prompt, - sampler, - steps, - cfg_scale, - ddim_eta, - conditioning, - init_img, - strength, - width, - height, - embiggen, - embiggen_tiles, - step_callback=None, - **kwargs, - ): - """ - Returns a function returning an image derived from the prompt and multi-stage twice-baked potato layering over the img2img on the initial image - Return value depends on the seed at the time you call it - """ - assert ( - not sampler.uses_inpainting_model() - ), "--embiggen is not supported by inpainting models" - - # Construct embiggen arg array, and sanity check arguments - if embiggen == None: # embiggen can also be called with just embiggen_tiles - embiggen = [1.0] # If not specified, assume no scaling - elif embiggen[0] < 0: - embiggen[0] = 1.0 - logger.warning( - "Embiggen scaling factor cannot be negative, fell back to the default of 1.0 !" - ) - if len(embiggen) < 2: - embiggen.append(0.75) - elif embiggen[1] > 1.0 or embiggen[1] < 0: - embiggen[1] = 0.75 - logger.warning( - "Embiggen upscaling strength for ESRGAN must be between 0 and 1, fell back to the default of 0.75 !" - ) - if len(embiggen) < 3: - embiggen.append(0.25) - elif embiggen[2] < 0: - embiggen[2] = 0.25 - logger.warning( - "Overlap size for Embiggen must be a positive ratio between 0 and 1 OR a number of pixels, fell back to the default of 0.25 !" - ) - - # Convert tiles from their user-freindly count-from-one to count-from-zero, because we need to do modulo math - # and then sort them, because... people. - if embiggen_tiles: - embiggen_tiles = list(map(lambda n: n - 1, embiggen_tiles)) - embiggen_tiles.sort() - - if strength >= 0.5: - logger.warning( - f"Embiggen may produce mirror motifs if the strength (-f) is too high (currently {strength}). Try values between 0.35-0.45." - ) - - # Prep img2img generator, since we wrap over it - gen_img2img = Img2Img(self.model, self.precision) - - # Open original init image (not a tensor) to manipulate - initsuperimage = Image.open(init_img) - - with Image.open(init_img) as img: - initsuperimage = img.convert("RGB") - - # Size of the target super init image in pixels - initsuperwidth, initsuperheight = initsuperimage.size - - # Increase by scaling factor if not already resized, using ESRGAN as able - if embiggen[0] != 1.0: - initsuperwidth = round(initsuperwidth * embiggen[0]) - initsuperheight = round(initsuperheight * embiggen[0]) - if embiggen[1] > 0: # No point in ESRGAN upscaling if strength is set zero - from ..restoration.realesrgan import ESRGAN - - esrgan = ESRGAN() - logger.info( - f"ESRGAN upscaling init image prior to cutting with Embiggen with strength {embiggen[1]}" - ) - if embiggen[0] > 2: - initsuperimage = esrgan.process( - initsuperimage, - embiggen[1], # upscale strength - self.seed, - 4, # upscale scale - ) - else: - initsuperimage = esrgan.process( - initsuperimage, - embiggen[1], # upscale strength - self.seed, - 2, # upscale scale - ) - # We could keep recursively re-running ESRGAN for a requested embiggen[0] larger than 4x - # but from personal experiance it doesn't greatly improve anything after 4x - # Resize to target scaling factor resolution - initsuperimage = initsuperimage.resize( - (initsuperwidth, initsuperheight), Image.Resampling.LANCZOS - ) - - # Use width and height as tile widths and height - # Determine buffer size in pixels - if embiggen[2] < 1: - if embiggen[2] < 0: - embiggen[2] = 0 - overlap_size_x = round(embiggen[2] * width) - overlap_size_y = round(embiggen[2] * height) - else: - overlap_size_x = round(embiggen[2]) - overlap_size_y = round(embiggen[2]) - - # With overall image width and height known, determine how many tiles we need - def ceildiv(a, b): - return -1 * (-a // b) - - # X and Y needs to be determined independantly (we may have savings on one based on the buffer pixel count) - # (initsuperwidth - width) is the area remaining to the right that we need to layers tiles to fill - # (width - overlap_size_x) is how much new we can fill with a single tile - emb_tiles_x = 1 - emb_tiles_y = 1 - if (initsuperwidth - width) > 0: - emb_tiles_x = ceildiv(initsuperwidth - width, width - overlap_size_x) + 1 - if (initsuperheight - height) > 0: - emb_tiles_y = ceildiv(initsuperheight - height, height - overlap_size_y) + 1 - # Sanity - assert ( - emb_tiles_x > 1 or emb_tiles_y > 1 - ), f"ERROR: Based on the requested dimensions of {initsuperwidth}x{initsuperheight} and tiles of {width}x{height} you don't need to Embiggen! Check your arguments." - - # Prep alpha layers -------------- - # https://stackoverflow.com/questions/69321734/how-to-create-different-transparency-like-gradient-with-python-pil - # agradientL is Left-side transparent - agradientL = ( - Image.linear_gradient("L").rotate(90).resize((overlap_size_x, height)) - ) - # agradientT is Top-side transparent - agradientT = Image.linear_gradient("L").resize((width, overlap_size_y)) - # radial corner is the left-top corner, made full circle then cut to just the left-top quadrant - agradientC = Image.new("L", (256, 256)) - for y in range(256): - for x in range(256): - # Find distance to lower right corner (numpy takes arrays) - distanceToLR = np.sqrt([(255 - x) ** 2 + (255 - y) ** 2])[0] - # Clamp values to max 255 - if distanceToLR > 255: - distanceToLR = 255 - # Place the pixel as invert of distance - agradientC.putpixel((x, y), round(255 - distanceToLR)) - - # Create alternative asymmetric diagonal corner to use on "tailing" intersections to prevent hard edges - # Fits for a left-fading gradient on the bottom side and full opacity on the right side. - agradientAsymC = Image.new("L", (256, 256)) - for y in range(256): - for x in range(256): - value = round(max(0, x - (255 - y)) * (255 / max(1, y))) - # Clamp values - value = max(0, value) - value = min(255, value) - agradientAsymC.putpixel((x, y), value) - - # Create alpha layers default fully white - alphaLayerL = Image.new("L", (width, height), 255) - alphaLayerT = Image.new("L", (width, height), 255) - alphaLayerLTC = Image.new("L", (width, height), 255) - # Paste gradients into alpha layers - alphaLayerL.paste(agradientL, (0, 0)) - alphaLayerT.paste(agradientT, (0, 0)) - alphaLayerLTC.paste(agradientL, (0, 0)) - alphaLayerLTC.paste(agradientT, (0, 0)) - alphaLayerLTC.paste(agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0)) - # make masks with an asymmetric upper-right corner so when the curved transparent corner of the next tile - # to its right is placed it doesn't reveal a hard trailing semi-transparent edge in the overlapping space - alphaLayerTaC = alphaLayerT.copy() - alphaLayerTaC.paste( - agradientAsymC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - alphaLayerLTaC = alphaLayerLTC.copy() - alphaLayerLTaC.paste( - agradientAsymC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - - if embiggen_tiles: - # Individual unconnected sides - alphaLayerR = Image.new("L", (width, height), 255) - alphaLayerR.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerB = Image.new("L", (width, height), 255) - alphaLayerB.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerTB = Image.new("L", (width, height), 255) - alphaLayerTB.paste(agradientT, (0, 0)) - alphaLayerTB.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerLR = Image.new("L", (width, height), 255) - alphaLayerLR.paste(agradientL, (0, 0)) - alphaLayerLR.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - - # Sides and corner Layers - alphaLayerRBC = Image.new("L", (width, height), 255) - alphaLayerRBC.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerRBC.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerRBC.paste( - agradientC.rotate(180).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, height - overlap_size_y), - ) - alphaLayerLBC = Image.new("L", (width, height), 255) - alphaLayerLBC.paste(agradientL, (0, 0)) - alphaLayerLBC.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerLBC.paste( - agradientC.rotate(90).resize((overlap_size_x, overlap_size_y)), - (0, height - overlap_size_y), - ) - alphaLayerRTC = Image.new("L", (width, height), 255) - alphaLayerRTC.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerRTC.paste(agradientT, (0, 0)) - alphaLayerRTC.paste( - agradientC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - - # All but X layers - alphaLayerABT = Image.new("L", (width, height), 255) - alphaLayerABT.paste(alphaLayerLBC, (0, 0)) - alphaLayerABT.paste(agradientL.rotate(180), (width - overlap_size_x, 0)) - alphaLayerABT.paste( - agradientC.rotate(180).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, height - overlap_size_y), - ) - alphaLayerABL = Image.new("L", (width, height), 255) - alphaLayerABL.paste(alphaLayerRTC, (0, 0)) - alphaLayerABL.paste(agradientT.rotate(180), (0, height - overlap_size_y)) - alphaLayerABL.paste( - agradientC.rotate(180).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, height - overlap_size_y), - ) - alphaLayerABR = Image.new("L", (width, height), 255) - alphaLayerABR.paste(alphaLayerLBC, (0, 0)) - alphaLayerABR.paste(agradientT, (0, 0)) - alphaLayerABR.paste( - agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0) - ) - alphaLayerABB = Image.new("L", (width, height), 255) - alphaLayerABB.paste(alphaLayerRTC, (0, 0)) - alphaLayerABB.paste(agradientL, (0, 0)) - alphaLayerABB.paste( - agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0) - ) - - # All-around layer - alphaLayerAA = Image.new("L", (width, height), 255) - alphaLayerAA.paste(alphaLayerABT, (0, 0)) - alphaLayerAA.paste(agradientT, (0, 0)) - alphaLayerAA.paste( - agradientC.resize((overlap_size_x, overlap_size_y)), (0, 0) - ) - alphaLayerAA.paste( - agradientC.rotate(270).resize((overlap_size_x, overlap_size_y)), - (width - overlap_size_x, 0), - ) - - # Clean up temporary gradients - del agradientL - del agradientT - del agradientC - - def make_image(): - # Make main tiles ------------------------------------------------- - if embiggen_tiles: - logger.info(f"Making {len(embiggen_tiles)} Embiggen tiles...") - else: - logger.info( - f"Making {(emb_tiles_x * emb_tiles_y)} Embiggen tiles ({emb_tiles_x}x{emb_tiles_y})..." - ) - - emb_tile_store = [] - # Although we could use the same seed for every tile for determinism, at higher strengths this may - # produce duplicated structures for each tile and make the tiling effect more obvious - # instead track and iterate a local seed we pass to Img2Img - seed = self.seed - seedintlimit = ( - np.iinfo(np.uint32).max - 1 - ) # only retreive this one from numpy - - for tile in range(emb_tiles_x * emb_tiles_y): - # Don't iterate on first tile - if tile != 0: - if seed < seedintlimit: - seed += 1 - else: - seed = 0 - - # Determine if this is a re-run and replace - if embiggen_tiles and not tile in embiggen_tiles: - continue - # Get row and column entries - emb_row_i = tile // emb_tiles_x - emb_column_i = tile % emb_tiles_x - # Determine bounds to cut up the init image - # Determine upper-left point - if emb_column_i + 1 == emb_tiles_x: - left = initsuperwidth - width - else: - left = round(emb_column_i * (width - overlap_size_x)) - if emb_row_i + 1 == emb_tiles_y: - top = initsuperheight - height - else: - top = round(emb_row_i * (height - overlap_size_y)) - right = left + width - bottom = top + height - - # Cropped image of above dimension (does not modify the original) - newinitimage = initsuperimage.crop((left, top, right, bottom)) - # DEBUG: - # newinitimagepath = init_img[0:-4] + f'_emb_Ti{tile}.png' - # newinitimage.save(newinitimagepath) - - if embiggen_tiles: - logger.debug( - f"Making tile #{tile + 1} ({embiggen_tiles.index(tile) + 1} of {len(embiggen_tiles)} requested)" - ) - else: - logger.debug(f"Starting {tile + 1} of {(emb_tiles_x * emb_tiles_y)} tiles") - - # create a torch tensor from an Image - newinitimage = np.array(newinitimage).astype(np.float32) / 255.0 - newinitimage = newinitimage[None].transpose(0, 3, 1, 2) - newinitimage = torch.from_numpy(newinitimage) - newinitimage = 2.0 * newinitimage - 1.0 - newinitimage = newinitimage.to(self.model.device) - clear_cuda_cache = ( - kwargs["clear_cuda_cache"] if "clear_cuda_cache" in kwargs else None - ) - - tile_results = gen_img2img.generate( - prompt, - iterations=1, - seed=seed, - sampler=sampler, - steps=steps, - cfg_scale=cfg_scale, - conditioning=conditioning, - ddim_eta=ddim_eta, - image_callback=None, # called only after the final image is generated - step_callback=step_callback, # called after each intermediate image is generated - width=width, - height=height, - init_image=newinitimage, # notice that init_image is different from init_img - mask_image=None, - strength=strength, - clear_cuda_cache=clear_cuda_cache, - ) - - emb_tile_store.append(tile_results[0][0]) - # DEBUG (but, also has other uses), worth saving if you want tiles without a transparency overlap to manually composite - # emb_tile_store[-1].save(init_img[0:-4] + f'_emb_To{tile}.png') - del newinitimage - - # Sanity check we have them all - if len(emb_tile_store) == (emb_tiles_x * emb_tiles_y) or ( - embiggen_tiles != [] and len(emb_tile_store) == len(embiggen_tiles) - ): - outputsuperimage = Image.new("RGBA", (initsuperwidth, initsuperheight)) - if embiggen_tiles: - outputsuperimage.alpha_composite( - initsuperimage.convert("RGBA"), (0, 0) - ) - for tile in range(emb_tiles_x * emb_tiles_y): - if embiggen_tiles: - if tile in embiggen_tiles: - intileimage = emb_tile_store.pop(0) - else: - continue - else: - intileimage = emb_tile_store[tile] - intileimage = intileimage.convert("RGBA") - # Get row and column entries - emb_row_i = tile // emb_tiles_x - emb_column_i = tile % emb_tiles_x - if emb_row_i == 0 and emb_column_i == 0 and not embiggen_tiles: - left = 0 - top = 0 - else: - # Determine upper-left point - if emb_column_i + 1 == emb_tiles_x: - left = initsuperwidth - width - else: - left = round(emb_column_i * (width - overlap_size_x)) - if emb_row_i + 1 == emb_tiles_y: - top = initsuperheight - height - else: - top = round(emb_row_i * (height - overlap_size_y)) - # Handle gradients for various conditions - # Handle emb_rerun case - if embiggen_tiles: - # top of image - if emb_row_i == 0: - if emb_column_i == 0: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) not in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerB) - # Otherwise do nothing on this tile - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerR) - else: - intileimage.putalpha(alphaLayerRBC) - elif emb_column_i == emb_tiles_x - 1: - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerL) - else: - intileimage.putalpha(alphaLayerLBC) - else: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerL) - else: - intileimage.putalpha(alphaLayerLBC) - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerLR) - else: - intileimage.putalpha(alphaLayerABT) - # bottom of image - elif emb_row_i == emb_tiles_y - 1: - if emb_column_i == 0: - if (tile + 1) in embiggen_tiles: # Look-ahead right - intileimage.putalpha(alphaLayerTaC) - else: - intileimage.putalpha(alphaLayerRTC) - elif emb_column_i == emb_tiles_x - 1: - # No tiles to look ahead to - intileimage.putalpha(alphaLayerLTC) - else: - if (tile + 1) in embiggen_tiles: # Look-ahead right - intileimage.putalpha(alphaLayerLTaC) - else: - intileimage.putalpha(alphaLayerABB) - # vertical middle of image - else: - if emb_column_i == 0: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerTaC) - else: - intileimage.putalpha(alphaLayerTB) - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerRTC) - else: - intileimage.putalpha(alphaLayerABL) - elif emb_column_i == emb_tiles_x - 1: - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerLTC) - else: - intileimage.putalpha(alphaLayerABR) - else: - if (tile + 1) in embiggen_tiles: # Look-ahead right - if ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down - intileimage.putalpha(alphaLayerLTaC) - else: - intileimage.putalpha(alphaLayerABR) - elif ( - tile + emb_tiles_x - ) in embiggen_tiles: # Look-ahead down only - intileimage.putalpha(alphaLayerABB) - else: - intileimage.putalpha(alphaLayerAA) - # Handle normal tiling case (much simpler - since we tile left to right, top to bottom) - else: - if emb_row_i == 0 and emb_column_i >= 1: - intileimage.putalpha(alphaLayerL) - elif emb_row_i >= 1 and emb_column_i == 0: - if ( - emb_column_i + 1 == emb_tiles_x - ): # If we don't have anything that can be placed to the right - intileimage.putalpha(alphaLayerT) - else: - intileimage.putalpha(alphaLayerTaC) - else: - if ( - emb_column_i + 1 == emb_tiles_x - ): # If we don't have anything that can be placed to the right - intileimage.putalpha(alphaLayerLTC) - else: - intileimage.putalpha(alphaLayerLTaC) - # Layer tile onto final image - outputsuperimage.alpha_composite(intileimage, (left, top)) - else: - logger.error( - "Could not find all Embiggen output tiles in memory? Something must have gone wrong with img2img generation." - ) - - # after internal loops and patching up return Embiggen image - return outputsuperimage - - # end of function declaration - return make_image diff --git a/invokeai/backend/generator/img2img.py b/invokeai/backend/generator/img2img.py index 2c62bec4d6..1cfbeb66c0 100644 --- a/invokeai/backend/generator/img2img.py +++ b/invokeai/backend/generator/img2img.py @@ -22,7 +22,6 @@ class Img2Img(Generator): def get_make_image( self, - prompt, sampler, steps, cfg_scale, diff --git a/invokeai/backend/generator/inpaint.py b/invokeai/backend/generator/inpaint.py index a7fec83eb7..eaf4047109 100644 --- a/invokeai/backend/generator/inpaint.py +++ b/invokeai/backend/generator/inpaint.py @@ -161,9 +161,7 @@ class Inpaint(Img2Img): im: Image.Image, seam_size: int, seam_blur: int, - prompt, seed, - sampler, steps, cfg_scale, ddim_eta, @@ -177,8 +175,6 @@ class Inpaint(Img2Img): mask = self.mask_edge(hard_mask, seam_size, seam_blur) make_image = self.get_make_image( - prompt, - sampler, steps, cfg_scale, ddim_eta, @@ -203,8 +199,6 @@ class Inpaint(Img2Img): @torch.no_grad() def get_make_image( self, - prompt, - sampler, steps, cfg_scale, ddim_eta, @@ -306,7 +300,6 @@ class Inpaint(Img2Img): # noinspection PyTypeChecker pipeline: StableDiffusionGeneratorPipeline = self.model - pipeline.scheduler = sampler # todo: support cross-attention control uc, c, _ = conditioning @@ -345,9 +338,7 @@ class Inpaint(Img2Img): result, seam_size, seam_blur, - prompt, seed, - sampler, seam_steps, cfg_scale, ddim_eta, @@ -360,8 +351,6 @@ class Inpaint(Img2Img): # Restore original settings self.get_make_image( - prompt, - sampler, steps, cfg_scale, ddim_eta, diff --git a/invokeai/backend/generator/txt2img.py b/invokeai/backend/generator/txt2img.py deleted file mode 100644 index a83a8e0c31..0000000000 --- a/invokeai/backend/generator/txt2img.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -invokeai.backend.generator.txt2img inherits from invokeai.backend.generator -""" -import PIL.Image -import torch - -from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from diffusers.models.controlnet import ControlNetModel, ControlNetOutput -from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_controlnet import MultiControlNetModel - -from ..stable_diffusion import ( - ConditioningData, - PostprocessingSettings, - StableDiffusionGeneratorPipeline, -) -from .base import Generator - - -class Txt2Img(Generator): - def __init__(self, model, precision, - control_model: Optional[Union[ControlNetModel, List[ControlNetModel]]] = None, - **kwargs): - self.control_model = control_model - if isinstance(self.control_model, list): - self.control_model = MultiControlNetModel(self.control_model) - super().__init__(model, precision, **kwargs) - - @torch.no_grad() - def get_make_image( - self, - prompt, - sampler, - steps, - cfg_scale, - ddim_eta, - conditioning, - width, - height, - step_callback=None, - threshold=0.0, - warmup=0.2, - perlin=0.0, - h_symmetry_time_pct=None, - v_symmetry_time_pct=None, - attention_maps_callback=None, - **kwargs, - ): - """ - Returns a function returning an image derived from the prompt and the initial image - Return value depends on the seed at the time you call it - kwargs are 'width' and 'height' - """ - self.perlin = perlin - control_image = kwargs.get("control_image", None) - do_classifier_free_guidance = cfg_scale > 1.0 - - # noinspection PyTypeChecker - pipeline: StableDiffusionGeneratorPipeline = self.model - pipeline.control_model = self.control_model - pipeline.scheduler = sampler - - uc, c, extra_conditioning_info = conditioning - conditioning_data = ConditioningData( - uc, - c, - cfg_scale, - extra_conditioning_info, - postprocessing_settings=PostprocessingSettings( - threshold=threshold, - warmup=warmup, - h_symmetry_time_pct=h_symmetry_time_pct, - v_symmetry_time_pct=v_symmetry_time_pct, - ), - ).add_scheduler_args_if_applicable(pipeline.scheduler, eta=ddim_eta) - - # FIXME: still need to test with different widths, heights, devices, dtypes - # and add in batch_size, num_images_per_prompt? - if control_image is not None: - if isinstance(self.control_model, ControlNetModel): - control_image = pipeline.prepare_control_image( - image=control_image, - do_classifier_free_guidance=do_classifier_free_guidance, - width=width, - height=height, - # batch_size=batch_size * num_images_per_prompt, - # num_images_per_prompt=num_images_per_prompt, - device=self.control_model.device, - dtype=self.control_model.dtype, - ) - elif isinstance(self.control_model, MultiControlNetModel): - images = [] - for image_ in control_image: - image_ = self.model.prepare_control_image( - image=image_, - do_classifier_free_guidance=do_classifier_free_guidance, - width=width, - height=height, - # batch_size=batch_size * num_images_per_prompt, - # num_images_per_prompt=num_images_per_prompt, - device=self.control_model.device, - dtype=self.control_model.dtype, - ) - images.append(image_) - control_image = images - kwargs["control_image"] = control_image - - def make_image(x_T: torch.Tensor, _: int) -> PIL.Image.Image: - pipeline_output = pipeline.image_from_embeddings( - latents=torch.zeros_like(x_T, dtype=self.torch_dtype()), - noise=x_T, - num_inference_steps=steps, - conditioning_data=conditioning_data, - callback=step_callback, - **kwargs, - ) - - if ( - pipeline_output.attention_map_saver is not None - and attention_maps_callback is not None - ): - attention_maps_callback(pipeline_output.attention_map_saver) - - return pipeline.numpy_to_pil(pipeline_output.images)[0] - - return make_image diff --git a/invokeai/backend/generator/txt2img2img.py b/invokeai/backend/generator/txt2img2img.py deleted file mode 100644 index 1257a44fb1..0000000000 --- a/invokeai/backend/generator/txt2img2img.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -invokeai.backend.generator.txt2img inherits from invokeai.backend.generator -""" - -import math -from typing import Callable, Optional - -import torch -from diffusers.utils.logging import get_verbosity, set_verbosity, set_verbosity_error - -from ..stable_diffusion import PostprocessingSettings -from .base import Generator -from ..stable_diffusion.diffusers_pipeline import StableDiffusionGeneratorPipeline -from ..stable_diffusion.diffusers_pipeline import ConditioningData -from ..stable_diffusion.diffusers_pipeline import trim_to_multiple_of - -import invokeai.backend.util.logging as logger - -class Txt2Img2Img(Generator): - def __init__(self, model, precision): - super().__init__(model, precision) - self.init_latent = None # for get_noise() - - def get_make_image( - self, - prompt: str, - sampler, - steps: int, - cfg_scale: float, - ddim_eta, - conditioning, - width: int, - height: int, - strength: float, - step_callback: Optional[Callable] = None, - threshold=0.0, - warmup=0.2, - perlin=0.0, - h_symmetry_time_pct=None, - v_symmetry_time_pct=None, - attention_maps_callback=None, - **kwargs, - ): - """ - Returns a function returning an image derived from the prompt and the initial image - Return value depends on the seed at the time you call it - kwargs are 'width' and 'height' - """ - self.perlin = perlin - - # noinspection PyTypeChecker - pipeline: StableDiffusionGeneratorPipeline = self.model - pipeline.scheduler = sampler - - uc, c, extra_conditioning_info = conditioning - conditioning_data = ConditioningData( - uc, - c, - cfg_scale, - extra_conditioning_info, - postprocessing_settings=PostprocessingSettings( - threshold=threshold, - warmup=0.2, - h_symmetry_time_pct=h_symmetry_time_pct, - v_symmetry_time_pct=v_symmetry_time_pct, - ), - ).add_scheduler_args_if_applicable(pipeline.scheduler, eta=ddim_eta) - - def make_image(x_T: torch.Tensor, _: int): - first_pass_latent_output, _ = pipeline.latents_from_embeddings( - latents=torch.zeros_like(x_T), - num_inference_steps=steps, - conditioning_data=conditioning_data, - noise=x_T, - callback=step_callback, - ) - - # Get our initial generation width and height directly from the latent output so - # the message below is accurate. - init_width = first_pass_latent_output.size()[3] * self.downsampling_factor - init_height = first_pass_latent_output.size()[2] * self.downsampling_factor - logger.info( - f"Interpolating from {init_width}x{init_height} to {width}x{height} using DDIM sampling" - ) - - # resizing - resized_latents = torch.nn.functional.interpolate( - first_pass_latent_output, - size=( - height // self.downsampling_factor, - width // self.downsampling_factor, - ), - mode="bilinear", - ) - - # Free up memory from the last generation. - clear_cuda_cache = kwargs["clear_cuda_cache"] or None - if clear_cuda_cache is not None: - clear_cuda_cache() - - second_pass_noise = self.get_noise_like( - resized_latents, override_perlin=True - ) - - # Clear symmetry for the second pass - from dataclasses import replace - - new_postprocessing_settings = replace( - conditioning_data.postprocessing_settings, h_symmetry_time_pct=None - ) - new_postprocessing_settings = replace( - new_postprocessing_settings, v_symmetry_time_pct=None - ) - new_conditioning_data = replace( - conditioning_data, postprocessing_settings=new_postprocessing_settings - ) - - verbosity = get_verbosity() - set_verbosity_error() - pipeline_output = pipeline.img2img_from_latents_and_embeddings( - resized_latents, - num_inference_steps=steps, - conditioning_data=new_conditioning_data, - strength=strength, - noise=second_pass_noise, - callback=step_callback, - ) - set_verbosity(verbosity) - - if ( - pipeline_output.attention_map_saver is not None - and attention_maps_callback is not None - ): - attention_maps_callback(pipeline_output.attention_map_saver) - - return pipeline.numpy_to_pil(pipeline_output.images)[0] - - # FIXME: do we really need something entirely different for the inpainting model? - - # in the case of the inpainting model being loaded, the trick of - # providing an interpolated latent doesn't work, so we transiently - # create a 512x512 PIL image, upscale it, and run the inpainting - # over it in img2img mode. Because the inpaing model is so conservative - # it doesn't change the image (much) - - return make_image - - def get_noise_like(self, like: torch.Tensor, override_perlin: bool = False): - device = like.device - if device.type == "mps": - x = torch.randn_like(like, device="cpu", dtype=self.torch_dtype()).to( - device - ) - else: - x = torch.randn_like(like, device=device, dtype=self.torch_dtype()) - if self.perlin > 0.0 and override_perlin == False: - shape = like.shape - x = (1 - self.perlin) * x + self.perlin * self.get_perlin_noise( - shape[3], shape[2] - ) - return x - - # returns a tensor filled with random numbers from a normal distribution - def get_noise(self, width, height, scale=True): - # print(f"Get noise: {width}x{height}") - if scale: - # Scale the input width and height for the initial generation - # Make their area equivalent to the model's resolution area (e.g. 512*512 = 262144), - # while keeping the minimum dimension at least 0.5 * resolution (e.g. 512*0.5 = 256) - - aspect = width / height - dimension = self.model.unet.config.sample_size * self.model.vae_scale_factor - min_dimension = math.floor(dimension * 0.5) - model_area = ( - dimension * dimension - ) # hardcoded for now since all models are trained on square images - - if aspect > 1.0: - init_height = max(min_dimension, math.sqrt(model_area / aspect)) - init_width = init_height * aspect - else: - init_width = max(min_dimension, math.sqrt(model_area * aspect)) - init_height = init_width / aspect - - scaled_width, scaled_height = trim_to_multiple_of( - math.floor(init_width), math.floor(init_height) - ) - - else: - scaled_width = width - scaled_height = height - - device = self.model.device - channels = self.latent_channels - if channels == 9: - channels = 4 # we don't really want noise for all the mask channels - shape = ( - 1, - channels, - scaled_height // self.downsampling_factor, - scaled_width // self.downsampling_factor, - ) - if self.use_mps_noise or device.type == "mps": - tensor = torch.empty(size=shape, device="cpu") - tensor = self.get_noise_like(like=tensor).to(device) - else: - tensor = torch.empty(size=shape, device=device) - tensor = self.get_noise_like(like=tensor) - return tensor diff --git a/invokeai/backend/image_util/patchmatch.py b/invokeai/backend/image_util/patchmatch.py index 0d2221be41..2e65f08d9f 100644 --- a/invokeai/backend/image_util/patchmatch.py +++ b/invokeai/backend/image_util/patchmatch.py @@ -6,7 +6,8 @@ be suppressed or deferred """ import numpy as np import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig +config = InvokeAIAppConfig.get_config() class PatchMatch: """ @@ -21,7 +22,6 @@ class PatchMatch: @classmethod def _load_patch_match(self): - config = get_invokeai_config() if self.tried_load: return if config.try_patchmatch: diff --git a/invokeai/backend/image_util/txt2mask.py b/invokeai/backend/image_util/txt2mask.py index 1a8fcfeb90..429c9b63fb 100644 --- a/invokeai/backend/image_util/txt2mask.py +++ b/invokeai/backend/image_util/txt2mask.py @@ -33,10 +33,11 @@ from PIL import Image, ImageOps from transformers import AutoProcessor, CLIPSegForImageSegmentation import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig CLIPSEG_MODEL = "CIDAS/clipseg-rd64-refined" CLIPSEG_SIZE = 352 +config = InvokeAIAppConfig.get_config() class SegmentedGrayscale(object): def __init__(self, image: Image, heatmap: torch.Tensor): @@ -83,7 +84,6 @@ class Txt2Mask(object): def __init__(self, device="cpu", refined=False): logger.info("Initializing clipseg model for text to mask inference") - config = get_invokeai_config() # BUG: we are not doing anything with the device option at this time self.device = device diff --git a/invokeai/backend/config/__init__.py b/invokeai/backend/install/__init__.py similarity index 100% rename from invokeai/backend/config/__init__.py rename to invokeai/backend/install/__init__.py diff --git a/invokeai/backend/config/invokeai_configure.py b/invokeai/backend/install/invokeai_configure.py similarity index 62% rename from invokeai/backend/config/invokeai_configure.py rename to invokeai/backend/install/invokeai_configure.py index 59f11d35bc..a0104bef25 100755 --- a/invokeai/backend/config/invokeai_configure.py +++ b/invokeai/backend/install/invokeai_configure.py @@ -7,15 +7,14 @@ # Coauthor: Kevin Turner http://github.com/keturn # import sys -print("Loading Python libraries...\n",file=sys.stderr) - import argparse import io import os -import re import shutil +import textwrap import traceback import warnings +import yaml from argparse import Namespace from pathlib import Path from shutil import get_terminal_size @@ -25,6 +24,7 @@ from urllib import request import npyscreen import transformers from diffusers import AutoencoderKL +from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker from huggingface_hub import HfFolder from huggingface_hub import login as hf_hub_login from omegaconf import OmegaConf @@ -34,51 +34,48 @@ from transformers import ( CLIPSegForImageSegmentation, CLIPTextModel, CLIPTokenizer, + AutoFeatureExtractor, + BertTokenizerFast, ) - import invokeai.configs as configs +from invokeai.app.services.config import ( + InvokeAIAppConfig, +) +from invokeai.backend.util.logging import InvokeAILogger from invokeai.frontend.install.model_install import addModelsForm, process_and_execute from invokeai.frontend.install.widgets import ( CenteredButtonPress, IntTitleSlider, set_min_terminal_size, + CyclingForm, + MIN_COLS, + MIN_LINES, ) -from invokeai.backend.config.legacy_arg_parsing import legacy_parser -from invokeai.backend.config.model_install_backend import ( - default_dataset, - download_from_hf, - hf_download_with_resume, - recommended_datasets, -) -from invokeai.app.services.config import ( - get_invokeai_config, - InvokeAIAppConfig, +from invokeai.backend.install.legacy_arg_parsing import legacy_parser +from invokeai.backend.install.model_install_backend import ( + hf_download_from_pretrained, + InstallSelections, + ModelInstall, ) +from invokeai.backend.model_management.model_probe import ( + ModelType, BaseModelType + ) warnings.filterwarnings("ignore") - transformers.logging.set_verbosity_error() # --------------------------globals----------------------- -config = get_invokeai_config() + +config = InvokeAIAppConfig.get_config() Model_dir = "models" Weights_dir = "ldm/stable-diffusion-v1/" -# the initial "configs" dir is now bundled in the `invokeai.configs` package -Dataset_path = Path(configs.__path__[0]) / "INITIAL_MODELS.yaml" - Default_config_file = config.model_conf_path SD_Configs = config.legacy_conf_path -Datasets = OmegaConf.load(Dataset_path) - -# minimum size for the UI -MIN_COLS = 135 -MIN_LINES = 45 - PRECISION_CHOICES = ['auto','float16','float32','autocast'] INIT_FILE_PREAMBLE = """# InvokeAI initialization file @@ -87,6 +84,7 @@ INIT_FILE_PREAMBLE = """# InvokeAI initialization file # or renaming it and then running invokeai-configure again. """ +logger=InvokeAILogger.getLogger() # -------------------------------------------- def postscript(errors: None): @@ -103,7 +101,7 @@ Command-line client: invokeai If you installed using an installation script, run: - {config.root}/invoke.{"bat" if sys.platform == "win32" else "sh"} + {config.root_path}/invoke.{"bat" if sys.platform == "win32" else "sh"} Add the '--help' argument to see all of the command-line switches available for use. """ @@ -167,152 +165,125 @@ class ProgressBar: # --------------------------------------------- def download_with_progress_bar(model_url: str, model_dest: str, label: str = "the"): try: - print(f"Installing {label} model file {model_url}...", end="", file=sys.stderr) + logger.info(f"Installing {label} model file {model_url}...") if not os.path.exists(model_dest): os.makedirs(os.path.dirname(model_dest), exist_ok=True) request.urlretrieve( model_url, model_dest, ProgressBar(os.path.basename(model_dest)) ) - print("...downloaded successfully", file=sys.stderr) + logger.info("...downloaded successfully") else: - print("...exists", file=sys.stderr) + logger.info("...exists") except Exception: - print("...download failed", file=sys.stderr) - print(f"Error downloading {label} model", file=sys.stderr) + logger.info("...download failed") + logger.info(f"Error downloading {label} model") print(traceback.format_exc(), file=sys.stderr) -# --------------------------------------------- -# this will preload the Bert tokenizer fles -def download_bert(): - print("Installing bert tokenizer...", file=sys.stderr) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - from transformers import BertTokenizerFast +def download_conversion_models(): + target_dir = config.root_path / 'models/core/convert' + kwargs = dict() # for future use + try: + logger.info('Downloading core tokenizers and text encoders') - download_from_hf(BertTokenizerFast, "bert-base-uncased") + # bert + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + bert = BertTokenizerFast.from_pretrained("bert-base-uncased", **kwargs) + bert.save_pretrained(target_dir / 'bert-base-uncased', safe_serialization=True) + + # sd-1 + repo_id = 'openai/clip-vit-large-patch14' + hf_download_from_pretrained(CLIPTokenizer, repo_id, target_dir / 'clip-vit-large-patch14') + hf_download_from_pretrained(CLIPTextModel, repo_id, target_dir / 'clip-vit-large-patch14') + # sd-2 + repo_id = "stabilityai/stable-diffusion-2" + pipeline = CLIPTokenizer.from_pretrained(repo_id, subfolder="tokenizer", **kwargs) + pipeline.save_pretrained(target_dir / 'stable-diffusion-2-clip' / 'tokenizer', safe_serialization=True) -# --------------------------------------------- -def download_sd1_clip(): - print("Installing SD1 clip model...", file=sys.stderr) - version = "openai/clip-vit-large-patch14" - download_from_hf(CLIPTokenizer, version) - download_from_hf(CLIPTextModel, version) + pipeline = CLIPTextModel.from_pretrained(repo_id, subfolder="text_encoder", **kwargs) + pipeline.save_pretrained(target_dir / 'stable-diffusion-2-clip' / 'text_encoder', safe_serialization=True) + # VAE + logger.info('Downloading stable diffusion VAE') + vae = AutoencoderKL.from_pretrained('stabilityai/sd-vae-ft-mse', **kwargs) + vae.save_pretrained(target_dir / 'sd-vae-ft-mse', safe_serialization=True) -# --------------------------------------------- -def download_sd2_clip(): - version = "stabilityai/stable-diffusion-2" - print("Installing SD2 clip model...", file=sys.stderr) - download_from_hf(CLIPTokenizer, version, subfolder="tokenizer") - download_from_hf(CLIPTextModel, version, subfolder="text_encoder") + # safety checking + logger.info('Downloading safety checker') + repo_id = "CompVis/stable-diffusion-safety-checker" + pipeline = AutoFeatureExtractor.from_pretrained(repo_id,**kwargs) + pipeline.save_pretrained(target_dir / 'stable-diffusion-safety-checker', safe_serialization=True) + pipeline = StableDiffusionSafetyChecker.from_pretrained(repo_id,**kwargs) + pipeline.save_pretrained(target_dir / 'stable-diffusion-safety-checker', safe_serialization=True) + except KeyboardInterrupt: + raise + except Exception as e: + logger.error(str(e)) # --------------------------------------------- def download_realesrgan(): - print("Installing models from RealESRGAN...", file=sys.stderr) + logger.info("Installing models from RealESRGAN...") model_url = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth" wdn_model_url = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-wdn-x4v3.pth" - model_dest = os.path.join( - config.root, "models/realesrgan/realesr-general-x4v3.pth" - ) + model_dest = config.root_path / "models/core/upscaling/realesrgan/realesr-general-x4v3.pth" + wdn_model_dest = config.root_path / "models/core/upscaling/realesrgan/realesr-general-wdn-x4v3.pth" - wdn_model_dest = os.path.join( - config.root, "models/realesrgan/realesr-general-wdn-x4v3.pth" - ) - - download_with_progress_bar(model_url, model_dest, "RealESRGAN") - download_with_progress_bar(wdn_model_url, wdn_model_dest, "RealESRGANwdn") + download_with_progress_bar(model_url, str(model_dest), "RealESRGAN") + download_with_progress_bar(wdn_model_url, str(wdn_model_dest), "RealESRGANwdn") def download_gfpgan(): - print("Installing GFPGAN models...", file=sys.stderr) + logger.info("Installing GFPGAN models...") for model in ( [ "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth", - "./models/gfpgan/GFPGANv1.4.pth", + "./models/core/face_restoration/gfpgan/GFPGANv1.4.pth", ], [ "https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth", - "./models/gfpgan/weights/detection_Resnet50_Final.pth", + "./models/core/face_restoration/gfpgan/weights/detection_Resnet50_Final.pth", ], [ "https://github.com/xinntao/facexlib/releases/download/v0.2.2/parsing_parsenet.pth", - "./models/gfpgan/weights/parsing_parsenet.pth", + "./models/core/face_restoration/gfpgan/weights/parsing_parsenet.pth", ], ): - model_url, model_dest = model[0], os.path.join(config.root, model[1]) - download_with_progress_bar(model_url, model_dest, "GFPGAN weights") + model_url, model_dest = model[0], config.root_path / model[1] + download_with_progress_bar(model_url, str(model_dest), "GFPGAN weights") # --------------------------------------------- def download_codeformer(): - print("Installing CodeFormer model file...", file=sys.stderr) + logger.info("Installing CodeFormer model file...") model_url = ( "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth" ) - model_dest = os.path.join(config.root, "models/codeformer/codeformer.pth") - download_with_progress_bar(model_url, model_dest, "CodeFormer") + model_dest = config.root_path / "models/core/face_restoration/codeformer/codeformer.pth" + download_with_progress_bar(model_url, str(model_dest), "CodeFormer") # --------------------------------------------- def download_clipseg(): - print("Installing clipseg model for text-based masking...", file=sys.stderr) + logger.info("Installing clipseg model for text-based masking...") CLIPSEG_MODEL = "CIDAS/clipseg-rd64-refined" try: - download_from_hf(AutoProcessor, CLIPSEG_MODEL) - download_from_hf(CLIPSegForImageSegmentation, CLIPSEG_MODEL) + hf_download_from_pretrained(AutoProcessor, CLIPSEG_MODEL, config.root_path / 'models/core/misc/clipseg') + hf_download_from_pretrained(CLIPSegForImageSegmentation, CLIPSEG_MODEL, config.root_path / 'models/core/misc/clipseg') except Exception: - print("Error installing clipseg model:") - print(traceback.format_exc()) + logger.info("Error installing clipseg model:") + logger.info(traceback.format_exc()) -# ------------------------------------- -def download_safety_checker(): - print("Installing model for NSFW content detection...", file=sys.stderr) - try: - from diffusers.pipelines.stable_diffusion.safety_checker import ( - StableDiffusionSafetyChecker, - ) - from transformers import AutoFeatureExtractor - except ModuleNotFoundError: - print("Error installing NSFW checker model:") - print(traceback.format_exc()) - return - safety_model_id = "CompVis/stable-diffusion-safety-checker" - print("AutoFeatureExtractor...", file=sys.stderr) - download_from_hf(AutoFeatureExtractor, safety_model_id) - print("StableDiffusionSafetyChecker...", file=sys.stderr) - download_from_hf(StableDiffusionSafetyChecker, safety_model_id) - - -# ------------------------------------- -def download_vaes(): - print("Installing stabilityai VAE...", file=sys.stderr) - try: - # first the diffusers version - repo_id = "stabilityai/sd-vae-ft-mse" - args = dict( - cache_dir=config.cache_dir, - ) - if not AutoencoderKL.from_pretrained(repo_id, **args): - raise Exception(f"download of {repo_id} failed") - - repo_id = "stabilityai/sd-vae-ft-mse-original" - model_name = "vae-ft-mse-840000-ema-pruned.ckpt" - # next the legacy checkpoint version - if not hf_download_with_resume( - repo_id=repo_id, - model_name=model_name, - model_dir=str(config.root / Model_dir / Weights_dir), - ): - raise Exception(f"download of {model_name} failed") - except Exception as e: - print(f"Error downloading StabilityAI standard VAE: {str(e)}", file=sys.stderr) - print(traceback.format_exc(), file=sys.stderr) - +def download_support_models(): + download_realesrgan() + download_gfpgan() + download_codeformer() + download_clipseg() + download_conversion_models() # ------------------------------------- def get_root(root: str = None) -> str: @@ -321,24 +292,24 @@ def get_root(root: str = None) -> str: elif os.environ.get("INVOKEAI_ROOT"): return os.environ.get("INVOKEAI_ROOT") else: - return config.root + return str(config.root_path) # ------------------------------------- -class editOptsForm(npyscreen.FormMultiPage): +class editOptsForm(CyclingForm, npyscreen.FormMultiPage): # for responsive resizing - disabled # FIX_MINIMUM_SIZE_WHEN_CREATED = False def create(self): program_opts = self.parentApp.program_opts old_opts = self.parentApp.invokeai_opts - first_time = not (config.root / 'invokeai.yaml').exists() + first_time = not (config.root_path / 'invokeai.yaml').exists() access_token = HfFolder.get_token() window_width, window_height = get_terminal_size() - for i in [ - "Configure startup settings. You can come back and change these later.", - "Use ctrl-N and ctrl-P to move to the ext and

revious fields.", - "Use cursor arrows to make a checkbox selection, and space to toggle.", - ]: + label = """Configure startup settings. You can come back and change these later. +Use ctrl-N and ctrl-P to move to the ext and

revious fields. +Use cursor arrows to make a checkbox selection, and space to toggle. +""" + for i in textwrap.wrap(label,width=window_width-6): self.add_widget_intelligent( npyscreen.FixedText, value=i, @@ -365,7 +336,7 @@ class editOptsForm(npyscreen.FormMultiPage): self.outdir = self.add_widget_intelligent( npyscreen.TitleFilename, name="( autocompletes, ctrl-N advances):", - value=str(old_opts.outdir) or str(default_output_dir()), + value=str(default_output_dir()), select_dir=True, must_exist=False, use_two_lines=False, @@ -388,14 +359,13 @@ class editOptsForm(npyscreen.FormMultiPage): scroll_exit=True, ) self.nextrely += 1 - for i in [ - "If you have an account at HuggingFace you may optionally paste your access token here", - 'to allow InvokeAI to download restricted styles & subjects from the "Concept Library".', - "See https://huggingface.co/settings/tokens", - ]: + label = """If you have an account at HuggingFace you may optionally paste your access token here +to allow InvokeAI to download restricted styles & subjects from the "Concept Library". See https://huggingface.co/settings/tokens. +""" + for line in textwrap.wrap(label,width=window_width-6): self.add_widget_intelligent( npyscreen.FixedText, - value=i, + value=line, editable=False, color="CONTROL", ) @@ -472,32 +442,23 @@ class editOptsForm(npyscreen.FormMultiPage): self.nextrely += 1 self.add_widget_intelligent( npyscreen.FixedText, - value="Directories containing textual inversion and LoRA models ( autocompletes, ctrl-N advances):", + value="Directories containing textual inversion, controlnet and LoRA models ( autocompletes, ctrl-N advances):", editable=False, color="CONTROL", ) - self.embedding_dir = self.add_widget_intelligent( - npyscreen.TitleFilename, - name=" Textual Inversion Embeddings:", - value=str(default_embedding_dir()), - select_dir=True, - must_exist=False, - use_two_lines=False, - labelColor="GOOD", - begin_entry_at=32, - scroll_exit=True, - ) - self.lora_dir = self.add_widget_intelligent( - npyscreen.TitleFilename, - name=" LoRA and LyCORIS:", - value=str(default_lora_dir()), - select_dir=True, - must_exist=False, - use_two_lines=False, - labelColor="GOOD", - begin_entry_at=32, - scroll_exit=True, - ) + self.autoimport_dirs = {} + for description, config_name, path in autoimport_paths(old_opts): + self.autoimport_dirs[config_name] = self.add_widget_intelligent( + npyscreen.TitleFilename, + name=description+':', + value=str(path), + select_dir=True, + must_exist=False, + use_two_lines=False, + labelColor="GOOD", + begin_entry_at=32, + scroll_exit=True + ) self.nextrely += 1 self.add_widget_intelligent( npyscreen.TitleFixedText, @@ -508,11 +469,11 @@ class editOptsForm(npyscreen.FormMultiPage): scroll_exit=True, ) self.nextrely -= 1 - for i in [ - "BY DOWNLOADING THE STABLE DIFFUSION WEIGHT FILES, YOU AGREE TO HAVE READ", - "AND ACCEPTED THE CREATIVEML RESPONSIBLE AI LICENSE LOCATED AT", - "https://huggingface.co/spaces/CompVis/stable-diffusion-license", - ]: + label = """BY DOWNLOADING THE STABLE DIFFUSION WEIGHT FILES, YOU AGREE TO HAVE READ +AND ACCEPTED THE CREATIVEML RESPONSIBLE AI LICENSE LOCATED AT +https://huggingface.co/spaces/CompVis/stable-diffusion-license +""" + for i in textwrap.wrap(label,width=window_width-6): self.add_widget_intelligent( npyscreen.FixedText, value=i, @@ -551,7 +512,7 @@ class editOptsForm(npyscreen.FormMultiPage): self.editing = False else: self.editing = True - + def validate_field_values(self, opt: Namespace) -> bool: bad_fields = [] if not opt.license_acceptance: @@ -562,10 +523,6 @@ class editOptsForm(npyscreen.FormMultiPage): bad_fields.append( f"The output directory does not seem to be valid. Please check that {str(Path(opt.outdir).parent)} is an existing directory." ) - if not Path(opt.embedding_dir).parent.exists(): - bad_fields.append( - f"The embedding directory does not seem to be valid. Please check that {str(Path(opt.embedding_dir).parent)} is an existing directory." - ) if len(bad_fields) > 0: message = "The following problems were detected and must be corrected:\n" for problem in bad_fields: @@ -585,11 +542,15 @@ class editOptsForm(npyscreen.FormMultiPage): "max_loaded_models", "xformers_enabled", "always_use_cpu", - "embedding_dir", - "lora_dir", ]: setattr(new_opts, attr, getattr(self, attr).value) + for attr in self.autoimport_dirs: + directory = Path(self.autoimport_dirs[attr].value) + if directory.is_relative_to(config.root_path): + directory = directory.relative_to(config.root_path) + setattr(new_opts, attr, directory) + new_opts.hf_token = self.hf_token.value new_opts.license_acceptance = self.license_acceptance.value new_opts.precision = PRECISION_CHOICES[self.precision.value[0]] @@ -606,7 +567,8 @@ class EditOptApplication(npyscreen.NPSAppManaged): self.program_opts = program_opts self.invokeai_opts = invokeai_opts self.user_cancelled = False - self.user_selections = default_user_selections(program_opts) + self.autoload_pending = True + self.install_selections = default_user_selections(program_opts) def onStart(self): npyscreen.setTheme(npyscreen.Themes.DefaultTheme) @@ -614,6 +576,7 @@ class EditOptApplication(npyscreen.NPSAppManaged): "MAIN", editOptsForm, name="InvokeAI Startup Options", + cycle_widgets=True, ) if not (self.program_opts.skip_sd_weights or self.program_opts.default_only): self.model_select = self.addForm( @@ -621,6 +584,7 @@ class EditOptApplication(npyscreen.NPSAppManaged): addModelsForm, name="Install Stable Diffusion Models", multipage=True, + cycle_widgets=True, ) def new_opts(self): @@ -634,62 +598,90 @@ def edit_opts(program_opts: Namespace, invokeai_opts: Namespace) -> argparse.Nam def default_startup_options(init_file: Path) -> Namespace: - opts = InvokeAIAppConfig(argv=[]) - outdir = Path(opts.outdir) - if not outdir.is_absolute(): - opts.outdir = str(config.root / opts.outdir) + opts = InvokeAIAppConfig.get_config() if not init_file.exists(): opts.nsfw_checker = True return opts -def default_user_selections(program_opts: Namespace) -> Namespace: - return Namespace( - starter_models=default_dataset() +def default_user_selections(program_opts: Namespace) -> InstallSelections: + installer = ModelInstall(config) + models = installer.all_models() + return InstallSelections( + install_models=[models[installer.default_model()].path or models[installer.default_model()].repo_id] if program_opts.default_only - else recommended_datasets() + else [models[x].path or models[x].repo_id for x in installer.recommended_models()] if program_opts.yes_to_all - else dict(), - purge_deleted_models=False, - scan_directory=None, - autoscan_on_startup=None, - import_model_paths=None, - convert_to_diffusers=None, + else list(), +# scan_directory=None, +# autoscan_on_startup=None, ) - # ------------------------------------- -def initialize_rootdir(root: str, yes_to_all: bool = False): - print("** INITIALIZING INVOKEAI RUNTIME DIRECTORY **") - +def autoimport_paths(config: InvokeAIAppConfig): + return [ + ('Checkpoints & diffusers models', 'autoimport_dir', config.root_path / config.autoimport_dir), + ('LoRA/LyCORIS models', 'lora_dir', config.root_path / config.lora_dir), + ('Controlnet models', 'controlnet_dir', config.root_path / config.controlnet_dir), + ('Textual Inversion Embeddings', 'embedding_dir', config.root_path / config.embedding_dir), + ] + +# ------------------------------------- +def initialize_rootdir(root: Path, yes_to_all: bool = False): + logger.info("** INITIALIZING INVOKEAI RUNTIME DIRECTORY **") for name in ( - "models", - "configs", - "embeddings", - "text-inversion-output", - "text-inversion-training-data", + "models", + "databases", + "text-inversion-output", + "text-inversion-training-data", + "configs" ): os.makedirs(os.path.join(root, name), exist_ok=True) + for model_type in ModelType: + Path(root, 'autoimport', model_type.value).mkdir(parents=True, exist_ok=True) configs_src = Path(configs.__path__[0]) - configs_dest = Path(root) / "configs" + configs_dest = root / "configs" if not os.path.samefile(configs_src, configs_dest): shutil.copytree(configs_src, configs_dest, dirs_exist_ok=True) + dest = root / 'models' + for model_base in BaseModelType: + for model_type in ModelType: + path = dest / model_base.value / model_type.value + path.mkdir(parents=True, exist_ok=True) + path = dest / 'core' + path.mkdir(parents=True, exist_ok=True) + with open(root / 'configs' / 'models.yaml','w') as yaml_file: + yaml_file.write(yaml.dump({'__metadata__': + {'version':'3.0.0'} + } + ) + ) + # ------------------------------------- def run_console_ui( program_opts: Namespace, initfile: Path = None ) -> (Namespace, Namespace): # parse_args() will read from init file if present invokeai_opts = default_startup_options(initfile) + invokeai_opts.root = program_opts.root - set_min_terminal_size(MIN_COLS, MIN_LINES) + # The third argument is needed in the Windows 11 environment to + # launch a console window running this program. + set_min_terminal_size(MIN_COLS, MIN_LINES,'invokeai-configure') + + # the install-models application spawns a subprocess to install + # models, and will crash unless this is set before running. + import torch + torch.multiprocessing.set_start_method("spawn") + editApp = EditOptApplication(program_opts, invokeai_opts) editApp.run() if editApp.user_cancelled: return (None, None) else: - return (editApp.new_opts, editApp.user_selections) + return (editApp.new_opts, editApp.install_selections) # ------------------------------------- @@ -697,27 +689,20 @@ def write_opts(opts: Namespace, init_file: Path): """ Update the invokeai.yaml file with values from current settings. """ - # this will load current settings - config = InvokeAIAppConfig() + new_config = InvokeAIAppConfig.get_config() + new_config.root = config.root + for key,value in opts.__dict__.items(): - if hasattr(config,key): - setattr(config,key,value) + if hasattr(new_config,key): + setattr(new_config,key,value) with open(init_file,'w', encoding='utf-8') as file: - file.write(config.to_yaml()) + file.write(new_config.to_yaml()) # ------------------------------------- def default_output_dir() -> Path: - return config.root / "outputs" - -# ------------------------------------- -def default_embedding_dir() -> Path: - return config.root / "embeddings" - -# ------------------------------------- -def default_lora_dir() -> Path: - return config.root / "loras" + return config.root_path / "outputs" # ------------------------------------- def write_default_options(program_opts: Namespace, initfile: Path): @@ -731,7 +716,7 @@ def write_default_options(program_opts: Namespace, initfile: Path): # yaml format. def migrate_init_file(legacy_format:Path): old = legacy_parser.parse_args([f'@{str(legacy_format)}']) - new = InvokeAIAppConfig(conf={}) + new = InvokeAIAppConfig.get_config() fields = list(get_type_hints(InvokeAIAppConfig).keys()) for attr in fields: @@ -743,14 +728,42 @@ def migrate_init_file(legacy_format:Path): new.nsfw_checker = old.safety_checker new.xformers_enabled = old.xformers new.conf_path = old.conf - new.embedding_dir = old.embedding_path + new.root = legacy_format.parent.resolve() invokeai_yaml = legacy_format.parent / 'invokeai.yaml' with open(invokeai_yaml,"w", encoding="utf-8") as outfile: outfile.write(new.to_yaml()) - legacy_format.replace(legacy_format.parent / 'invokeai.init.old') + legacy_format.replace(legacy_format.parent / 'invokeai.init.orig') +# ------------------------------------- +def migrate_models(root: Path): + from invokeai.backend.install.migrate_to_3 import do_migrate + do_migrate(root, root) + +def migrate_if_needed(opt: Namespace, root: Path)->bool: + # We check for to see if the runtime directory is correctly initialized. + old_init_file = root / 'invokeai.init' + new_init_file = root / 'invokeai.yaml' + old_hub = root / 'models/hub' + migration_needed = old_init_file.exists() and not new_init_file.exists() or old_hub.exists() + + if migration_needed: + if opt.yes_to_all or \ + yes_or_no(f'{str(config.root_path)} appears to be a 2.3 format root directory. Convert to version 3.0?'): + + logger.info('** Migrating invokeai.init to invokeai.yaml') + migrate_init_file(old_init_file) + config.parse_args(argv=[],conf=OmegaConf.load(new_init_file)) + + if old_hub.exists(): + migrate_models(config.root_path) + else: + print('Cannot continue without conversion. Aborting.') + + return migration_needed + + # ------------------------------------- def main(): parser = argparse.ArgumentParser(description="InvokeAI model downloader") @@ -805,27 +818,27 @@ def main(): ) opt = parser.parse_args() - # setting a global here - global config - config.root = Path(os.path.expanduser(get_root(opt.root) or "")) + invoke_args = [] + if opt.root: + invoke_args.extend(['--root',opt.root]) + if opt.full_precision: + invoke_args.extend(['--precision','float32']) + config.parse_args(invoke_args) + logger = InvokeAILogger().getLogger(config=config) errors = set() try: - models_to_download = default_user_selections(opt) - - # We check for to see if the runtime directory is correctly initialized. - old_init_file = Path(config.root, 'invokeai.init') - new_init_file = Path(config.root, 'invokeai.yaml') - if old_init_file.exists() and not new_init_file.exists(): - print('** Migrating invokeai.init to invokeai.yaml') - migrate_init_file(old_init_file) - config = get_invokeai_config() # reread defaults - + # if we do a root migration/upgrade, then we are keeping previous + # configuration and we are done. + if migrate_if_needed(opt, config.root_path): + sys.exit(0) if not config.model_conf_path.exists(): - initialize_rootdir(config.root, opt.yes_to_all) + initialize_rootdir(config.root_path, opt.yes_to_all) + models_to_download = default_user_selections(opt) + new_init_file = config.root_path / 'invokeai.yaml' if opt.yes_to_all: write_default_options(opt, new_init_file) init_options = Namespace( @@ -836,32 +849,26 @@ def main(): if init_options: write_opts(init_options, new_init_file) else: - print( + logger.info( '\n** CANCELLED AT USER\'S REQUEST. USE THE "invoke.sh" LAUNCHER TO RUN LATER **\n' ) sys.exit(0) - + if opt.skip_support_models: - print("\n** SKIPPING SUPPORT MODEL DOWNLOADS PER USER REQUEST **") + logger.info("SKIPPING SUPPORT MODEL DOWNLOADS PER USER REQUEST") else: - print("\n** DOWNLOADING SUPPORT MODELS **") - download_bert() - download_sd1_clip() - download_sd2_clip() - download_realesrgan() - download_gfpgan() - download_codeformer() - download_clipseg() - download_safety_checker() - download_vaes() + logger.info("CHECKING/UPDATING SUPPORT MODELS") + download_support_models() if opt.skip_sd_weights: - print("\n** SKIPPING DIFFUSION WEIGHTS DOWNLOAD PER USER REQUEST **") + logger.info("\n** SKIPPING DIFFUSION WEIGHTS DOWNLOAD PER USER REQUEST **") elif models_to_download: - print("\n** DOWNLOADING DIFFUSION WEIGHTS **") + logger.info("\n** DOWNLOADING DIFFUSION WEIGHTS **") process_and_execute(opt, models_to_download) postscript(errors=errors) + if not opt.yes_to_all: + input('Press any key to continue...') except KeyboardInterrupt: print("\nGoodbye! Come back soon.") diff --git a/invokeai/backend/config/legacy_arg_parsing.py b/invokeai/backend/install/legacy_arg_parsing.py similarity index 99% rename from invokeai/backend/config/legacy_arg_parsing.py rename to invokeai/backend/install/legacy_arg_parsing.py index 85ca588fe2..4a58ff8336 100644 --- a/invokeai/backend/config/legacy_arg_parsing.py +++ b/invokeai/backend/install/legacy_arg_parsing.py @@ -9,6 +9,7 @@ SAMPLER_CHOICES = [ "ddpm", "deis", "lms", + "lms_k", "pndm", "heun", "heun_k", @@ -18,8 +19,13 @@ SAMPLER_CHOICES = [ "kdpm_2", "kdpm_2_a", "dpmpp_2s", + "dpmpp_2s_k", "dpmpp_2m", "dpmpp_2m_k", + "dpmpp_2m_sde", + "dpmpp_2m_sde_k", + "dpmpp_sde", + "dpmpp_sde_k", "unipc", ] diff --git a/invokeai/backend/install/migrate_to_3.py b/invokeai/backend/install/migrate_to_3.py new file mode 100644 index 0000000000..c8e024f484 --- /dev/null +++ b/invokeai/backend/install/migrate_to_3.py @@ -0,0 +1,581 @@ +''' +Migrate the models directory and models.yaml file from an existing +InvokeAI 2.3 installation to 3.0.0. +''' + +import io +import os +import argparse +import shutil +import yaml + +import transformers +import diffusers +import warnings + +from dataclasses import dataclass +from pathlib import Path +from omegaconf import OmegaConf, DictConfig +from typing import Union + +from diffusers import StableDiffusionPipeline, AutoencoderKL +from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker +from transformers import ( + CLIPTextModel, + CLIPTokenizer, + AutoFeatureExtractor, + BertTokenizerFast, +) + +import invokeai.backend.util.logging as logger +from invokeai.backend.model_management import ModelManager +from invokeai.backend.model_management.model_probe import ( + ModelProbe, ModelType, BaseModelType, SchedulerPredictionType, ModelProbeInfo + ) + +warnings.filterwarnings("ignore") +transformers.logging.set_verbosity_error() +diffusers.logging.set_verbosity_error() + +# holder for paths that we will migrate +@dataclass +class ModelPaths: + models: Path + embeddings: Path + loras: Path + controlnets: Path + +class MigrateTo3(object): + def __init__(self, + root_directory: Path, + dest_models: Path, + yaml_file: io.TextIOBase, + src_paths: ModelPaths, + ): + self.root_directory = root_directory + self.dest_models = dest_models + self.dest_yaml = yaml_file + self.model_names = set() + self.src_paths = src_paths + + self._initialize_yaml() + + def _initialize_yaml(self): + self.dest_yaml.write( + yaml.dump( + { + '__metadata__': + { + 'version':'3.0.0'} + } + ) + ) + + def unique_name(self,name,info)->str: + ''' + Create a unique name for a model for use within models.yaml. + ''' + done = False + key = ModelManager.create_key(name,info.base_type,info.model_type) + unique_name = key + counter = 1 + while not done: + if unique_name in self.model_names: + unique_name = f'{key}-{counter:0>2d}' + counter += 1 + else: + done = True + self.model_names.add(unique_name) + name,_,_ = ModelManager.parse_key(unique_name) + return name + + def create_directory_structure(self): + ''' + Create the basic directory structure for the models folder. + ''' + for model_base in [BaseModelType.StableDiffusion1,BaseModelType.StableDiffusion2]: + for model_type in [ModelType.Main, ModelType.Vae, ModelType.Lora, + ModelType.ControlNet,ModelType.TextualInversion]: + path = self.dest_models / model_base.value / model_type.value + path.mkdir(parents=True, exist_ok=True) + path = self.dest_models / 'core' + path.mkdir(parents=True, exist_ok=True) + + @staticmethod + def copy_file(src:Path,dest:Path): + ''' + copy a single file with logging + ''' + if dest.exists(): + logger.info(f'Skipping existing {str(dest)}') + return + logger.info(f'Copying {str(src)} to {str(dest)}') + try: + shutil.copy(src, dest) + except Exception as e: + logger.error(f'COPY FAILED: {str(e)}') + + @staticmethod + def copy_dir(src:Path,dest:Path): + ''' + Recursively copy a directory with logging + ''' + if dest.exists(): + logger.info(f'Skipping existing {str(dest)}') + return + + logger.info(f'Copying {str(src)} to {str(dest)}') + try: + shutil.copytree(src, dest) + except Exception as e: + logger.error(f'COPY FAILED: {str(e)}') + + def migrate_models(self, src_dir: Path): + ''' + Recursively walk through src directory, probe anything + that looks like a model, and copy the model into the + appropriate location within the destination models directory. + ''' + for root, dirs, files in os.walk(src_dir): + for f in files: + # hack - don't copy raw learned_embeds.bin, let them + # be copied as part of a tree copy operation + if f == 'learned_embeds.bin': + continue + try: + model = Path(root,f) + info = ModelProbe().heuristic_probe(model) + if not info: + continue + dest = self._model_probe_to_path(info) / f + self.copy_file(model, dest) + except KeyboardInterrupt: + raise + except Exception as e: + logger.error(str(e)) + for d in dirs: + try: + model = Path(root,d) + info = ModelProbe().heuristic_probe(model) + if not info: + continue + dest = self._model_probe_to_path(info) / model.name + self.copy_dir(model, dest) + except KeyboardInterrupt: + raise + except Exception as e: + logger.error(str(e)) + + def migrate_support_models(self): + ''' + Copy the clipseg, upscaler, and restoration models to their new + locations. + ''' + dest_directory = self.dest_models + if (self.root_directory / 'models/clipseg').exists(): + self.copy_dir(self.root_directory / 'models/clipseg', dest_directory / 'core/misc/clipseg') + if (self.root_directory / 'models/realesrgan').exists(): + self.copy_dir(self.root_directory / 'models/realesrgan', dest_directory / 'core/upscaling/realesrgan') + for d in ['codeformer','gfpgan']: + path = self.root_directory / 'models' / d + if path.exists(): + self.copy_dir(path,dest_directory / f'core/face_restoration/{d}') + + def migrate_tuning_models(self): + ''' + Migrate the embeddings, loras and controlnets directories to their new homes. + ''' + for src in [self.src_paths.embeddings, self.src_paths.loras, self.src_paths.controlnets]: + if not src: + continue + if src.is_dir(): + logger.info(f'Scanning {src}') + self.migrate_models(src) + else: + logger.info(f'{src} directory not found; skipping') + continue + + def migrate_conversion_models(self): + ''' + Migrate all the models that are needed by the ckpt_to_diffusers conversion + script. + ''' + + dest_directory = self.dest_models + kwargs = dict( + cache_dir = self.root_directory / 'models/hub', + #local_files_only = True + ) + try: + logger.info('Migrating core tokenizers and text encoders') + target_dir = dest_directory / 'core' / 'convert' + + self._migrate_pretrained(BertTokenizerFast, + repo_id='bert-base-uncased', + dest = target_dir / 'bert-base-uncased', + **kwargs) + + # sd-1 + repo_id = 'openai/clip-vit-large-patch14' + self._migrate_pretrained(CLIPTokenizer, + repo_id= repo_id, + dest= target_dir / 'clip-vit-large-patch14' / 'tokenizer', + **kwargs) + self._migrate_pretrained(CLIPTextModel, + repo_id = repo_id, + dest = target_dir / 'clip-vit-large-patch14' / 'text_encoder', + **kwargs) + + # sd-2 + repo_id = "stabilityai/stable-diffusion-2" + self._migrate_pretrained(CLIPTokenizer, + repo_id = repo_id, + dest = target_dir / 'stable-diffusion-2-clip' / 'tokenizer', + **{'subfolder':'tokenizer',**kwargs} + ) + self._migrate_pretrained(CLIPTextModel, + repo_id = repo_id, + dest = target_dir / 'stable-diffusion-2-clip' / 'text_encoder', + **{'subfolder':'text_encoder',**kwargs} + ) + + # VAE + logger.info('Migrating stable diffusion VAE') + self._migrate_pretrained(AutoencoderKL, + repo_id = 'stabilityai/sd-vae-ft-mse', + dest = target_dir / 'sd-vae-ft-mse', + **kwargs) + + # safety checking + logger.info('Migrating safety checker') + repo_id = "CompVis/stable-diffusion-safety-checker" + self._migrate_pretrained(AutoFeatureExtractor, + repo_id = repo_id, + dest = target_dir / 'stable-diffusion-safety-checker', + **kwargs) + self._migrate_pretrained(StableDiffusionSafetyChecker, + repo_id = repo_id, + dest = target_dir / 'stable-diffusion-safety-checker', + **kwargs) + except KeyboardInterrupt: + raise + except Exception as e: + logger.error(str(e)) + + def write_yaml(self, model_name: str, path:Path, info:ModelProbeInfo, **kwargs): + ''' + Write a stanza for a moved model into the new models.yaml file. + ''' + name = self.unique_name(model_name, info) + stanza = { + f'{info.base_type.value}/{info.model_type.value}/{name}': { + 'name': model_name, + 'path': str(path), + 'description': f'A {info.base_type.value} {info.model_type.value} model', + 'format': info.format, + 'image_size': info.image_size, + 'base': info.base_type.value, + 'variant': info.variant_type.value, + 'prediction_type': info.prediction_type.value, + 'upcast_attention': info.prediction_type == SchedulerPredictionType.VPrediction, + **kwargs, + } + } + self.dest_yaml.write(yaml.dump(stanza)) + self.dest_yaml.flush() + + def _model_probe_to_path(self, info: ModelProbeInfo)->Path: + return Path(self.dest_models, info.base_type.value, info.model_type.value) + + def _migrate_pretrained(self, model_class, repo_id: str, dest: Path, **kwargs): + if dest.exists(): + logger.info(f'Skipping existing {dest}') + return + model = model_class.from_pretrained(repo_id, **kwargs) + self._save_pretrained(model, dest) + + def _save_pretrained(self, model, dest: Path): + if dest.exists(): + logger.info(f'Skipping existing {dest}') + return + model_name = dest.name + download_path = dest.with_name(f'{model_name}.downloading') + model.save_pretrained(download_path, safe_serialization=True) + download_path.replace(dest) + + def _download_vae(self, repo_id: str, subfolder:str=None)->Path: + vae = AutoencoderKL.from_pretrained(repo_id, cache_dir=self.root_directory / 'models/hub', subfolder=subfolder) + info = ModelProbe().heuristic_probe(vae) + _, model_name = repo_id.split('/') + dest = self._model_probe_to_path(info) / self.unique_name(model_name, info) + vae.save_pretrained(dest, safe_serialization=True) + return dest + + def _vae_path(self, vae: Union[str,dict])->Path: + ''' + Convert 2.3 VAE stanza to a straight path. + ''' + vae_path = None + + # First get a path + if isinstance(vae,str): + vae_path = vae + + elif isinstance(vae,DictConfig): + if p := vae.get('path'): + vae_path = p + elif repo_id := vae.get('repo_id'): + if repo_id=='stabilityai/sd-vae-ft-mse': # this guy is already downloaded + vae_path = 'models/core/convert/sd-vae-ft-mse' + else: + vae_path = self._download_vae(repo_id, vae.get('subfolder')) + + assert vae_path is not None, "Couldn't find VAE for this model" + + # if the VAE is in the old models directory, then we must move it into the new + # one. VAEs outside of this directory can stay where they are. + vae_path = Path(vae_path) + if vae_path.is_relative_to(self.src_paths.models): + info = ModelProbe().heuristic_probe(vae_path) + dest = self._model_probe_to_path(info) / vae_path.name + if not dest.exists(): + self.copy_dir(vae_path,dest) + vae_path = dest + + if vae_path.is_relative_to(self.dest_models): + rel_path = vae_path.relative_to(self.dest_models) + return Path('models',rel_path) + else: + return vae_path + + def migrate_repo_id(self, repo_id: str, model_name :str=None, **extra_config): + ''' + Migrate a locally-cached diffusers pipeline identified with a repo_id + ''' + dest_dir = self.dest_models + + cache = self.root_directory / 'models/hub' + kwargs = dict( + cache_dir = cache, + safety_checker = None, + # local_files_only = True, + ) + + owner,repo_name = repo_id.split('/') + model_name = model_name or repo_name + model = cache / '--'.join(['models',owner,repo_name]) + + if len(list(model.glob('snapshots/**/model_index.json')))==0: + return + revisions = [x.name for x in model.glob('refs/*')] + + # if an fp16 is available we use that + revision = 'fp16' if len(revisions) > 1 and 'fp16' in revisions else revisions[0] + pipeline = StableDiffusionPipeline.from_pretrained( + repo_id, + revision=revision, + **kwargs) + + info = ModelProbe().heuristic_probe(pipeline) + if not info: + return + + dest = self._model_probe_to_path(info) / repo_name + self._save_pretrained(pipeline, dest) + + rel_path = Path('models',dest.relative_to(dest_dir)) + self.write_yaml(model_name, path=rel_path, info=info, **extra_config) + + def migrate_path(self, location: Path, model_name: str=None, **extra_config): + ''' + Migrate a model referred to using 'weights' or 'path' + ''' + + # handle relative paths + dest_dir = self.dest_models + location = self.root_directory / location + + info = ModelProbe().heuristic_probe(location) + if not info: + return + + # uh oh, weights is in the old models directory - move it into the new one + if Path(location).is_relative_to(self.src_paths.models): + dest = Path(dest_dir, info.base_type.value, info.model_type.value, location.name) + self.copy_dir(location,dest) + location = Path('models', info.base_type.value, info.model_type.value, location.name) + model_name = model_name or location.stem + model_name = self.unique_name(model_name, info) + self.write_yaml(model_name, path=location, info=info, **extra_config) + + def migrate_defined_models(self): + ''' + Migrate models defined in models.yaml + ''' + # find any models referred to in old models.yaml + conf = OmegaConf.load(self.root_directory / 'configs/models.yaml') + + for model_name, stanza in conf.items(): + + try: + passthru_args = {} + + if vae := stanza.get('vae'): + try: + passthru_args['vae'] = str(self._vae_path(vae)) + except Exception as e: + logger.warning(f'Could not find a VAE matching "{vae}" for model "{model_name}"') + logger.warning(str(e)) + + if config := stanza.get('config'): + passthru_args['config'] = config + + if repo_id := stanza.get('repo_id'): + logger.info(f'Migrating diffusers model {model_name}') + self.migrate_repo_id(repo_id, model_name, **passthru_args) + + elif location := stanza.get('weights'): + logger.info(f'Migrating checkpoint model {model_name}') + self.migrate_path(Path(location), model_name, **passthru_args) + + elif location := stanza.get('path'): + logger.info(f'Migrating diffusers model {model_name}') + self.migrate_path(Path(location), model_name, **passthru_args) + + except KeyboardInterrupt: + raise + except Exception as e: + logger.error(str(e)) + + def migrate(self): + self.create_directory_structure() + # the configure script is doing this + self.migrate_support_models() + self.migrate_conversion_models() + self.migrate_tuning_models() + self.migrate_defined_models() + +def _parse_legacy_initfile(root: Path, initfile: Path)->ModelPaths: + ''' + Returns tuple of (embedding_path, lora_path, controlnet_path) + ''' + parser = argparse.ArgumentParser(fromfile_prefix_chars='@') + parser.add_argument( + '--embedding_directory', + '--embedding_path', + type=Path, + dest='embedding_path', + default=Path('embeddings'), + ) + parser.add_argument( + '--lora_directory', + dest='lora_path', + type=Path, + default=Path('loras'), + ) + opt,_ = parser.parse_known_args([f'@{str(initfile)}']) + return ModelPaths( + models = root / 'models', + embeddings = root / str(opt.embedding_path).strip('"'), + loras = root / str(opt.lora_path).strip('"'), + controlnets = root / 'controlnets', + ) + +def _parse_legacy_yamlfile(root: Path, initfile: Path)->ModelPaths: + ''' + Returns tuple of (embedding_path, lora_path, controlnet_path) + ''' + # Don't use the config object because it is unforgiving of version updates + # Just use omegaconf directly + opt = OmegaConf.load(initfile) + paths = opt.InvokeAI.Paths + models = paths.get('models_dir','models') + embeddings = paths.get('embedding_dir','embeddings') + loras = paths.get('lora_dir','loras') + controlnets = paths.get('controlnet_dir','controlnets') + return ModelPaths( + models = root / models, + embeddings = root / embeddings, + loras = root /loras, + controlnets = root / controlnets, + ) + +def get_legacy_embeddings(root: Path) -> ModelPaths: + path = root / 'invokeai.init' + if path.exists(): + return _parse_legacy_initfile(root, path) + path = root / 'invokeai.yaml' + if path.exists(): + return _parse_legacy_yamlfile(root, path) + +def do_migrate(src_directory: Path, dest_directory: Path): + + dest_models = dest_directory / 'models-3.0' + dest_yaml = dest_directory / 'configs/models.yaml-3.0' + + paths = get_legacy_embeddings(src_directory) + + with open(dest_yaml,'w') as yaml_file: + migrator = MigrateTo3(src_directory, + dest_models, + yaml_file, + src_paths = paths, + ) + migrator.migrate() + + shutil.rmtree(dest_directory / 'models.orig', ignore_errors=True) + (dest_directory / 'models').replace(dest_directory / 'models.orig') + dest_models.replace(dest_directory / 'models') + + (dest_directory /'configs/models.yaml').replace(dest_directory / 'configs/models.yaml.orig') + dest_yaml.replace(dest_directory / 'configs/models.yaml') + print(f"""Migration successful. +Original models directory moved to {dest_directory}/models.orig +Original models.yaml file moved to {dest_directory}/configs/models.yaml.orig +""") + +def main(): + parser = argparse.ArgumentParser(prog="invokeai-migrate3", + description=""" +This will copy and convert the models directory and the configs/models.yaml from the InvokeAI 2.3 format +'--from-directory' root to the InvokeAI 3.0 '--to-directory' root. These may be abbreviated '--from' and '--to'.a + +The old models directory and config file will be renamed 'models.orig' and 'models.yaml.orig' respectively. +It is safe to provide the same directory for both arguments, but it is better to use the invokeai_configure +script, which will perform a full upgrade in place.""" + ) + parser.add_argument('--from-directory', + dest='root_directory', + type=Path, + required=True, + help='Source InvokeAI 2.3 root directory (containing "invokeai.init" or "invokeai.yaml")' + ) + parser.add_argument('--to-directory', + dest='dest_directory', + type=Path, + required=True, + help='Destination InvokeAI 3.0 directory (containing "invokeai.yaml")' + ) +# TO DO: Implement full directory scanning +# parser.add_argument('--all-models', +# action="store_true", +# help='Migrate all models found in `models` directory, not just those mentioned in models.yaml', +# ) + args = parser.parse_args() + root_directory = args.root_directory + assert root_directory.is_dir(), f"{root_directory} is not a valid directory" + assert (root_directory / 'models').is_dir(), f"{root_directory} does not contain a 'models' subdirectory" + assert (root_directory / 'invokeai.init').exists() or (root_directory / 'invokeai.yaml').exists(), f"{root_directory} does not contain an InvokeAI init file." + + dest_directory = args.dest_directory + assert dest_directory.is_dir(), f"{dest_directory} is not a valid directory" + assert (dest_directory / 'models').is_dir(), f"{dest_directory} does not contain a 'models' subdirectory" + assert (dest_directory / 'invokeai.yaml').exists(), f"{dest_directory} does not contain an InvokeAI init file." + + do_migrate(root_directory,dest_directory) + +if __name__ == '__main__': + main() + + + diff --git a/invokeai/backend/install/model_install_backend.py b/invokeai/backend/install/model_install_backend.py new file mode 100644 index 0000000000..1c2f4d2fc1 --- /dev/null +++ b/invokeai/backend/install/model_install_backend.py @@ -0,0 +1,467 @@ +""" +Utility (backend) functions used by model_install.py +""" +import os +import shutil +import warnings +from dataclasses import dataclass,field +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import List, Dict, Callable, Union, Set + +import requests +from diffusers import StableDiffusionPipeline +from huggingface_hub import hf_hub_url, HfFolder, HfApi +from omegaconf import OmegaConf +from tqdm import tqdm + +import invokeai.configs as configs + +from invokeai.app.services.config import InvokeAIAppConfig +from invokeai.backend.model_management import ModelManager, ModelType, BaseModelType, ModelVariantType +from invokeai.backend.model_management.model_probe import ModelProbe, SchedulerPredictionType, ModelProbeInfo +from invokeai.backend.util import download_with_resume +from ..util.logging import InvokeAILogger + +warnings.filterwarnings("ignore") + +# --------------------------globals----------------------- +config = InvokeAIAppConfig.get_config() +logger = InvokeAILogger.getLogger(name='InvokeAI') + +# the initial "configs" dir is now bundled in the `invokeai.configs` package +Dataset_path = Path(configs.__path__[0]) / "INITIAL_MODELS.yaml" + +Config_preamble = """ +# This file describes the alternative machine learning models +# available to InvokeAI script. +# +# To add a new model, follow the examples below. Each +# model requires a model config file, a weights file, +# and the width and height of the images it +# was trained on. +""" + +LEGACY_CONFIGS = { + BaseModelType.StableDiffusion1: { + ModelVariantType.Normal: 'v1-inference.yaml', + ModelVariantType.Inpaint: 'v1-inpainting-inference.yaml', + }, + + BaseModelType.StableDiffusion2: { + ModelVariantType.Normal: { + SchedulerPredictionType.Epsilon: 'v2-inference.yaml', + SchedulerPredictionType.VPrediction: 'v2-inference-v.yaml', + }, + ModelVariantType.Inpaint: { + SchedulerPredictionType.Epsilon: 'v2-inpainting-inference.yaml', + SchedulerPredictionType.VPrediction: 'v2-inpainting-inference-v.yaml', + } + } +} + +@dataclass +class ModelInstallList: + '''Class for listing models to be installed/removed''' + install_models: List[str] = field(default_factory=list) + remove_models: List[str] = field(default_factory=list) + +@dataclass +class InstallSelections(): + install_models: List[str]= field(default_factory=list) + remove_models: List[str]=field(default_factory=list) +# scan_directory: Path = None +# autoscan_on_startup: bool=False + +@dataclass +class ModelLoadInfo(): + name: str + model_type: ModelType + base_type: BaseModelType + path: Path = None + repo_id: str = None + description: str = '' + installed: bool = False + recommended: bool = False + default: bool = False + +class ModelInstall(object): + def __init__(self, + config:InvokeAIAppConfig, + prediction_type_helper: Callable[[Path],SchedulerPredictionType]=None, + model_manager: ModelManager = None, + access_token:str = None): + self.config = config + self.mgr = model_manager or ModelManager(config.model_conf_path) + self.datasets = OmegaConf.load(Dataset_path) + self.prediction_helper = prediction_type_helper + self.access_token = access_token or HfFolder.get_token() + self.reverse_paths = self._reverse_paths(self.datasets) + + def all_models(self)->Dict[str,ModelLoadInfo]: + ''' + Return dict of model_key=>ModelLoadInfo objects. + This method consolidates and simplifies the entries in both + models.yaml and INITIAL_MODELS.yaml so that they can + be treated uniformly. It also sorts the models alphabetically + by their name, to improve the display somewhat. + ''' + model_dict = dict() + + # first populate with the entries in INITIAL_MODELS.yaml + for key, value in self.datasets.items(): + name,base,model_type = ModelManager.parse_key(key) + value['name'] = name + value['base_type'] = base + value['model_type'] = model_type + model_dict[key] = ModelLoadInfo(**value) + + # supplement with entries in models.yaml + installed_models = self.mgr.list_models() + for md in installed_models: + base = md['base_model'] + model_type = md['type'] + name = md['name'] + key = ModelManager.create_key(name, base, model_type) + if key in model_dict: + model_dict[key].installed = True + else: + model_dict[key] = ModelLoadInfo( + name = name, + base_type = base, + model_type = model_type, + path = value.get('path'), + installed = True, + ) + return {x : model_dict[x] for x in sorted(model_dict.keys(),key=lambda y: model_dict[y].name.lower())} + + def starter_models(self)->Set[str]: + models = set() + for key, value in self.datasets.items(): + name,base,model_type = ModelManager.parse_key(key) + if model_type==ModelType.Main: + models.add(key) + return models + + def recommended_models(self)->Set[str]: + starters = self.starter_models() + return set([x for x in starters if self.datasets[x].get('recommended',False)]) + + def default_model(self)->str: + starters = self.starter_models() + defaults = [x for x in starters if self.datasets[x].get('default',False)] + return defaults[0] + + def install(self, selections: InstallSelections): + job = 1 + jobs = len(selections.remove_models) + len(selections.install_models) + + # remove requested models + for key in selections.remove_models: + name,base,mtype = self.mgr.parse_key(key) + logger.info(f'Deleting {mtype} model {name} [{job}/{jobs}]') + self.mgr.del_model(name,base,mtype) + job += 1 + + # add requested models + for path in selections.install_models: + logger.info(f'Installing {path} [{job}/{jobs}]') + self.heuristic_install(path) + job += 1 + + self.mgr.commit() + + def heuristic_install(self, + model_path_id_or_url: Union[str,Path], + models_installed: Set[Path]=None)->Set[Path]: + + if not models_installed: + models_installed = set() + + # A little hack to allow nested routines to retrieve info on the requested ID + self.current_id = model_path_id_or_url + path = Path(model_path_id_or_url) + + try: + # checkpoint file, or similar + if path.is_file(): + models_installed.add(self._install_path(path)) + + # folders style or similar + elif path.is_dir() and any([(path/x).exists() for x in {'config.json','model_index.json','learned_embeds.bin'}]): + models_installed.add(self._install_path(path)) + + # recursive scan + elif path.is_dir(): + for child in path.iterdir(): + self.heuristic_install(child, models_installed=models_installed) + + # huggingface repo + elif len(str(path).split('/')) == 2: + models_installed.add(self._install_repo(str(path))) + + # a URL + elif model_path_id_or_url.startswith(("http:", "https:", "ftp:")): + models_installed.add(self._install_url(model_path_id_or_url)) + + else: + logger.warning(f'{str(model_path_id_or_url)} is not recognized as a local path, repo ID or URL. Skipping') + + except ValueError as e: + logger.error(str(e)) + + return models_installed + + # install a model from a local path. The optional info parameter is there to prevent + # the model from being probed twice in the event that it has already been probed. + def _install_path(self, path: Path, info: ModelProbeInfo=None)->Path: + try: + # logger.debug(f'Probing {path}') + info = info or ModelProbe().heuristic_probe(path,self.prediction_helper) + model_name = path.stem if info.format=='checkpoint' else path.name + if self.mgr.model_exists(model_name, info.base_type, info.model_type): + raise ValueError(f'A model named "{model_name}" is already installed.') + attributes = self._make_attributes(path,info) + self.mgr.add_model(model_name = model_name, + base_model = info.base_type, + model_type = info.model_type, + model_attributes = attributes, + ) + except Exception as e: + logger.warning(f'{str(e)} Skipping registration.') + return path + + def _install_url(self, url: str)->Path: + # copy to a staging area, probe, import and delete + with TemporaryDirectory(dir=self.config.models_path) as staging: + location = download_with_resume(url,Path(staging)) + if not location: + logger.error(f'Unable to download {url}. Skipping.') + info = ModelProbe().heuristic_probe(location) + dest = self.config.models_path / info.base_type.value / info.model_type.value / location.name + models_path = shutil.move(location,dest) + + # staged version will be garbage-collected at this time + return self._install_path(Path(models_path), info) + + def _install_repo(self, repo_id: str)->Path: + hinfo = HfApi().model_info(repo_id) + + # we try to figure out how to download this most economically + # list all the files in the repo + files = [x.rfilename for x in hinfo.siblings] + location = None + + with TemporaryDirectory(dir=self.config.models_path) as staging: + staging = Path(staging) + if 'model_index.json' in files: + location = self._download_hf_pipeline(repo_id, staging) # pipeline + else: + for suffix in ['safetensors','bin']: + if f'pytorch_lora_weights.{suffix}' in files: + location = self._download_hf_model(repo_id, ['pytorch_lora_weights.bin'], staging) # LoRA + break + elif self.config.precision=='float16' and f'diffusion_pytorch_model.fp16.{suffix}' in files: # vae, controlnet or some other standalone + files = ['config.json', f'diffusion_pytorch_model.fp16.{suffix}'] + location = self._download_hf_model(repo_id, files, staging) + break + elif f'diffusion_pytorch_model.{suffix}' in files: + files = ['config.json', f'diffusion_pytorch_model.{suffix}'] + location = self._download_hf_model(repo_id, files, staging) + break + elif f'learned_embeds.{suffix}' in files: + location = self._download_hf_model(repo_id, ['learned_embeds.suffix'], staging) + break + if not location: + logger.warning(f'Could not determine type of repo {repo_id}. Skipping install.') + return + + info = ModelProbe().heuristic_probe(location, self.prediction_helper) + if not info: + logger.warning(f'Could not probe {location}. Skipping install.') + return + dest = self.config.models_path / info.base_type.value / info.model_type.value / self._get_model_name(repo_id,location) + if dest.exists(): + shutil.rmtree(dest) + shutil.copytree(location,dest) + return self._install_path(dest, info) + + def _get_model_name(self,path_name: str, location: Path)->str: + ''' + Calculate a name for the model - primitive implementation. + ''' + if key := self.reverse_paths.get(path_name): + (name, base, mtype) = ModelManager.parse_key(key) + return name + else: + return location.stem + + def _make_attributes(self, path: Path, info: ModelProbeInfo)->dict: + model_name = path.name if path.is_dir() else path.stem + description = f'{info.base_type.value} {info.model_type.value} model {model_name}' + if key := self.reverse_paths.get(self.current_id): + if key in self.datasets: + description = self.datasets[key].get('description') or description + + rel_path = self.relative_to_root(path) + + attributes = dict( + path = str(rel_path), + description = str(description), + model_format = info.format, + ) + if info.model_type == ModelType.Main: + attributes.update(dict(variant = info.variant_type,)) + if info.format=="checkpoint": + try: + possible_conf = path.with_suffix('.yaml') + if possible_conf.exists(): + legacy_conf = str(self.relative_to_root(possible_conf)) + elif info.base_type == BaseModelType.StableDiffusion2: + legacy_conf = Path(self.config.legacy_conf_dir, LEGACY_CONFIGS[info.base_type][info.variant_type][info.prediction_type]) + else: + legacy_conf = Path(self.config.legacy_conf_dir, LEGACY_CONFIGS[info.base_type][info.variant_type]) + except KeyError: + legacy_conf = Path(self.config.legacy_conf_dir, 'v1-inference.yaml') # best guess + + attributes.update( + dict( + config = str(legacy_conf) + ) + ) + return attributes + + def relative_to_root(self, path: Path)->Path: + root = self.config.root_path + if path.is_relative_to(root): + return path.relative_to(root) + else: + return path + + def _download_hf_pipeline(self, repo_id: str, staging: Path)->Path: + ''' + This retrieves a StableDiffusion model from cache or remote and then + does a save_pretrained() to the indicated staging area. + ''' + _,name = repo_id.split("/") + revisions = ['fp16','main'] if self.config.precision=='float16' else ['main'] + model = None + for revision in revisions: + try: + model = StableDiffusionPipeline.from_pretrained(repo_id,revision=revision,safety_checker=None) + except: # most errors are due to fp16 not being present. Fix this to catch other errors + pass + if model: + break + if not model: + logger.error(f'Diffusers model {repo_id} could not be downloaded. Skipping.') + return None + model.save_pretrained(staging / name, safe_serialization=True) + return staging / name + + def _download_hf_model(self, repo_id: str, files: List[str], staging: Path)->Path: + _,name = repo_id.split("/") + location = staging / name + paths = list() + for filename in files: + p = hf_download_with_resume(repo_id, + model_dir=location, + model_name=filename, + access_token = self.access_token + ) + if p: + paths.append(p) + else: + logger.warning(f'Could not download {filename} from {repo_id}.') + + return location if len(paths)>0 else None + + @classmethod + def _reverse_paths(cls,datasets)->dict: + ''' + Reverse mapping from repo_id/path to destination name. + ''' + return {v.get('path') or v.get('repo_id') : k for k, v in datasets.items()} + +# ------------------------------------- +def yes_or_no(prompt: str, default_yes=True): + default = "y" if default_yes else "n" + response = input(f"{prompt} [{default}] ") or default + if default_yes: + return response[0] not in ("n", "N") + else: + return response[0] in ("y", "Y") + +# --------------------------------------------- +def hf_download_from_pretrained( + model_class: object, model_name: str, destination: Path, **kwargs +): + logger = InvokeAILogger.getLogger('InvokeAI') + logger.addFilter(lambda x: 'fp16 is not a valid' not in x.getMessage()) + + model = model_class.from_pretrained( + model_name, + resume_download=True, + **kwargs, + ) + model.save_pretrained(destination, safe_serialization=True) + return destination + +# --------------------------------------------- +def hf_download_with_resume( + repo_id: str, + model_dir: str, + model_name: str, + model_dest: Path = None, + access_token: str = None, +) -> Path: + model_dest = model_dest or Path(os.path.join(model_dir, model_name)) + os.makedirs(model_dir, exist_ok=True) + + url = hf_hub_url(repo_id, model_name) + + header = {"Authorization": f"Bearer {access_token}"} if access_token else {} + open_mode = "wb" + exist_size = 0 + + if os.path.exists(model_dest): + exist_size = os.path.getsize(model_dest) + header["Range"] = f"bytes={exist_size}-" + open_mode = "ab" + + resp = requests.get(url, headers=header, stream=True) + total = int(resp.headers.get("content-length", 0)) + + if ( + resp.status_code == 416 + ): # "range not satisfiable", which means nothing to return + logger.info(f"{model_name}: complete file found. Skipping.") + return model_dest + elif resp.status_code == 404: + logger.warning("File not found") + return None + elif resp.status_code != 200: + logger.warning(f"{model_name}: {resp.reason}") + elif exist_size > 0: + logger.info(f"{model_name}: partial file found. Resuming...") + else: + logger.info(f"{model_name}: Downloading...") + + try: + with open(model_dest, open_mode) as file, tqdm( + desc=model_name, + initial=exist_size, + total=total + exist_size, + unit="iB", + unit_scale=True, + unit_divisor=1000, + ) as bar: + for data in resp.iter_content(chunk_size=1024): + size = file.write(data) + bar.update(size) + except Exception as e: + logger.error(f"An error occurred while downloading {model_name}: {str(e)}") + return None + return model_dest + + diff --git a/invokeai/backend/model_management/__init__.py b/invokeai/backend/model_management/__init__.py index 64ef4e9ee9..fb3b20a20a 100644 --- a/invokeai/backend/model_management/__init__.py +++ b/invokeai/backend/model_management/__init__.py @@ -1,11 +1,7 @@ """ Initialization file for invokeai.backend.model_management """ -from .convert_ckpt_to_diffusers import ( - convert_ckpt_to_diffusers, - load_pipeline_from_original_stable_diffusion_ckpt, -) -from .model_manager import ModelManager,SDModelComponent - - +from .model_manager import ModelManager, ModelInfo +from .model_cache import ModelCache +from .models import BaseModelType, ModelType, SubModelType, ModelVariantType diff --git a/invokeai/backend/model_management/convert_ckpt_to_diffusers.py b/invokeai/backend/model_management/convert_ckpt_to_diffusers.py index 467fe39155..1eeee92fb7 100644 --- a/invokeai/backend/model_management/convert_ckpt_to_diffusers.py +++ b/invokeai/backend/model_management/convert_ckpt_to_diffusers.py @@ -26,12 +26,15 @@ import torch from safetensors.torch import load_file import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig -from .model_manager import ModelManager, SDLegacyType +from .model_manager import ModelManager +from .model_cache import ModelCache +from .models import BaseModelType, ModelVariantType try: from omegaconf import OmegaConf + from omegaconf.dictconfig import DictConfig except ImportError: raise ImportError( "OmegaConf is required to convert the LDM checkpoints. Please install it with `pip install OmegaConf`." @@ -56,10 +59,6 @@ from diffusers.pipelines.latent_diffusion.pipeline_latent_diffusion import ( LDMBertConfig, LDMBertModel, ) -from diffusers.pipelines.paint_by_example import ( - PaintByExampleImageEncoder, - PaintByExamplePipeline, -) from diffusers.pipelines.stable_diffusion.safety_checker import ( StableDiffusionSafetyChecker, ) @@ -74,6 +73,10 @@ from transformers import ( from ..stable_diffusion import StableDiffusionGeneratorPipeline +# TODO: redo in future +#CONVERT_MODEL_ROOT = InvokeAIAppConfig.get_config().models_path / "core" / "convert" +CONVERT_MODEL_ROOT = InvokeAIAppConfig.get_config().root_path / "models" / "core" / "convert" + def shave_segments(path, n_shave_prefix_segments=1): """ Removes segments. Positive values shave the first segments, negative shave the last segments. @@ -158,17 +161,17 @@ def renew_vae_attention_paths(old_list, n_shave_prefix_segments=0): new_item = new_item.replace("norm.weight", "group_norm.weight") new_item = new_item.replace("norm.bias", "group_norm.bias") - new_item = new_item.replace("q.weight", "query.weight") - new_item = new_item.replace("q.bias", "query.bias") + new_item = new_item.replace("q.weight", "to_q.weight") + new_item = new_item.replace("q.bias", "to_q.bias") - new_item = new_item.replace("k.weight", "key.weight") - new_item = new_item.replace("k.bias", "key.bias") + new_item = new_item.replace("k.weight", "to_k.weight") + new_item = new_item.replace("k.bias", "to_k.bias") - new_item = new_item.replace("v.weight", "value.weight") - new_item = new_item.replace("v.bias", "value.bias") + new_item = new_item.replace("v.weight", "to_v.weight") + new_item = new_item.replace("v.bias", "to_v.bias") - new_item = new_item.replace("proj_out.weight", "proj_attn.weight") - new_item = new_item.replace("proj_out.bias", "proj_attn.bias") + new_item = new_item.replace("proj_out.weight", "to_out.0.weight") + new_item = new_item.replace("proj_out.bias", "to_out.0.bias") new_item = shave_segments( new_item, n_shave_prefix_segments=n_shave_prefix_segments @@ -183,7 +186,6 @@ def assign_to_checkpoint( paths, checkpoint, old_checkpoint, - attention_paths_to_split=None, additional_replacements=None, config=None, ): @@ -198,35 +200,9 @@ def assign_to_checkpoint( paths, list ), "Paths should be a list of dicts containing 'old' and 'new' keys." - # Splits the attention layers into three variables. - if attention_paths_to_split is not None: - for path, path_map in attention_paths_to_split.items(): - old_tensor = old_checkpoint[path] - channels = old_tensor.shape[0] // 3 - - target_shape = (-1, channels) if len(old_tensor.shape) == 3 else (-1) - - num_heads = old_tensor.shape[0] // config["num_head_channels"] // 3 - - old_tensor = old_tensor.reshape( - (num_heads, 3 * channels // num_heads) + old_tensor.shape[1:] - ) - query, key, value = old_tensor.split(channels // num_heads, dim=1) - - checkpoint[path_map["query"]] = query.reshape(target_shape) - checkpoint[path_map["key"]] = key.reshape(target_shape) - checkpoint[path_map["value"]] = value.reshape(target_shape) - for path in paths: new_path = path["new"] - # These have already been assigned - if ( - attention_paths_to_split is not None - and new_path in attention_paths_to_split - ): - continue - # Global renaming happens here new_path = new_path.replace("middle_block.0", "mid_block.resnets.0") new_path = new_path.replace("middle_block.1", "mid_block.attentions.0") @@ -245,14 +221,14 @@ def assign_to_checkpoint( def conv_attn_to_linear(checkpoint): keys = list(checkpoint.keys()) - attn_keys = ["query.weight", "key.weight", "value.weight"] + attn_keys = ["to_q.weight", "to_k.weight", "to_v.weight"] for key in keys: if ".".join(key.split(".")[-2:]) in attn_keys: if checkpoint[key].ndim > 2: checkpoint[key] = checkpoint[key][:, :, 0, 0] - elif "proj_attn.weight" in key: + elif "to_out.0.weight" in key: if checkpoint[key].ndim > 2: - checkpoint[key] = checkpoint[key][:, :, 0] + checkpoint[key] = checkpoint[key][:, :, 0, 0] def create_unet_diffusers_config(original_config, image_size: int): @@ -612,16 +588,29 @@ def convert_ldm_unet_checkpoint(checkpoint, config, path=None, extract_ema=False return new_checkpoint - def convert_ldm_vae_checkpoint(checkpoint, config): - # extract state dict for VAE - vae_state_dict = {} - vae_key = "first_stage_model." - keys = list(checkpoint.keys()) - for key in keys: - if key.startswith(vae_key): - vae_state_dict[key.replace(vae_key, "")] = checkpoint.get(key) + # Extract state dict for VAE. Works both with burnt-in + # VAEs, and with standalone VAEs. + # checkpoint can either be a all-in-one stable diffusion + # model, or an isolated vae .ckpt. This tests for + # a key that will be present in the all-in-one model + # that isn't present in the isolated ckpt. + probe_key = "first_stage_model.encoder.conv_in.weight" + if probe_key in checkpoint: + vae_state_dict = {} + vae_key = "first_stage_model." + keys = list(checkpoint.keys()) + for key in keys: + if key.startswith(vae_key): + vae_state_dict[key.replace(vae_key, "")] = checkpoint.get(key) + else: + vae_state_dict = checkpoint + + new_checkpoint = convert_ldm_vae_state_dict(vae_state_dict, config) + return new_checkpoint + +def convert_ldm_vae_state_dict(vae_state_dict, config): new_checkpoint = {} new_checkpoint["encoder.conv_in.weight"] = vae_state_dict["encoder.conv_in.weight"] @@ -841,10 +830,7 @@ def convert_ldm_bert_checkpoint(checkpoint, config): def convert_ldm_clip_checkpoint(checkpoint): - text_model = CLIPTextModel.from_pretrained( - "openai/clip-vit-large-patch14", cache_dir=get_invokeai_config().cache_dir - ) - + text_model = CLIPTextModel.from_pretrained(CONVERT_MODEL_ROOT / 'clip-vit-large-patch14') keys = list(checkpoint.keys()) text_model_dict = {} @@ -896,82 +882,10 @@ protected = {re.escape(x[0]): x[1] for x in textenc_transformer_conversion_lst} textenc_pattern = re.compile("|".join(protected.keys())) -def convert_paint_by_example_checkpoint(checkpoint): - cache_dir = get_invokeai_config().cache_dir - config = CLIPVisionConfig.from_pretrained( - "openai/clip-vit-large-patch14", cache_dir=cache_dir - ) - model = PaintByExampleImageEncoder(config) - - keys = list(checkpoint.keys()) - - text_model_dict = {} - - for key in keys: - if key.startswith("cond_stage_model.transformer"): - text_model_dict[key[len("cond_stage_model.transformer.") :]] = checkpoint[ - key - ] - - # load clip vision - model.model.load_state_dict(text_model_dict) - - # load mapper - keys_mapper = { - k[len("cond_stage_model.mapper.res") :]: v - for k, v in checkpoint.items() - if k.startswith("cond_stage_model.mapper") - } - - MAPPING = { - "attn.c_qkv": ["attn1.to_q", "attn1.to_k", "attn1.to_v"], - "attn.c_proj": ["attn1.to_out.0"], - "ln_1": ["norm1"], - "ln_2": ["norm3"], - "mlp.c_fc": ["ff.net.0.proj"], - "mlp.c_proj": ["ff.net.2"], - } - - mapped_weights = {} - for key, value in keys_mapper.items(): - prefix = key[: len("blocks.i")] - suffix = key.split(prefix)[-1].split(".")[-1] - name = key.split(prefix)[-1].split(suffix)[0][1:-1] - mapped_names = MAPPING[name] - - num_splits = len(mapped_names) - for i, mapped_name in enumerate(mapped_names): - new_name = ".".join([prefix, mapped_name, suffix]) - shape = value.shape[0] // num_splits - mapped_weights[new_name] = value[i * shape : (i + 1) * shape] - - model.mapper.load_state_dict(mapped_weights) - - # load final layer norm - model.final_layer_norm.load_state_dict( - { - "bias": checkpoint["cond_stage_model.final_ln.bias"], - "weight": checkpoint["cond_stage_model.final_ln.weight"], - } - ) - - # load final proj - model.proj_out.load_state_dict( - { - "bias": checkpoint["proj_out.bias"], - "weight": checkpoint["proj_out.weight"], - } - ) - - # load uncond vector - model.uncond_vector.data = torch.nn.Parameter(checkpoint["learnable_vector"]) - return model - - def convert_open_clip_checkpoint(checkpoint): - cache_dir = get_invokeai_config().cache_dir text_model = CLIPTextModel.from_pretrained( - "stabilityai/stable-diffusion-2", subfolder="text_encoder", cache_dir=cache_dir + CONVERT_MODEL_ROOT / 'stable-diffusion-2-clip', + subfolder='text_encoder', ) keys = list(checkpoint.keys()) @@ -1037,7 +951,7 @@ def convert_open_clip_checkpoint(checkpoint): return text_model -def replace_checkpoint_vae(checkpoint, vae_path:str): +def replace_checkpoint_vae(checkpoint, vae_path: str): if vae_path.endswith(".safetensors"): vae_ckpt = load_file(vae_path) else: @@ -1047,22 +961,28 @@ def replace_checkpoint_vae(checkpoint, vae_path:str): new_key = f'first_stage_model.{vae_key}' checkpoint[new_key] = state_dict[vae_key] +def convert_ldm_vae_to_diffusers(checkpoint, vae_config: DictConfig, image_size: int) -> AutoencoderKL: + vae_config = create_vae_diffusers_config( + vae_config, image_size=image_size + ) + + converted_vae_checkpoint = convert_ldm_vae_checkpoint( + checkpoint, vae_config + ) + + vae = AutoencoderKL(**vae_config) + vae.load_state_dict(converted_vae_checkpoint) + return vae + def load_pipeline_from_original_stable_diffusion_ckpt( checkpoint_path: str, - original_config_file: str = None, - num_in_channels: int = None, - scheduler_type: str = "pndm", - pipeline_type: str = None, - image_size: int = None, - prediction_type: str = None, + model_version: BaseModelType, + model_variant: ModelVariantType, + original_config_file: str, extract_ema: bool = True, - upcast_attn: bool = False, - vae: AutoencoderKL = None, - vae_path: str = None, precision: torch.dtype = torch.float32, - return_generator_pipeline: bool = False, - scan_needed:bool=True, -) -> Union[StableDiffusionPipeline, StableDiffusionGeneratorPipeline]: + scan_needed: bool = True, +) -> StableDiffusionPipeline: """ Load a Stable Diffusion pipeline object from a CompVis-style `.ckpt`/`.safetensors` file and (ideally) a `.yaml` config file. @@ -1074,147 +994,69 @@ def load_pipeline_from_original_stable_diffusion_ckpt( :param checkpoint_path: Path to `.ckpt` file. :param original_config_file: Path to `.yaml` config file corresponding to the original architecture. If `None`, will be automatically inferred by looking for a key that only exists in SD2.0 models. - :param image_size: The image size that the model was trained on. Use 512 for Stable Diffusion v1.X and Stable Diffusion v2 - Base. Use 768 for Stable Diffusion v2. - :param prediction_type: The prediction type that the model was trained on. Use `'epsilon'` for Stable Diffusion - v1.X and Stable Diffusion v2 Base. Use `'v-prediction'` for Stable Diffusion v2. - :param num_in_channels: The number of input channels. If `None` number of input channels will be automatically - inferred. :param scheduler_type: Type of scheduler to use. Should be one of `["pndm", "lms", "heun", "euler", "euler-ancestral", "dpm", "ddim"]`. :param model_type: The pipeline type. `None` to automatically infer, or one of - `["FrozenOpenCLIPEmbedder", "FrozenCLIPEmbedder", "PaintByExample"]`. :param extract_ema: Only relevant for + `["FrozenOpenCLIPEmbedder", "FrozenCLIPEmbedder"]`. :param extract_ema: Only relevant for checkpoints that have both EMA and non-EMA weights. Whether to extract the EMA weights or not. Defaults to `False`. Pass `True` to extract the EMA weights. EMA weights usually yield higher quality images for inference. Non-EMA weights are usually better to continue fine-tuning. :param precision: precision to use - torch.float16, torch.float32 or torch.autocast - :param upcast_attention: Whether the attention computation should always be upcasted. This is necessary when - running stable diffusion 2.1. - :param vae: A diffusers VAE to load into the pipeline. - :param vae_path: Path to a checkpoint VAE that will be converted into diffusers and loaded into the pipeline. """ - config = get_invokeai_config() + if not isinstance(checkpoint_path, Path): + checkpoint_path = Path(checkpoint_path) + with warnings.catch_warnings(): warnings.simplefilter("ignore") verbosity = dlogging.get_verbosity() dlogging.set_verbosity_error() - if Path(checkpoint_path).suffix == '.ckpt': - if scan_needed: - ModelManager.scan_model(checkpoint_path,checkpoint_path) - checkpoint = torch.load(checkpoint_path) - else: + if checkpoint_path.suffix == ".safetensors": checkpoint = load_file(checkpoint_path) - - cache_dir = config.cache_dir - pipeline_class = ( - StableDiffusionGeneratorPipeline - if return_generator_pipeline - else StableDiffusionPipeline - ) - - # Sometimes models don't have the global_step item - if "global_step" in checkpoint: - global_step = checkpoint["global_step"] else: - logger.debug("global_step key not found in model") - global_step = None + if scan_needed: + ModelCache.scan_model(checkpoint_path, checkpoint_path) + checkpoint = torch.load(checkpoint_path) # sometimes there is a state_dict key and sometimes not if "state_dict" in checkpoint: checkpoint = checkpoint["state_dict"] - upcast_attention = False - if original_config_file is None: - model_type = ModelManager.probe_model_type(checkpoint) - - if model_type == SDLegacyType.V2_v: - original_config_file = ( - config.legacy_conf_path / "v2-inference-v.yaml" - ) - if global_step == 110000: - # v2.1 needs to upcast attention - upcast_attention = True - elif model_type == SDLegacyType.V2_e: - original_config_file = ( - config.legacy_conf_path / "v2-inference.yaml" - ) - elif model_type == SDLegacyType.V1_INPAINT: - original_config_file = ( - config.legacy_conf_path / "v1-inpainting-inference.yaml" - ) - - elif model_type == SDLegacyType.V1: - original_config_file = ( - config.legacy_conf_path / "v1-inference.yaml" - ) - - else: - raise Exception("Unknown checkpoint type") - original_config = OmegaConf.load(original_config_file) - if num_in_channels is not None: - original_config["model"]["params"]["unet_config"]["params"][ - "in_channels" - ] = num_in_channels - - if ( - "parameterization" in original_config["model"]["params"] - and original_config["model"]["params"]["parameterization"] == "v" - ): - if prediction_type is None: - # NOTE: For stable diffusion 2 base it is recommended to pass `prediction_type=="epsilon"` - # as it relies on a brittle global step parameter here - prediction_type = "epsilon" if global_step == 875000 else "v_prediction" - if image_size is None: - # NOTE: For stable diffusion 2 base one has to pass `image_size==512` - # as it relies on a brittle global step parameter here - image_size = 512 if global_step == 875000 else 768 + if model_version == BaseModelType.StableDiffusion2 and original_config["model"]["params"]["parameterization"] == "v": + prediction_type = "v_prediction" + upcast_attention = True + image_size = 768 else: - if prediction_type is None: - prediction_type = "epsilon" - if image_size is None: - image_size = 512 + prediction_type = "epsilon" + upcast_attention = False + image_size = 512 + + # + # convert scheduler + # num_train_timesteps = original_config.model.params.timesteps beta_start = original_config.model.params.linear_start beta_end = original_config.model.params.linear_end - scheduler = DDIMScheduler( + scheduler = PNDMScheduler( beta_end=beta_end, beta_schedule="scaled_linear", beta_start=beta_start, num_train_timesteps=num_train_timesteps, steps_offset=1, - clip_sample=False, set_alpha_to_one=False, prediction_type=prediction_type, + skip_prk_steps=True ) # make sure scheduler works correctly with DDIM scheduler.register_to_config(clip_sample=False) - if scheduler_type == "pndm": - config = dict(scheduler.config) - config["skip_prk_steps"] = True - scheduler = PNDMScheduler.from_config(config) - elif scheduler_type == "lms": - scheduler = LMSDiscreteScheduler.from_config(scheduler.config) - elif scheduler_type == "heun": - scheduler = HeunDiscreteScheduler.from_config(scheduler.config) - elif scheduler_type == "euler": - scheduler = EulerDiscreteScheduler.from_config(scheduler.config) - elif scheduler_type == "euler-ancestral": - scheduler = EulerAncestralDiscreteScheduler.from_config(scheduler.config) - elif scheduler_type == "dpm": - scheduler = DPMSolverMultistepScheduler.from_config(scheduler.config) - elif scheduler_type == 'unipc': - scheduler = UniPCMultistepScheduler.from_config(scheduler.config) - elif scheduler_type == "ddim": - scheduler = scheduler - else: - raise ValueError(f"Scheduler of type {scheduler_type} doesn't exist!") + # + # convert unet + # - # Convert the UNet2DConditionModel model. unet_config = create_unet_diffusers_config( original_config, image_size=image_size ) @@ -1227,44 +1069,25 @@ def load_pipeline_from_original_stable_diffusion_ckpt( unet.load_state_dict(converted_unet_checkpoint) - # If a replacement VAE path was specified, we'll incorporate that into - # the checkpoint model and then convert it - if vae_path: - logger.debug(f"Converting VAE {vae_path}") - replace_checkpoint_vae(checkpoint,vae_path) - # otherwise we use the original VAE, provided that - # an externally loaded diffusers VAE was not passed - elif not vae: - logger.debug("Using checkpoint model's original VAE") + # + # convert vae + # - if vae: - logger.debug("Using replacement diffusers VAE") - else: # convert the original or replacement VAE - vae_config = create_vae_diffusers_config( - original_config, image_size=image_size - ) - converted_vae_checkpoint = convert_ldm_vae_checkpoint( - checkpoint, vae_config - ) - - vae = AutoencoderKL(**vae_config) - vae.load_state_dict(converted_vae_checkpoint) + vae = convert_ldm_vae_to_diffusers( + checkpoint, + original_config, + image_size, + ) # Convert the text model. - model_type = pipeline_type - if model_type is None: - model_type = original_config.model.params.cond_stage_config.target.split( - "." - )[-1] - + model_type = original_config.model.params.cond_stage_config.target.split(".")[-1] if model_type == "FrozenOpenCLIPEmbedder": text_model = convert_open_clip_checkpoint(checkpoint) tokenizer = CLIPTokenizer.from_pretrained( - "stabilityai/stable-diffusion-2", - subfolder="tokenizer", - cache_dir=cache_dir, + CONVERT_MODEL_ROOT / 'stable-diffusion-2-clip', + subfolder='tokenizer', ) - pipe = pipeline_class( + pipe = StableDiffusionPipeline( vae=vae.to(precision), text_encoder=text_model.to(precision), tokenizer=tokenizer, @@ -1274,49 +1097,26 @@ def load_pipeline_from_original_stable_diffusion_ckpt( feature_extractor=None, requires_safety_checker=False, ) - elif model_type == "PaintByExample": - vision_model = convert_paint_by_example_checkpoint(checkpoint) - tokenizer = CLIPTokenizer.from_pretrained( - "openai/clip-vit-large-patch14", cache_dir=cache_dir - ) - feature_extractor = AutoFeatureExtractor.from_pretrained( - "CompVis/stable-diffusion-safety-checker", cache_dir=cache_dir - ) - pipe = PaintByExamplePipeline( - vae=vae, - image_encoder=vision_model, - unet=unet, - scheduler=scheduler, - safety_checker=None, - feature_extractor=feature_extractor, - ) + elif model_type in ["FrozenCLIPEmbedder", "WeightedFrozenCLIPEmbedder"]: text_model = convert_ldm_clip_checkpoint(checkpoint) - tokenizer = CLIPTokenizer.from_pretrained( - "openai/clip-vit-large-patch14", cache_dir=cache_dir - ) - safety_checker = StableDiffusionSafetyChecker.from_pretrained( - "CompVis/stable-diffusion-safety-checker", - cache_dir=config.cache_dir, - ) - feature_extractor = AutoFeatureExtractor.from_pretrained( - "CompVis/stable-diffusion-safety-checker", cache_dir=cache_dir - ) - pipe = pipeline_class( + tokenizer = CLIPTokenizer.from_pretrained(CONVERT_MODEL_ROOT / 'clip-vit-large-patch14') + safety_checker = StableDiffusionSafetyChecker.from_pretrained(CONVERT_MODEL_ROOT / 'stable-diffusion-safety-checker') + feature_extractor = AutoFeatureExtractor.from_pretrained(CONVERT_MODEL_ROOT / 'stable-diffusion-safety-checker') + pipe = StableDiffusionPipeline( vae=vae.to(precision), text_encoder=text_model.to(precision), tokenizer=tokenizer, unet=unet.to(precision), scheduler=scheduler, - safety_checker=None if return_generator_pipeline else safety_checker.to(precision), + safety_checker=safety_checker.to(precision), feature_extractor=feature_extractor, ) + else: text_config = create_ldm_bert_config(original_config) text_model = convert_ldm_bert_checkpoint(checkpoint, text_config) - tokenizer = BertTokenizerFast.from_pretrained( - "bert-base-uncased", cache_dir=cache_dir - ) + tokenizer = BertTokenizerFast.from_pretrained(CONVERT_MODEL_ROOT / "bert-base-uncased") pipe = LDMTextToImagePipeline( vqvae=vae, bert=text_model, @@ -1330,9 +1130,9 @@ def load_pipeline_from_original_stable_diffusion_ckpt( def convert_ckpt_to_diffusers( - checkpoint_path: Union[str, Path], - dump_path: Union[str, Path], - **kwargs, + checkpoint_path: Union[str, Path], + dump_path: Union[str, Path], + **kwargs, ): """ Takes all the arguments of load_pipeline_from_original_stable_diffusion_ckpt(), diff --git a/invokeai/backend/model_management/lora.py b/invokeai/backend/model_management/lora.py new file mode 100644 index 0000000000..6cfcb8dd8d --- /dev/null +++ b/invokeai/backend/model_management/lora.py @@ -0,0 +1,691 @@ +from __future__ import annotations + +import copy +from pathlib import Path +from contextlib import contextmanager +from typing import Optional, Dict, Tuple, Any + +import torch +from safetensors.torch import load_file +from torch.utils.hooks import RemovableHandle + +from diffusers.models import UNet2DConditionModel +from transformers import CLIPTextModel + +from compel.embeddings_provider import BaseTextualInversionManager + +class LoRALayerBase: + #rank: Optional[int] + #alpha: Optional[float] + #bias: Optional[torch.Tensor] + #layer_key: str + + #@property + #def scale(self): + # return self.alpha / self.rank if (self.alpha and self.rank) else 1.0 + + def __init__( + self, + layer_key: str, + values: dict, + ): + if "alpha" in values: + self.alpha = values["alpha"].item() + else: + self.alpha = None + + if ( + "bias_indices" in values + and "bias_values" in values + and "bias_size" in values + ): + self.bias = torch.sparse_coo_tensor( + values["bias_indices"], + values["bias_values"], + tuple(values["bias_size"]), + ) + + else: + self.bias = None + + self.rank = None # set in layer implementation + self.layer_key = layer_key + + def forward( + self, + module: torch.nn.Module, + input_h: Any, # for real looks like Tuple[torch.nn.Tensor] but not sure + multiplier: float, + ): + if type(module) == torch.nn.Conv2d: + op = torch.nn.functional.conv2d + extra_args = dict( + stride=module.stride, + padding=module.padding, + dilation=module.dilation, + groups=module.groups, + ) + + else: + op = torch.nn.functional.linear + extra_args = {} + + weight = self.get_weight() + + bias = self.bias if self.bias is not None else 0 + scale = self.alpha / self.rank if (self.alpha and self.rank) else 1.0 + return op( + *input_h, + (weight + bias).view(module.weight.shape), + None, + **extra_args, + ) * multiplier * scale + + def get_weight(self): + raise NotImplementedError() + + def calc_size(self) -> int: + model_size = 0 + for val in [self.bias]: + if val is not None: + model_size += val.nelement() * val.element_size() + return model_size + + def to( + self, + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = None, + ): + if self.bias is not None: + self.bias = self.bias.to(device=device, dtype=dtype) + + +# TODO: find and debug lora/locon with bias +class LoRALayer(LoRALayerBase): + #up: torch.Tensor + #mid: Optional[torch.Tensor] + #down: torch.Tensor + + def __init__( + self, + layer_key: str, + values: dict, + ): + super().__init__(layer_key, values) + + self.up = values["lora_up.weight"] + self.down = values["lora_down.weight"] + if "lora_mid.weight" in values: + self.mid = values["lora_mid.weight"] + else: + self.mid = None + + self.rank = self.down.shape[0] + + def get_weight(self): + if self.mid is not None: + up = self.up.reshape(up.shape[0], up.shape[1]) + down = self.down.reshape(up.shape[0], up.shape[1]) + weight = torch.einsum("m n w h, i m, n j -> i j w h", self.mid, up, down) + else: + weight = self.up.reshape(self.up.shape[0], -1) @ self.down.reshape(self.down.shape[0], -1) + + return weight + + def calc_size(self) -> int: + model_size = super().calc_size() + for val in [self.up, self.mid, self.down]: + if val is not None: + model_size += val.nelement() * val.element_size() + return model_size + + def to( + self, + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = None, + ): + super().to(device=device, dtype=dtype) + + self.up = self.up.to(device=device, dtype=dtype) + self.down = self.down.to(device=device, dtype=dtype) + + if self.mid is not None: + self.mid = self.mid.to(device=device, dtype=dtype) + + +class LoHALayer(LoRALayerBase): + #w1_a: torch.Tensor + #w1_b: torch.Tensor + #w2_a: torch.Tensor + #w2_b: torch.Tensor + #t1: Optional[torch.Tensor] = None + #t2: Optional[torch.Tensor] = None + + def __init__( + self, + layer_key: str, + values: dict, + ): + super().__init__(layer_key, values) + + self.w1_a = values["hada_w1_a"] + self.w1_b = values["hada_w1_b"] + self.w2_a = values["hada_w2_a"] + self.w2_b = values["hada_w2_b"] + + if "hada_t1" in values: + self.t1 = values["hada_t1"] + else: + self.t1 = None + + if "hada_t2" in values: + self.t2 = values["hada_t2"] + else: + self.t2 = None + + self.rank = self.w1_b.shape[0] + + def get_weight(self): + if self.t1 is None: + weight = (self.w1_a @ self.w1_b) * (self.w2_a @ self.w2_b) + + else: + rebuild1 = torch.einsum( + "i j k l, j r, i p -> p r k l", self.t1, self.w1_b, self.w1_a + ) + rebuild2 = torch.einsum( + "i j k l, j r, i p -> p r k l", self.t2, self.w2_b, self.w2_a + ) + weight = rebuild1 * rebuild2 + + return weight + + def calc_size(self) -> int: + model_size = super().calc_size() + for val in [self.w1_a, self.w1_b, self.w2_a, self.w2_b, self.t1, self.t2]: + if val is not None: + model_size += val.nelement() * val.element_size() + return model_size + + def to( + self, + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = None, + ): + super().to(device=device, dtype=dtype) + + self.w1_a = self.w1_a.to(device=device, dtype=dtype) + self.w1_b = self.w1_b.to(device=device, dtype=dtype) + if self.t1 is not None: + self.t1 = self.t1.to(device=device, dtype=dtype) + + self.w2_a = self.w2_a.to(device=device, dtype=dtype) + self.w2_b = self.w2_b.to(device=device, dtype=dtype) + if self.t2 is not None: + self.t2 = self.t2.to(device=device, dtype=dtype) + + +class LoKRLayer(LoRALayerBase): + #w1: Optional[torch.Tensor] = None + #w1_a: Optional[torch.Tensor] = None + #w1_b: Optional[torch.Tensor] = None + #w2: Optional[torch.Tensor] = None + #w2_a: Optional[torch.Tensor] = None + #w2_b: Optional[torch.Tensor] = None + #t2: Optional[torch.Tensor] = None + + def __init__( + self, + layer_key: str, + values: dict, + ): + super().__init__(layer_key, values) + + if "lokr_w1" in values: + self.w1 = values["lokr_w1"] + self.w1_a = None + self.w1_b = None + else: + self.w1 = None + self.w1_a = values["lokr_w1_a"] + self.w1_b = values["lokr_w1_b"] + + if "lokr_w2" in values: + self.w2 = values["lokr_w2"] + self.w2_a = None + self.w2_b = None + else: + self.w2 = None + self.w2_a = values["lokr_w2_a"] + self.w2_b = values["lokr_w2_b"] + + if "lokr_t2" in values: + self.t2 = values["lokr_t2"] + else: + self.t2 = None + + if "lokr_w1_b" in values: + self.rank = values["lokr_w1_b"].shape[0] + elif "lokr_w2_b" in values: + self.rank = values["lokr_w2_b"].shape[0] + else: + self.rank = None # unscaled + + def get_weight(self): + w1 = self.w1 + if w1 is None: + w1 = self.w1_a @ self.w1_b + + w2 = self.w2 + if w2 is None: + if self.t2 is None: + w2 = self.w2_a @ self.w2_b + else: + w2 = torch.einsum('i j k l, i p, j r -> p r k l', self.t2, self.w2_a, self.w2_b) + + if len(w2.shape) == 4: + w1 = w1.unsqueeze(2).unsqueeze(2) + w2 = w2.contiguous() + weight = torch.kron(w1, w2) + + return weight + + def calc_size(self) -> int: + model_size = super().calc_size() + for val in [self.w1, self.w1_a, self.w1_b, self.w2, self.w2_a, self.w2_b, self.t2]: + if val is not None: + model_size += val.nelement() * val.element_size() + return model_size + + def to( + self, + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = None, + ): + super().to(device=device, dtype=dtype) + + if self.w1 is not None: + self.w1 = self.w1.to(device=device, dtype=dtype) + else: + self.w1_a = self.w1_a.to(device=device, dtype=dtype) + self.w1_b = self.w1_b.to(device=device, dtype=dtype) + + if self.w2 is not None: + self.w2 = self.w2.to(device=device, dtype=dtype) + else: + self.w2_a = self.w2_a.to(device=device, dtype=dtype) + self.w2_b = self.w2_b.to(device=device, dtype=dtype) + + if self.t2 is not None: + self.t2 = self.t2.to(device=device, dtype=dtype) + + +class LoRAModel: #(torch.nn.Module): + _name: str + layers: Dict[str, LoRALayer] + _device: torch.device + _dtype: torch.dtype + + def __init__( + self, + name: str, + layers: Dict[str, LoRALayer], + device: torch.device, + dtype: torch.dtype, + ): + self._name = name + self._device = device or torch.cpu + self._dtype = dtype or torch.float32 + self.layers = layers + + @property + def name(self): + return self._name + + @property + def device(self): + return self._device + + @property + def dtype(self): + return self._dtype + + def to( + self, + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = None, + ) -> LoRAModel: + # TODO: try revert if exception? + for key, layer in self.layers.items(): + layer.to(device=device, dtype=dtype) + self._device = device + self._dtype = dtype + + def calc_size(self) -> int: + model_size = 0 + for _, layer in self.layers.items(): + model_size += layer.calc_size() + return model_size + + @classmethod + def from_checkpoint( + cls, + file_path: Union[str, Path], + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = None, + ): + device = device or torch.device("cpu") + dtype = dtype or torch.float32 + + if isinstance(file_path, str): + file_path = Path(file_path) + + model = cls( + device=device, + dtype=dtype, + name=file_path.stem, # TODO: + layers=dict(), + ) + + if file_path.suffix == ".safetensors": + state_dict = load_file(file_path.absolute().as_posix(), device="cpu") + else: + state_dict = torch.load(file_path, map_location="cpu") + + state_dict = cls._group_state(state_dict) + + for layer_key, values in state_dict.items(): + + # lora and locon + if "lora_down.weight" in values: + layer = LoRALayer(layer_key, values) + + # loha + elif "hada_w1_b" in values: + layer = LoHALayer(layer_key, values) + + # lokr + elif "lokr_w1_b" in values or "lokr_w1" in values: + layer = LoKRLayer(layer_key, values) + + else: + # TODO: diff/ia3/... format + print( + f">> Encountered unknown lora layer module in {self.name}: {layer_key}" + ) + return + + # lower memory consumption by removing already parsed layer values + state_dict[layer_key].clear() + + layer.to(device=device, dtype=dtype) + model.layers[layer_key] = layer + + return model + + @staticmethod + def _group_state(state_dict: dict): + state_dict_groupped = dict() + + for key, value in state_dict.items(): + stem, leaf = key.split(".", 1) + if stem not in state_dict_groupped: + state_dict_groupped[stem] = dict() + state_dict_groupped[stem][leaf] = value + + return state_dict_groupped + + +""" +loras = [ + (lora_model1, 0.7), + (lora_model2, 0.4), +] +with LoRAHelper.apply_lora_unet(unet, loras): + # unet with applied loras +# unmodified unet + +""" +# TODO: rename smth like ModelPatcher and add TI method? +class ModelPatcher: + + @staticmethod + def _resolve_lora_key(model: torch.nn.Module, lora_key: str, prefix: str) -> Tuple[str, torch.nn.Module]: + assert "." not in lora_key + + if not lora_key.startswith(prefix): + raise Exception(f"lora_key with invalid prefix: {lora_key}, {prefix}") + + module = model + module_key = "" + key_parts = lora_key[len(prefix):].split('_') + + submodule_name = key_parts.pop(0) + + while len(key_parts) > 0: + try: + module = module.get_submodule(submodule_name) + module_key += "." + submodule_name + submodule_name = key_parts.pop(0) + except: + submodule_name += "_" + key_parts.pop(0) + + module = module.get_submodule(submodule_name) + module_key = (module_key + "." + submodule_name).lstrip(".") + + return (module_key, module) + + @staticmethod + def _lora_forward_hook( + applied_loras: List[Tuple[LoraModel, float]], + layer_name: str, + ): + + def lora_forward(module, input_h, output): + if len(applied_loras) == 0: + return output + + for lora, weight in applied_loras: + layer = lora.layers.get(layer_name, None) + if layer is None: + continue + output += layer.forward(module, input_h, weight) + return output + + return lora_forward + + + @classmethod + @contextmanager + def apply_lora_unet( + cls, + unet: UNet2DConditionModel, + loras: List[Tuple[LoRAModel, float]], + ): + with cls.apply_lora(unet, loras, "lora_unet_"): + yield + + + @classmethod + @contextmanager + def apply_lora_text_encoder( + cls, + text_encoder: CLIPTextModel, + loras: List[Tuple[LoRAModel, float]], + ): + with cls.apply_lora(text_encoder, loras, "lora_te_"): + yield + + + @classmethod + @contextmanager + def apply_lora( + cls, + model: torch.nn.Module, + loras: List[Tuple[LoraModel, float]], + prefix: str, + ): + original_weights = dict() + try: + with torch.no_grad(): + for lora, lora_weight in loras: + #assert lora.device.type == "cpu" + for layer_key, layer in lora.layers.items(): + if not layer_key.startswith(prefix): + continue + + module_key, module = cls._resolve_lora_key(model, layer_key, prefix) + if module_key not in original_weights: + original_weights[module_key] = module.weight.detach().to(device="cpu", copy=True) + + # enable autocast to calc fp16 loras on cpu + with torch.autocast(device_type="cpu"): + layer_scale = layer.alpha / layer.rank if (layer.alpha and layer.rank) else 1.0 + layer_weight = layer.get_weight() * lora_weight * layer_scale + + if module.weight.shape != layer_weight.shape: + # TODO: debug on lycoris + layer_weight = layer_weight.reshape(module.weight.shape) + + module.weight += layer_weight.to(device=module.weight.device, dtype=module.weight.dtype) + + yield # wait for context manager exit + + finally: + with torch.no_grad(): + for module_key, weight in original_weights.items(): + model.get_submodule(module_key).weight.copy_(weight) + + + @classmethod + @contextmanager + def apply_ti( + cls, + tokenizer: CLIPTokenizer, + text_encoder: CLIPTextModel, + ti_list: List[Any], + ) -> Tuple[CLIPTokenizer, TextualInversionManager]: + init_tokens_count = None + new_tokens_added = None + + try: + ti_tokenizer = copy.deepcopy(tokenizer) + ti_manager = TextualInversionManager(ti_tokenizer) + init_tokens_count = text_encoder.resize_token_embeddings(None).num_embeddings + + def _get_trigger(ti, index): + trigger = ti.name + if index > 0: + trigger += f"-!pad-{i}" + return f"<{trigger}>" + + # modify tokenizer + new_tokens_added = 0 + for ti in ti_list: + for i in range(ti.embedding.shape[0]): + new_tokens_added += ti_tokenizer.add_tokens(_get_trigger(ti, i)) + + # modify text_encoder + text_encoder.resize_token_embeddings(init_tokens_count + new_tokens_added) + model_embeddings = text_encoder.get_input_embeddings() + + for ti in ti_list: + ti_tokens = [] + for i in range(ti.embedding.shape[0]): + embedding = ti.embedding[i] + trigger = _get_trigger(ti, i) + + token_id = ti_tokenizer.convert_tokens_to_ids(trigger) + if token_id == ti_tokenizer.unk_token_id: + raise RuntimeError(f"Unable to find token id for token '{trigger}'") + + if model_embeddings.weight.data[token_id].shape != embedding.shape: + raise ValueError( + f"Cannot load embedding for {trigger}. It was trained on a model with token dimension {embedding.shape[0]}, but the current model has token dimension {model_embeddings.weight.data[token_id].shape[0]}." + ) + + model_embeddings.weight.data[token_id] = embedding.to(device=text_encoder.device, dtype=text_encoder.dtype) + ti_tokens.append(token_id) + + if len(ti_tokens) > 1: + ti_manager.pad_tokens[ti_tokens[0]] = ti_tokens[1:] + + yield ti_tokenizer, ti_manager + + finally: + if init_tokens_count and new_tokens_added: + text_encoder.resize_token_embeddings(init_tokens_count) + + +class TextualInversionModel: + name: str + embedding: torch.Tensor # [n, 768]|[n, 1280] + + @classmethod + def from_checkpoint( + cls, + file_path: Union[str, Path], + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = None, + ): + if not isinstance(file_path, Path): + file_path = Path(file_path) + + result = cls() # TODO: + result.name = file_path.stem # TODO: + + if file_path.suffix == ".safetensors": + state_dict = load_file(file_path.absolute().as_posix(), device="cpu") + else: + state_dict = torch.load(file_path, map_location="cpu") + + # both v1 and v2 format embeddings + # difference mostly in metadata + if "string_to_param" in state_dict: + if len(state_dict["string_to_param"]) > 1: + print(f"Warn: Embedding \"{file_path.name}\" contains multiple tokens, which is not supported. The first token will be used.") + + result.embedding = next(iter(state_dict["string_to_param"].values())) + + # v3 (easynegative) + elif "emb_params" in state_dict: + result.embedding = state_dict["emb_params"] + + # v4(diffusers bin files) + else: + result.embedding = next(iter(state_dict.values())) + + if not isinstance(result.embedding, torch.Tensor): + raise ValueError(f"Invalid embeddings file: {file_path.name}") + + return result + + +class TextualInversionManager(BaseTextualInversionManager): + pad_tokens: Dict[int, List[int]] + tokenizer: CLIPTokenizer + + def __init__(self, tokenizer: CLIPTokenizer): + self.pad_tokens = dict() + self.tokenizer = tokenizer + + def expand_textual_inversion_token_ids_if_necessary( + self, token_ids: list[int] + ) -> list[int]: + + if len(self.pad_tokens) == 0: + return token_ids + + if token_ids[0] == self.tokenizer.bos_token_id: + raise ValueError("token_ids must not start with bos_token_id") + if token_ids[-1] == self.tokenizer.eos_token_id: + raise ValueError("token_ids must not end with eos_token_id") + + new_token_ids = [] + for token_id in token_ids: + new_token_ids.append(token_id) + if token_id in self.pad_tokens: + new_token_ids.extend(self.pad_tokens[token_id]) + + return new_token_ids + diff --git a/invokeai/backend/model_management/model_cache.py b/invokeai/backend/model_management/model_cache.py new file mode 100644 index 0000000000..77b6ac5115 --- /dev/null +++ b/invokeai/backend/model_management/model_cache.py @@ -0,0 +1,391 @@ +""" +Manage a RAM cache of diffusion/transformer models for fast switching. +They are moved between GPU VRAM and CPU RAM as necessary. If the cache +grows larger than a preset maximum, then the least recently used +model will be cleared and (re)loaded from disk when next needed. + +The cache returns context manager generators designed to load the +model into the GPU within the context, and unload outside the +context. Use like this: + + cache = ModelCache(max_models_cached=6) + with cache.get_model('runwayml/stable-diffusion-1-5') as SD1, + cache.get_model('stabilityai/stable-diffusion-2') as SD2: + do_something_in_GPU(SD1,SD2) + + +""" + +import gc +import os +import sys +import hashlib +from contextlib import suppress +from pathlib import Path +from typing import Dict, Union, types, Optional, Type, Any + +import torch + +import logging +import invokeai.backend.util.logging as logger +from invokeai.app.services.config import get_invokeai_config +from .lora import LoRAModel, TextualInversionModel +from .models import BaseModelType, ModelType, SubModelType, ModelBase + +# Maximum size of the cache, in gigs +# Default is roughly enough to hold three fp16 diffusers models in RAM simultaneously +DEFAULT_MAX_CACHE_SIZE = 6.0 + +# actual size of a gig +GIG = 1073741824 + +class ModelLocker(object): + "Forward declaration" + pass + +class ModelCache(object): + "Forward declaration" + pass + +class _CacheRecord: + size: int + model: Any + cache: ModelCache + _locks: int + + def __init__(self, cache, model: Any, size: int): + self.size = size + self.model = model + self.cache = cache + self._locks = 0 + + def lock(self): + self._locks += 1 + + def unlock(self): + self._locks -= 1 + assert self._locks >= 0 + + @property + def locked(self): + return self._locks > 0 + + @property + def loaded(self): + if self.model is not None and hasattr(self.model, "device"): + return self.model.device != self.cache.storage_device + else: + return False + + +class ModelCache(object): + def __init__( + self, + max_cache_size: float=DEFAULT_MAX_CACHE_SIZE, + execution_device: torch.device=torch.device('cuda'), + storage_device: torch.device=torch.device('cpu'), + precision: torch.dtype=torch.float16, + sequential_offload: bool=False, + lazy_offloading: bool=True, + sha_chunksize: int = 16777216, + logger: types.ModuleType = logger + ): + ''' + :param max_models: Maximum number of models to cache in CPU RAM [4] + :param execution_device: Torch device to load active model into [torch.device('cuda')] + :param storage_device: Torch device to save inactive model in [torch.device('cpu')] + :param precision: Precision for loaded models [torch.float16] + :param lazy_offloading: Keep model in VRAM until another model needs to be loaded + :param sequential_offload: Conserve VRAM by loading and unloading each stage of the pipeline sequentially + :param sha_chunksize: Chunksize to use when calculating sha256 model hash + ''' + #max_cache_size = 9999 + execution_device = torch.device('cuda') + + self.model_infos: Dict[str, ModelBase] = dict() + self.lazy_offloading = lazy_offloading + #self.sequential_offload: bool=sequential_offload + self.precision: torch.dtype=precision + self.max_cache_size: int=max_cache_size + self.execution_device: torch.device=execution_device + self.storage_device: torch.device=storage_device + self.sha_chunksize=sha_chunksize + self.logger = logger + + self._cached_models = dict() + self._cache_stack = list() + + def get_key( + self, + model_path: str, + base_model: BaseModelType, + model_type: ModelType, + submodel_type: Optional[SubModelType] = None, + ): + + key = f"{model_path}:{base_model}:{model_type}" + if submodel_type: + key += f":{submodel_type}" + return key + + #def get_model( + # self, + # repo_id_or_path: Union[str, Path], + # model_type: ModelType = ModelType.Diffusers, + # subfolder: Path = None, + # submodel: ModelType = None, + # revision: str = None, + # attach_model_part: Tuple[ModelType, str] = (None, None), + # gpu_load: bool = True, + #) -> ModelLocker: # ?? what does it return + def _get_model_info( + self, + model_path: str, + model_class: Type[ModelBase], + base_model: BaseModelType, + model_type: ModelType, + ): + model_info_key = self.get_key( + model_path=model_path, + base_model=base_model, + model_type=model_type, + submodel_type=None, + ) + + if model_info_key not in self.model_infos: + self.model_infos[model_info_key] = model_class( + model_path, + base_model, + model_type, + ) + + return self.model_infos[model_info_key] + + # TODO: args + def get_model( + self, + model_path: Union[str, Path], + model_class: Type[ModelBase], + base_model: BaseModelType, + model_type: ModelType, + submodel: Optional[SubModelType] = None, + gpu_load: bool = True, + ) -> Any: + + if not isinstance(model_path, Path): + model_path = Path(model_path) + + if not os.path.exists(model_path): + raise Exception(f"Model not found: {model_path}") + + model_info = self._get_model_info( + model_path=model_path, + model_class=model_class, + base_model=base_model, + model_type=model_type, + ) + key = self.get_key( + model_path=model_path, + base_model=base_model, + model_type=model_type, + submodel_type=submodel, + ) + + # TODO: lock for no copies on simultaneous calls? + cache_entry = self._cached_models.get(key, None) + if cache_entry is None: + self.logger.info(f'Loading model {model_path}, type {base_model}:{model_type}:{submodel}') + + # this will remove older cached models until + # there is sufficient room to load the requested model + self._make_cache_room(model_info.get_size(submodel)) + + # clean memory to make MemoryUsage() more accurate + gc.collect() + model = model_info.get_model(child_type=submodel, torch_dtype=self.precision) + if mem_used := model_info.get_size(submodel): + self.logger.debug(f'CPU RAM used for load: {(mem_used/GIG):.2f} GB') + + cache_entry = _CacheRecord(self, model, mem_used) + self._cached_models[key] = cache_entry + + with suppress(Exception): + self._cache_stack.remove(key) + self._cache_stack.append(key) + + return self.ModelLocker(self, key, cache_entry.model, gpu_load) + + class ModelLocker(object): + def __init__(self, cache, key, model, gpu_load): + self.gpu_load = gpu_load + self.cache = cache + self.key = key + self.model = model + self.cache_entry = self.cache._cached_models[self.key] + + def __enter__(self) -> Any: + if not hasattr(self.model, 'to'): + return self.model + + # NOTE that the model has to have the to() method in order for this + # code to move it into GPU! + if self.gpu_load: + self.cache_entry.lock() + + try: + if self.cache.lazy_offloading: + self.cache._offload_unlocked_models() + + if self.model.device != self.cache.execution_device: + self.cache.logger.debug(f'Moving {self.key} into {self.cache.execution_device}') + with VRAMUsage() as mem: + self.model.to(self.cache.execution_device) # move into GPU + self.cache.logger.debug(f'GPU VRAM used for load: {(mem.vram_used/GIG):.2f} GB') + + self.cache.logger.debug(f'Locking {self.key} in {self.cache.execution_device}') + self.cache._print_cuda_stats() + + except: + self.cache_entry.unlock() + raise + + + # TODO: not fully understand + # in the event that the caller wants the model in RAM, we + # move it into CPU if it is in GPU and not locked + elif self.cache_entry.loaded and not self.cache_entry.locked: + self.model.to(self.cache.storage_device) + + return self.model + + def __exit__(self, type, value, traceback): + if not hasattr(self.model, 'to'): + return + + self.cache_entry.unlock() + if not self.cache.lazy_offloading: + self.cache._offload_unlocked_models() + self.cache._print_cuda_stats() + + # TODO: should it be called untrack_model? + def uncache_model(self, cache_id: str): + with suppress(ValueError): + self._cache_stack.remove(cache_id) + self._cached_models.pop(cache_id, None) + + def model_hash( + self, + model_path: Union[str, Path], + ) -> str: + ''' + Given the HF repo id or path to a model on disk, returns a unique + hash. Works for legacy checkpoint files, HF models on disk, and HF repo IDs + :param model_path: Path to model file/directory on disk. + ''' + return self._local_model_hash(model_path) + + def cache_size(self) -> float: + "Return the current size of the cache, in GB" + current_cache_size = sum([m.size for m in self._cached_models.values()]) + return current_cache_size / GIG + + def _has_cuda(self) -> bool: + return self.execution_device.type == 'cuda' + + def _print_cuda_stats(self): + vram = "%4.2fG" % (torch.cuda.memory_allocated() / GIG) + ram = "%4.2fG" % self.cache_size() + + cached_models = 0 + loaded_models = 0 + locked_models = 0 + for model_info in self._cached_models.values(): + cached_models += 1 + if model_info.loaded: + loaded_models += 1 + if model_info.locked: + locked_models += 1 + + self.logger.debug(f"Current VRAM/RAM usage: {vram}/{ram}; cached_models/loaded_models/locked_models/ = {cached_models}/{loaded_models}/{locked_models}") + + + def _make_cache_room(self, model_size): + # calculate how much memory this model will require + #multiplier = 2 if self.precision==torch.float32 else 1 + bytes_needed = model_size + maximum_size = self.max_cache_size * GIG # stored in GB, convert to bytes + current_size = sum([m.size for m in self._cached_models.values()]) + + if current_size + bytes_needed > maximum_size: + self.logger.debug(f'Max cache size exceeded: {(current_size/GIG):.2f}/{self.max_cache_size:.2f} GB, need an additional {(bytes_needed/GIG):.2f} GB') + + self.logger.debug(f"Before unloading: cached_models={len(self._cached_models)}") + + pos = 0 + while current_size + bytes_needed > maximum_size and pos < len(self._cache_stack): + model_key = self._cache_stack[pos] + cache_entry = self._cached_models[model_key] + + refs = sys.getrefcount(cache_entry.model) + + device = cache_entry.model.device if hasattr(cache_entry.model, "device") else None + self.logger.debug(f"Model: {model_key}, locks: {cache_entry._locks}, device: {device}, loaded: {cache_entry.loaded}, refs: {refs}") + + # 2 refs: + # 1 from cache_entry + # 1 from getrefcount function + if not cache_entry.locked and refs <= 2: + self.logger.debug(f'Unloading model {model_key} to free {(model_size/GIG):.2f} GB (-{(cache_entry.size/GIG):.2f} GB)') + current_size -= cache_entry.size + del self._cache_stack[pos] + del self._cached_models[model_key] + del cache_entry + + else: + pos += 1 + + gc.collect() + torch.cuda.empty_cache() + + self.logger.debug(f"After unloading: cached_models={len(self._cached_models)}") + + + def _offload_unlocked_models(self): + for model_key, cache_entry in self._cached_models.items(): + if not cache_entry.locked and cache_entry.loaded: + self.logger.debug(f'Offloading {model_key} from {self.execution_device} into {self.storage_device}') + cache_entry.model.to(self.storage_device) + + def _local_model_hash(self, model_path: Union[str, Path]) -> str: + sha = hashlib.sha256() + path = Path(model_path) + + hashpath = path / "checksum.sha256" + if hashpath.exists() and path.stat().st_mtime <= hashpath.stat().st_mtime: + with open(hashpath) as f: + hash = f.read() + return hash + + self.logger.debug(f'computing hash of model {path.name}') + for file in list(path.rglob("*.ckpt")) \ + + list(path.rglob("*.safetensors")) \ + + list(path.rglob("*.pth")): + with open(file, "rb") as f: + while chunk := f.read(self.sha_chunksize): + sha.update(chunk) + hash = sha.hexdigest() + with open(hashpath, "w") as f: + f.write(hash) + return hash + +class VRAMUsage(object): + def __init__(self): + self.vram = None + self.vram_used = 0 + + def __enter__(self): + self.vram = torch.cuda.memory_allocated() + return self + + def __exit__(self, *args): + self.vram_used = torch.cuda.memory_allocated() - self.vram diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 909f35a201..7dc174bbce 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -1,412 +1,575 @@ -"""enum -Manage a cache of Stable Diffusion model files for fast switching. -They are moved between GPU and CPU as necessary. If CPU memory falls -below a preset minimum, the least recently used model will be -cleared and loaded from disk when next needed. +"""This module manages the InvokeAI `models.yaml` file, mapping +symbolic diffusers model names to the paths and repo_ids used by the +underlying `from_pretrained()` call. + +SYNOPSIS: + + mgr = ModelManager('/home/phi/invokeai/configs/models.yaml') + sd1_5 = mgr.get_model('stable-diffusion-v1-5', + model_type=ModelType.Main, + base_model=BaseModelType.StableDiffusion1, + submodel_type=SubModelType.Unet) + with sd1_5 as unet: + run_some_inference(unet) + +FETCHING MODELS: + +Models are described using four attributes: + + 1) model_name -- the symbolic name for the model + + 2) ModelType -- an enum describing the type of the model. Currently + defined types are: + ModelType.Main -- a full model capable of generating images + ModelType.Vae -- a VAE model + ModelType.Lora -- a LoRA or LyCORIS fine-tune + ModelType.TextualInversion -- a textual inversion embedding + ModelType.ControlNet -- a ControlNet model + + 3) BaseModelType -- an enum indicating the stable diffusion base model, one of: + BaseModelType.StableDiffusion1 + BaseModelType.StableDiffusion2 + + 4) SubModelType (optional) -- an enum that refers to one of the submodels contained + within the main model. Values are: + + SubModelType.UNet + SubModelType.TextEncoder + SubModelType.Tokenizer + SubModelType.Scheduler + SubModelType.SafetyChecker + +To fetch a model, use `manager.get_model()`. This takes the symbolic +name of the model, the ModelType, the BaseModelType and the +SubModelType. The latter is required for ModelType.Main. + +get_model() will return a ModelInfo object that can then be used in +context to retrieve the model and move it into GPU VRAM (on GPU +systems). + +A typical example is: + + sd1_5 = mgr.get_model('stable-diffusion-v1-5', + model_type=ModelType.Main, + base_model=BaseModelType.StableDiffusion1, + submodel_type=SubModelType.Unet) + with sd1_5 as unet: + run_some_inference(unet) + +The ModelInfo object provides a number of useful fields describing the +model, including: + + name -- symbolic name of the model + base_model -- base model (BaseModelType) + type -- model type (ModelType) + location -- path to the model file + precision -- torch precision of the model + hash -- unique sha256 checksum for this model + +SUBMODELS: + +When fetching a main model, you must specify the submodel. Retrieval +of full pipelines is not supported. + + vae_info = mgr.get_model('stable-diffusion-1.5', + model_type = ModelType.Main, + base_model = BaseModelType.StableDiffusion1, + submodel_type = SubModelType.Vae + ) + with vae_info as vae: + do_something(vae) + +This rule does not apply to controlnets, embeddings, loras and standalone +VAEs, which do not have submodels. + +LISTING MODELS + +The model_names() method will return a list of Tuples describing each +model it knows about: + + >> mgr.model_names() + [ + ('stable-diffusion-1.5', , ), + ('stable-diffusion-2.1', , ), + ('inpaint', , ) + ('Ink scenery', , ) + ... + ] + +The tuple is in the correct order to pass to get_model(): + + for m in mgr.model_names(): + info = get_model(*m) + +In contrast, the list_models() method returns a list of dicts, each +providing information about a model defined in models.yaml. For example: + + >>> models = mgr.list_models() + >>> json.dumps(models[0]) + {"path": "/home/lstein/invokeai-main/models/sd-1/controlnet/canny", + "model_format": "diffusers", + "name": "canny", + "base_model": "sd-1", + "type": "controlnet" + } + +You can filter by model type and base model as shown here: + + + controlnets = mgr.list_models(model_type=ModelType.ControlNet, + base_model=BaseModelType.StableDiffusion1) + for c in controlnets: + name = c['name'] + format = c['model_format'] + path = c['path'] + type = c['type'] + # etc + +ADDING AND REMOVING MODELS + +At startup time, the `models` directory will be scanned for +checkpoints, diffusers pipelines, controlnets, LoRAs and TI +embeddings. New entries will be added to the model manager and defunct +ones removed. Anything that is a main model (ModelType.Main) will be +added to models.yaml. For scanning to succeed, files need to be in +their proper places. For example, a controlnet folder built on the +stable diffusion 2 base, will need to be placed in +`models/sd-2/controlnet`. + +Layout of the `models` directory: + + models + ├── sd-1 + │   ├── controlnet + │   ├── lora + │   ├── main + │   └── embedding + ├── sd-2 + │   ├── controlnet + │   ├── lora + │   ├── main + │ └── embedding + └── core + ├── face_reconstruction + │ ├── codeformer + │ └── gfpgan + ├── sd-conversion + │ ├── clip-vit-large-patch14 - tokenizer, text_encoder subdirs + │ ├── stable-diffusion-2 - tokenizer, text_encoder subdirs + │ └── stable-diffusion-safety-checker + └── upscaling + └─── esrgan + + + +class ConfigMeta(BaseModel):Loras, textual_inversion and controlnet models are not listed +explicitly in models.yaml, but are added to the in-memory data +structure at initialization time by scanning the models directory. The +in-memory data structure can be resynchronized by calling +`manager.scan_models_directory()`. + +Files and folders placed inside the `autoimport` paths (paths +defined in `invokeai.yaml`) will also be scanned for new models at +initialization time and added to `models.yaml`. Files will not be +moved from this location but preserved in-place. These directories +are: + + configuration default description + ------------- ------- ----------- + autoimport_dir autoimport/main main models + lora_dir autoimport/lora LoRA/LyCORIS models + embedding_dir autoimport/embedding TI embeddings + controlnet_dir autoimport/controlnet ControlNet models + +In actuality, models located in any of these directories are scanned +to determine their type, so it isn't strictly necessary to organize +the different types in this way. This entry in `invokeai.yaml` will +recursively scan all subdirectories within `autoimport`, scan models +files it finds, and import them if recognized. + + Paths: + autoimport_dir: autoimport + +A model can be manually added using `add_model()` using the model's +name, base model, type and a dict of model attributes. See +`invokeai/backend/model_management/models` for the attributes required +by each model type. + +A model can be deleted using `del_model()`, providing the same +identifying information as `get_model()` + +The `heuristic_import()` method will take a set of strings +corresponding to local paths, remote URLs, and repo_ids, probe the +object to determine what type of model it is (if any), and import new +models into the manager. If passed a directory, it will recursively +scan it for models to import. The return value is a set of the models +successfully added. + +MODELS.YAML + +The general format of a models.yaml section is: + + type-of-model/name-of-model: + path: /path/to/local/file/or/directory + description: a description + format: diffusers|checkpoint + variant: normal|inpaint|depth + +The type of model is given in the stanza key, and is one of +{main, vae, lora, controlnet, textual} + +The format indicates whether the model is organized as a diffusers +folder with model subdirectories, or is contained in a single +checkpoint or safetensors file. + +The path points to a file or directory on disk. If a relative path, +the root is the InvokeAI ROOTDIR. + """ from __future__ import annotations -import contextlib -import gc -import hashlib import os -import re -import sys +import hashlib import textwrap -import time -import warnings -from enum import Enum, auto +from dataclasses import dataclass from pathlib import Path -from shutil import move, rmtree -from typing import Any, Optional, Union, Callable, types +from typing import Optional, List, Tuple, Union, Set, Callable, types +from shutil import rmtree -import safetensors -import safetensors.torch import torch -import transformers -import invokeai.backend.util.logging as logger -from diffusers import ( - AutoencoderKL, - UNet2DConditionModel, - SchedulerMixin, - logging as dlogging, -) -from huggingface_hub import scan_cache_dir from omegaconf import OmegaConf from omegaconf.dictconfig import DictConfig -from picklescan.scanner import scan_file_path -from transformers import ( - CLIPTextModel, - CLIPTokenizer, - CLIPFeatureExtractor, -) -from diffusers.pipelines.stable_diffusion.safety_checker import ( - StableDiffusionSafetyChecker, +from pydantic import BaseModel + +import invokeai.backend.util.logging as logger +from invokeai.app.services.config import InvokeAIAppConfig +from invokeai.backend.util import CUDA_DEVICE, Chdir +from .model_cache import ModelCache, ModelLocker +from .models import ( + BaseModelType, ModelType, SubModelType, + ModelError, SchedulerPredictionType, MODEL_CLASSES, + ModelConfigBase, ) -from ..stable_diffusion import ( - StableDiffusionGeneratorPipeline, -) -from invokeai.app.services.config import get_invokeai_config -from ..util import CUDA_DEVICE, ask_user, download_with_resume -class SDLegacyType(Enum): - V1 = auto() - V1_INPAINT = auto() - V2 = auto() - V2_e = auto() - V2_v = auto() - UNKNOWN = auto() +# We are only starting to number the config file with release 3. +# The config file version doesn't have to start at release version, but it will help +# reduce confusion. +CONFIG_FILE_VERSION='3.0.0' -class SDModelComponent(Enum): - vae="vae" - text_encoder="text_encoder" - tokenizer="tokenizer" - unet="unet" - scheduler="scheduler" - safety_checker="safety_checker" - feature_extractor="feature_extractor" +@dataclass +class ModelInfo(): + context: ModelLocker + name: str + base_model: BaseModelType + type: ModelType + hash: str + location: Union[Path, str] + precision: torch.dtype + _cache: ModelCache = None -DEFAULT_MAX_MODELS = 2 + def __enter__(self): + return self.context.__enter__() + + def __exit__(self,*args, **kwargs): + self.context.__exit__(*args, **kwargs) + +class InvalidModelError(Exception): + "Raised when an invalid model is requested" + pass + +MAX_CACHE_SIZE = 6.0 # GB + + +class ConfigMeta(BaseModel): + version: str class ModelManager(object): """ - Model manager handles loading, caching, importing, deleting, converting, and editing models. + High-level interface to model management. """ logger: types.ModuleType = logger def __init__( self, - config: OmegaConf | Path, + config: Union[Path, DictConfig, str], device_type: torch.device = CUDA_DEVICE, - precision: str = "float16", - max_loaded_models=DEFAULT_MAX_MODELS, + precision: torch.dtype = torch.float16, + max_cache_size=MAX_CACHE_SIZE, sequential_offload=False, - embedding_path: Path = None, logger: types.ModuleType = logger, ): """ - Initialize with the path to the models.yaml config file or - an initialized OmegaConf dictionary. Optional parameters - are the torch device type, precision, max_loaded_models, + Initialize with the path to the models.yaml config file. + Optional parameters are the torch device type, precision, max_models, and sequential_offload boolean. Note that the default device type and precision are set up for a CUDA system running at half precision. """ - # prevent nasty-looking CLIP log message - transformers.logging.set_verbosity_error() - if not isinstance(config, DictConfig): - config = OmegaConf.load(config) - self.config = config - self.globals = get_invokeai_config() - self.precision = precision - self.device = torch.device(device_type) - self.max_loaded_models = max_loaded_models - self.models = {} - self.stack = [] # this is an LRU FIFO - self.current_model = None - self.sequential_offload = sequential_offload - self.embedding_path = embedding_path - self.logger = logger - def valid_model(self, model_name: str) -> bool: + self.config_path = None + if isinstance(config, (str, Path)): + self.config_path = Path(config) + config = OmegaConf.load(self.config_path) + + elif not isinstance(config, DictConfig): + raise ValueError('config argument must be an OmegaConf object, a Path or a string') + + self.config_meta = ConfigMeta(**config.pop("__metadata__")) + # TODO: metadata not found + # TODO: version check + + self.models = dict() + for model_key, model_config in config.items(): + model_name, base_model, model_type = self.parse_key(model_key) + model_class = MODEL_CLASSES[base_model][model_type] + # alias for config file + model_config["model_format"] = model_config.pop("format") + self.models[model_key] = model_class.create_config(**model_config) + + # check config version number and update on disk/RAM if necessary + self.app_config = InvokeAIAppConfig.get_config() + self.logger = logger + self.cache = ModelCache( + max_cache_size=max_cache_size, + execution_device = device_type, + precision = precision, + sequential_offload = sequential_offload, + logger = logger, + ) + self.cache_keys = dict() + + # add controlnet, lora and textual_inversion models from disk + self.scan_models_directory() + + def model_exists( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ) -> bool: """ Given a model name, returns True if it is a valid identifier. """ - return model_name in self.config + model_key = self.create_key(model_name, base_model, model_type) + return model_key in self.models - def get_model(self, model_name: str = None) -> dict: - """Given a model named identified in models.yaml, return a dict - containing the model object and some of its key features. If - in RAM will load into GPU VRAM. If on disk, will load from - there. - The dict has the following keys: - 'model': The StableDiffusionGeneratorPipeline object - 'model_name': The name of the model in models.yaml - 'width': The width of images trained by this model - 'height': The height of images trained by this model - 'hash': A unique hash of this model's files on disk. + @classmethod + def create_key( + cls, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ) -> str: + return f"{base_model}/{model_type}/{model_name}" + + @classmethod + def parse_key(cls, model_key: str) -> Tuple[str, BaseModelType, ModelType]: + base_model_str, model_type_str, model_name = model_key.split('/', 2) + try: + model_type = ModelType(model_type_str) + except: + raise Exception(f"Unknown model type: {model_type_str}") + + try: + base_model = BaseModelType(base_model_str) + except: + raise Exception(f"Unknown base model: {base_model_str}") + + return (model_name, base_model, model_type) + + def _get_model_cache_path(self, model_path): + return self.app_config.models_path / ".cache" / hashlib.md5(str(model_path).encode()).hexdigest() + + def get_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + submodel_type: Optional[SubModelType] = None + )->ModelInfo: + """Given a model named identified in models.yaml, return + an ModelInfo object describing it. + :param model_name: symbolic name of the model in models.yaml + :param model_type: ModelType enum indicating the type of model to return + :param base_model: BaseModelType enum indicating the base model used by this model + :param submode_typel: an ModelType enum indicating the portion of + the model to retrieve (e.g. ModelType.Vae) """ - if not model_name: - return ( - self.get_model(self.current_model) - if self.current_model - else self.get_model(self.default_model()) - ) + model_class = MODEL_CLASSES[base_model][model_type] + model_key = self.create_key(model_name, base_model, model_type) - if not self.valid_model(model_name): - self.logger.error( - f'"{model_name}" is not a known model name. Please check your models.yaml file' - ) - return self.current_model + # if model not found try to find it (maybe file just pasted) + if model_key not in self.models: + self.scan_models_directory(base_model=base_model, model_type=model_type) + if model_key not in self.models: + raise Exception(f"Model not found - {model_key}") - if self.current_model != model_name: - if model_name not in self.models: # make room for a new one - self._make_cache_room() - self.offload_model(self.current_model) + model_config = self.models[model_key] + model_path = self.app_config.root_path / model_config.path - if model_name in self.models: - requested_model = self.models[model_name]["model"] - self.logger.info(f"Retrieving model {model_name} from system RAM cache") - requested_model.ready() - width = self.models[model_name]["width"] - height = self.models[model_name]["height"] - hash = self.models[model_name]["hash"] + if not model_path.exists(): + if model_class.save_to_config: + self.models[model_key].error = ModelError.NotFound + raise Exception(f"Files for model \"{model_key}\" not found") - else: # we're about to load a new model, so potentially offload the least recently used one - requested_model, width, height, hash = self._load_model(model_name) - self.models[model_name] = { - "model_name": model_name, - "model": requested_model, - "width": width, - "height": height, - "hash": hash, - } + else: + self.models.pop(model_key, None) + raise Exception(f"Model not found - {model_key}") - self.current_model = model_name - self._push_newest_model(model_name) - return { - "model_name": model_name, - "model": requested_model, - "width": width, - "height": height, - "hash": hash, - } + # vae/movq override + # TODO: + if submodel_type is not None and hasattr(model_config, submodel_type): + override_path = getattr(model_config, submodel_type) + if override_path: + model_path = override_path + model_type = submodel_type + submodel_type = None + model_class = MODEL_CLASSES[base_model][model_type] - def get_model_vae(self, model_name: str=None)->AutoencoderKL: - """Given a model name identified in models.yaml, load the model into - GPU if necessary and return its assigned VAE as an - AutoencoderKL object. If no model name is provided, return the - vae from the model currently in the GPU. - """ - return self._get_sub_model(model_name, SDModelComponent.vae) + # TODO: path + # TODO: is it accurate to use path as id + dst_convert_path = self._get_model_cache_path(model_path) + model_path = model_class.convert_if_required( + base_model=base_model, + model_path=str(model_path), # TODO: refactor str/Path types logic + output_path=dst_convert_path, + config=model_config, + ) - def get_model_tokenizer(self, model_name: str=None)->CLIPTokenizer: - """Given a model name identified in models.yaml, load the model into - GPU if necessary and return its assigned CLIPTokenizer. If no - model name is provided, return the tokenizer from the model - currently in the GPU. - """ - return self._get_sub_model(model_name, SDModelComponent.tokenizer) + model_context = self.cache.get_model( + model_path=model_path, + model_class=model_class, + base_model=base_model, + model_type=model_type, + submodel=submodel_type, + ) - def get_model_unet(self, model_name: str=None)->UNet2DConditionModel: - """Given a model name identified in models.yaml, load the model into - GPU if necessary and return its assigned UNet2DConditionModel. If no model - name is provided, return the UNet from the model - currently in the GPU. - """ - return self._get_sub_model(model_name, SDModelComponent.unet) + if model_key not in self.cache_keys: + self.cache_keys[model_key] = set() + self.cache_keys[model_key].add(model_context.key) - def get_model_text_encoder(self, model_name: str=None)->CLIPTextModel: - """Given a model name identified in models.yaml, load the model into - GPU if necessary and return its assigned CLIPTextModel. If no - model name is provided, return the text encoder from the model - currently in the GPU. - """ - return self._get_sub_model(model_name, SDModelComponent.text_encoder) + model_hash = "" # TODO: + + return ModelInfo( + context = model_context, + name = model_name, + base_model = base_model, + type = submodel_type or model_type, + hash = model_hash, + location = model_path, # TODO: + precision = self.cache.precision, + _cache = self.cache, + ) - def get_model_feature_extractor(self, model_name: str=None)->CLIPFeatureExtractor: - """Given a model name identified in models.yaml, load the model into - GPU if necessary and return its assigned CLIPFeatureExtractor. If no - model name is provided, return the text encoder from the model - currently in the GPU. - """ - return self._get_sub_model(model_name, SDModelComponent.feature_extractor) - - def get_model_scheduler(self, model_name: str=None)->SchedulerMixin: - """Given a model name identified in models.yaml, load the model into - GPU if necessary and return its assigned scheduler. If no - model name is provided, return the text encoder from the model - currently in the GPU. - """ - return self._get_sub_model(model_name, SDModelComponent.scheduler) - - def _get_sub_model( - self, - model_name: str=None, - model_part: SDModelComponent=SDModelComponent.vae, - ) -> Union[ - AutoencoderKL, - CLIPTokenizer, - CLIPFeatureExtractor, - UNet2DConditionModel, - CLIPTextModel, - StableDiffusionSafetyChecker, - ]: - """Given a model name identified in models.yaml, and the part of the - model you wish to retrieve, return that part. Parts are in an Enum - class named SDModelComponent, and consist of: - SDModelComponent.vae - SDModelComponent.text_encoder - SDModelComponent.tokenizer - SDModelComponent.unet - SDModelComponent.scheduler - SDModelComponent.safety_checker - SDModelComponent.feature_extractor - """ - model_dict = self.get_model(model_name) - model = model_dict["model"] - return getattr(model, model_part.value) - - def default_model(self) -> str | None: - """ - Returns the name of the default model, or None - if none is defined. - """ - for model_name in self.config: - if self.config[model_name].get("default"): - return model_name - return list(self.config.keys())[0] # first one - - def set_default_model(self, model_name: str) -> None: - """ - Set the default model. The change will not take - effect until you call model_manager.commit() - """ - assert model_name in self.model_names(), f"unknown model '{model_name}'" - - config = self.config - for model in config: - config[model].pop("default", None) - config[model_name]["default"] = True - - def model_info(self, model_name: str) -> dict: + def model_info( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ) -> dict: """ Given a model name returns the OmegaConf (dict-like) object describing it. """ - if model_name not in self.config: - return None - return self.config[model_name] + model_key = self.create_key(model_name, base_model, model_type) + if model_key in self.models: + return self.models[model_key].dict(exclude_defaults=True) + else: + return None # TODO: None or empty dict on not found - def model_names(self) -> list[str]: + def model_names(self) -> List[Tuple[str, BaseModelType, ModelType]]: """ - Return a list consisting of all the names of models defined in models.yaml + Return a list of (str, BaseModelType, ModelType) corresponding to all models + known to the configuration. """ - return list(self.config.keys()) + return [(self.parse_key(x)) for x in self.models.keys()] - def is_legacy(self, model_name: str) -> bool: + def list_models( + self, + base_model: Optional[BaseModelType] = None, + model_type: Optional[ModelType] = None, + ) -> list[dict]: """ - Return true if this is a legacy (.ckpt) model + Return a list of models. """ - # if we are converting legacy files automatically, then - # there are no legacy ckpts! - if self.globals.ckpt_convert: - return False - info = self.model_info(model_name) - if "weights" in info and info["weights"].endswith((".ckpt", ".safetensors")): - return True - return False - def list_models(self) -> dict: - """ - Return a dict of models in the format: - { model_name1: {'status': ('active'|'cached'|'not loaded'), - 'description': description, - 'format': ('ckpt'|'diffusers'|'vae'), - }, - model_name2: { etc } - Please use model_manager.models() to get all the model names, - model_manager.model_info('model-name') to get the stanza for the model - named 'model-name', and model_manager.config to get the full OmegaConf - object derived from models.yaml - """ - models = {} - for name in sorted(self.config, key=str.casefold): - stanza = self.config[name] + models = [] + for model_key in sorted(self.models, key=str.casefold): + model_config = self.models[model_key] - # don't include VAEs in listing (legacy style) - if "config" in stanza and "/VAE/" in stanza["config"]: + cur_model_name, cur_base_model, cur_model_type = self.parse_key(model_key) + if base_model is not None and cur_base_model != base_model: + continue + if model_type is not None and cur_model_type != model_type: continue - models[name] = dict() - format = stanza.get("format", "ckpt") # Determine Format - - # Common Attribs - description = stanza.get("description", None) - if self.current_model == name: - status = "active" - elif name in self.models: - status = "cached" - else: - status = "not loaded" - models[name].update( - description=description, - format=format, - status=status, + model_dict = dict( + **model_config.dict(exclude_defaults=True), + # OpenAPIModelInfoBase + name=cur_model_name, + base_model=cur_base_model, + type=cur_model_type, ) - # Checkpoint Config Parse - if format == "ckpt": - models[name].update( - config=str(stanza.get("config", None)), - weights=str(stanza.get("weights", None)), - vae=str(stanza.get("vae", None)), - width=str(stanza.get("width", 512)), - height=str(stanza.get("height", 512)), - ) - - # Diffusers Config Parse - if vae := stanza.get("vae", None): - if isinstance(vae, DictConfig): - vae = dict( - repo_id=str(vae.get("repo_id", None)), - path=str(vae.get("path", None)), - subfolder=str(vae.get("subfolder", None)), - ) - - if format == "diffusers": - models[name].update( - vae=vae, - repo_id=str(stanza.get("repo_id", None)), - path=str(stanza.get("path", None)), - ) + models.append(model_dict) return models def print_models(self) -> None: """ - Print a table of models, their descriptions, and load status + Print a table of models and their descriptions. This needs to be redone """ - models = self.list_models() - for name in models: - if models[name]["format"] == "vae": - continue - line = f'{name:25s} {models[name]["status"]:>10s} {models[name]["format"]:10s} {models[name]["description"]}' - if models[name]["status"] == "active": - line = f"\033[1m{line}\033[0m" - print(line) + # TODO: redo + for model_type, model_dict in self.list_models().items(): + for model_name, model_info in model_dict.items(): + line = f'{model_info["name"]:25s} {model_info["type"]:10s} {model_info["description"]}' + print(line) - def del_model(self, model_name: str, delete_files: bool = False) -> None: + # Tested - LS + def del_model( + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + ): """ Delete the named model. """ - omega = self.config - if model_name not in omega: - self.logger.error(f"Unknown model {model_name}") + model_key = self.create_key(model_name, base_model, model_type) + model_cfg = self.models.pop(model_key, None) + + if model_cfg is None: + self.logger.error( + f"Unknown model {model_key}" + ) return - # save these for use in deletion later - conf = omega[model_name] - repo_id = conf.get("repo_id", None) - path = self._abs_path(conf.get("path", None)) - weights = self._abs_path(conf.get("weights", None)) - del omega[model_name] - if model_name in self.stack: - self.stack.remove(model_name) - if delete_files: - if weights: - self.logger.info(f"Deleting file {weights}") - Path(weights).unlink(missing_ok=True) - elif path: - self.logger.info(f"Deleting directory {path}") - rmtree(path, ignore_errors=True) - elif repo_id: - self.logger.info(f"Deleting the cached model directory for {repo_id}") - self._delete_model_from_cache(repo_id) + # note: it not garantie to release memory(model can has other references) + cache_ids = self.cache_keys.pop(model_key, []) + for cache_id in cache_ids: + self.cache.uncache_model(cache_id) + # if model inside invoke models folder - delete files + model_path = self.app_config.root_path / model_cfg.path + cache_path = self._get_model_cache_path(model_path) + if cache_path.exists(): + rmtree(str(cache_path)) + + if model_path.is_relative_to(self.app_config.models_path): + if model_path.is_dir(): + rmtree(str(model_path)) + else: + model_path.unlink() + + # LS: tested def add_model( - self, model_name: str, model_attributes: dict, clobber: bool = False + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + model_attributes: dict, + clobber: bool = False, ) -> None: """ Update the named model with a dictionary of attributes. Will fail with an @@ -415,570 +578,34 @@ class ModelManager(object): method will return True. Will fail with an assertion error if provided attributes are incorrect or the model name is missing. """ - omega = self.config - assert "format" in model_attributes, 'missing required field "format"' - if model_attributes["format"] == "diffusers": - assert ( - "description" in model_attributes - ), 'required field "description" is missing' - assert ( - "path" in model_attributes or "repo_id" in model_attributes - ), 'model must have either the "path" or "repo_id" fields defined' - else: - for field in ("description", "weights", "height", "width", "config"): - assert field in model_attributes, f"required field {field} is missing" - assert ( - clobber or model_name not in omega - ), f'attempt to overwrite existing model definition "{model_name}"' + model_class = MODEL_CLASSES[base_model][model_type] + model_config = model_class.create_config(**model_attributes) + model_key = self.create_key(model_name, base_model, model_type) - omega[model_name] = model_attributes + if model_key in self.models and not clobber: + raise Exception(f'Attempt to overwrite existing model definition "{model_key}"') - if "weights" in omega[model_name]: - omega[model_name]["weights"].replace("\\", "/") + old_model = self.models.pop(model_key, None) + if old_model is not None: + # TODO: if path changed and old_model.path inside models folder should we delete this too? - if clobber: - self._invalidate_cached_model(model_name) - - def _load_model(self, model_name: str): - """Load and initialize the model from configuration variables passed at object creation time""" - if model_name not in self.config: - self.logger.error( - f'"{model_name}" is not a known model name. Please check your models.yaml file' - ) - return - - mconfig = self.config[model_name] - - # for usage statistics - if self._has_cuda(): - torch.cuda.reset_peak_memory_stats() - torch.cuda.empty_cache() - - tic = time.time() - - # this does the work - model_format = mconfig.get("format", "ckpt") - if model_format == "ckpt": - weights = mconfig.weights - self.logger.info(f"Loading {model_name} from {weights}") - model, width, height, model_hash = self._load_ckpt_model( - model_name, mconfig - ) - elif model_format == "diffusers": - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - model, width, height, model_hash = self._load_diffusers_model(mconfig) - else: - raise NotImplementedError( - f"Unknown model format {model_name}: {model_format}" - ) - self._add_embeddings_to_model(model) - - # usage statistics - toc = time.time() - self.logger.info("Model loaded in " + "%4.2fs" % (toc - tic)) - if self._has_cuda(): - self.logger.info( - "Max VRAM used to load the model: "+ - "%4.2fG" % (torch.cuda.max_memory_allocated() / 1e9) - ) - self.logger.info( - "Current VRAM usage: "+ - "%4.2fG" % (torch.cuda.memory_allocated() / 1e9) - ) - return model, width, height, model_hash - - def _load_diffusers_model(self, mconfig): - name_or_path = self.model_name_or_path(mconfig) - using_fp16 = self.precision == "float16" - - self.logger.info(f"Loading diffusers model from {name_or_path}") - if using_fp16: - self.logger.debug("Using faster float16 precision") - else: - self.logger.debug("Using more accurate float32 precision") - - # TODO: scan weights maybe? - pipeline_args: dict[str, Any] = dict( - safety_checker=None, local_files_only=not self.globals.internet_available - ) - if "vae" in mconfig and mconfig["vae"] is not None: - if vae := self._load_vae(mconfig["vae"]): - pipeline_args.update(vae=vae) - if not isinstance(name_or_path, Path): - pipeline_args.update(cache_dir=self.globals.cache_dir) - if using_fp16: - pipeline_args.update(torch_dtype=torch.float16) - fp_args_list = [{"revision": "fp16"}, {}] - else: - fp_args_list = [{}] - - verbosity = dlogging.get_verbosity() - dlogging.set_verbosity_error() - - pipeline = None - for fp_args in fp_args_list: - try: - pipeline = StableDiffusionGeneratorPipeline.from_pretrained( - name_or_path, - **pipeline_args, - **fp_args, - ) - except OSError as e: - if str(e).startswith("fp16 is not a valid"): - pass + # remove conversion cache as config changed + old_model_path = self.app_config.root_path / old_model.path + old_model_cache = self._get_model_cache_path(old_model_path) + if old_model_cache.exists(): + if old_model_cache.is_dir(): + rmtree(str(old_model_cache)) else: - self.logger.error( - f"An unexpected error occurred while downloading the model: {e})" - ) - if pipeline: - break + old_model_cache.unlink() - dlogging.set_verbosity(verbosity) - assert pipeline is not None, OSError(f'"{name_or_path}" could not be loaded') + # remove in-memory cache + # note: it not garantie to release memory(model can has other references) + cache_ids = self.cache_keys.pop(model_key, []) + for cache_id in cache_ids: + self.cache.uncache_model(cache_id) - if self.sequential_offload: - pipeline.enable_offload_submodels(self.device) - else: - pipeline.to(self.device) - - model_hash = self._diffuser_sha256(name_or_path) - - # square images??? - width = pipeline.unet.config.sample_size * pipeline.vae_scale_factor - height = width - self.logger.debug(f"Default image dimensions = {width} x {height}") - - return pipeline, width, height, model_hash - - def _load_ckpt_model(self, model_name, mconfig): - config = mconfig.config - weights = mconfig.weights - vae = mconfig.get("vae") - width = mconfig.width - height = mconfig.height - - root_dir = self.globals.root_dir - config = str(root_dir / config) - weights = str(root_dir / weights) - - # Convert to diffusers and return a diffusers pipeline - self.logger.info(f"Converting legacy checkpoint {model_name} into a diffusers model...") - - from . import load_pipeline_from_original_stable_diffusion_ckpt - - try: - if self.list_models()[self.current_model]["status"] == "active": - self.offload_model(self.current_model) - except Exception: - pass - - vae_path = None - if vae: - vae_path = str(root_dir / vae) - if self._has_cuda(): - torch.cuda.empty_cache() - pipeline = load_pipeline_from_original_stable_diffusion_ckpt( - checkpoint_path=weights, - original_config_file=config, - vae_path=vae_path, - return_generator_pipeline=True, - precision=torch.float16 if self.precision == "float16" else torch.float32, - ) - if self.sequential_offload: - pipeline.enable_offload_submodels(self.device) - else: - pipeline.to(self.device) - return ( - pipeline, - width, - height, - "NOHASH", - ) - - def model_name_or_path(self, model_name: Union[str, DictConfig]) -> str | Path: - if isinstance(model_name, DictConfig) or isinstance(model_name, dict): - mconfig = model_name - elif model_name in self.config: - mconfig = self.config[model_name] - else: - raise ValueError( - f'"{model_name}" is not a known model name. Please check your models.yaml file' - ) - - if "path" in mconfig and mconfig["path"] is not None: - path = self.globals.root_dir / Path(mconfig["path"]) - return path - elif "repo_id" in mconfig: - return mconfig["repo_id"] - else: - raise ValueError("Model config must specify either repo_id or path.") - - def offload_model(self, model_name: str) -> None: - """ - Offload the indicated model to CPU. Will call - _make_cache_room() to free space if needed. - """ - if model_name not in self.models: - return - - self.logger.info(f"Offloading {model_name} to CPU") - model = self.models[model_name]["model"] - model.offload_all() - self.current_model = None - - gc.collect() - if self._has_cuda(): - torch.cuda.empty_cache() - - @classmethod - def scan_model(self, model_name, checkpoint): - """ - Apply picklescanner to the indicated checkpoint and issue a warning - and option to exit if an infected file is identified. - """ - # scan model - self.logger.debug(f"Scanning Model: {model_name}") - scan_result = scan_file_path(checkpoint) - if scan_result.infected_files != 0: - if scan_result.infected_files == 1: - self.logger.critical(f"Issues Found In Model: {scan_result.issues_count}") - self.logger.critical("The model you are trying to load seems to be infected.") - self.logger.critical("For your safety, InvokeAI will not load this model.") - self.logger.critical("Please use checkpoints from trusted sources.") - self.logger.critical("Exiting InvokeAI") - sys.exit() - else: - self.logger.warning("InvokeAI was unable to scan the model you are using.") - model_safe_check_fail = ask_user( - "Do you want to to continue loading the model?", ["y", "n"] - ) - if model_safe_check_fail.lower() != "y": - self.logger.critical("Exiting InvokeAI") - sys.exit() - else: - self.logger.debug("Model scanned ok") - - def import_diffuser_model( - self, - repo_or_path: Union[str, Path], - model_name: str = None, - description: str = None, - vae: dict = None, - commit_to_conf: Path = None, - ) -> bool: - """ - Attempts to install the indicated diffuser model and returns True if successful. - - "repo_or_path" can be either a repo-id or a path-like object corresponding to the - top of a downloaded diffusers directory. - - You can optionally provide a model name and/or description. If not provided, - then these will be derived from the repo name. If you provide a commit_to_conf - path to the configuration file, then the new entry will be committed to the - models.yaml file. - """ - model_name = model_name or Path(repo_or_path).stem - model_description = description or f"Imported diffusers model {model_name}" - new_config = dict( - description=model_description, - vae=vae, - format="diffusers", - ) - if isinstance(repo_or_path, Path) and repo_or_path.exists(): - new_config.update(path=str(repo_or_path)) - else: - new_config.update(repo_id=repo_or_path) - - self.add_model(model_name, new_config, True) - if commit_to_conf: - self.commit(commit_to_conf) - return model_name - - @classmethod - def probe_model_type(self, checkpoint: dict) -> SDLegacyType: - """ - Given a pickle or safetensors model object, probes contents - of the object and returns an SDLegacyType indicating its - format. Valid return values include: - SDLegacyType.V1 - SDLegacyType.V1_INPAINT - SDLegacyType.V2 (V2 prediction type unknown) - SDLegacyType.V2_e (V2 using 'epsilon' prediction type) - SDLegacyType.V2_v (V2 using 'v_prediction' prediction type) - SDLegacyType.UNKNOWN - """ - global_step = checkpoint.get("global_step") - state_dict = checkpoint.get("state_dict") or checkpoint - - try: - key_name = "model.diffusion_model.input_blocks.2.1.transformer_blocks.0.attn2.to_k.weight" - if key_name in state_dict and state_dict[key_name].shape[-1] == 1024: - if global_step == 220000: - return SDLegacyType.V2_e - elif global_step == 110000: - return SDLegacyType.V2_v - else: - return SDLegacyType.V2 - # otherwise we assume a V1 file - in_channels = state_dict[ - "model.diffusion_model.input_blocks.0.0.weight" - ].shape[1] - if in_channels == 9: - return SDLegacyType.V1_INPAINT - elif in_channels == 4: - return SDLegacyType.V1 - else: - return SDLegacyType.UNKNOWN - except KeyError: - return SDLegacyType.UNKNOWN - - def heuristic_import( - self, - path_url_or_repo: str, - model_name: str = None, - description: str = None, - model_config_file: Path = None, - commit_to_conf: Path = None, - config_file_callback: Callable[[Path], Path] = None, - ) -> str: - """Accept a string which could be: - - a HF diffusers repo_id - - a URL pointing to a legacy .ckpt or .safetensors file - - a local path pointing to a legacy .ckpt or .safetensors file - - a local directory containing .ckpt and .safetensors files - - a local directory containing a diffusers model - - After determining the nature of the model and downloading it - (if necessary), the file is probed to determine the correct - configuration file (if needed) and it is imported. - - The model_name and/or description can be provided. If not, they will - be generated automatically. - - If commit_to_conf is provided, the newly loaded model will be written - to the `models.yaml` file at the indicated path. Otherwise, the changes - will only remain in memory. - - The routine will do its best to figure out the config file - needed to convert legacy checkpoint file, but if it can't it - will call the config_file_callback routine, if provided. The - callback accepts a single argument, the Path to the checkpoint - file, and returns a Path to the config file to use. - - The (potentially derived) name of the model is returned on - success, or None on failure. When multiple models are added - from a directory, only the last imported one is returned. - - """ - model_path: Path = None - thing = str(path_url_or_repo) # to save typing - - self.logger.info(f"Probing {thing} for import") - - if thing.startswith(("http:", "https:", "ftp:")): - self.logger.info(f"{thing} appears to be a URL") - model_path = self._resolve_path( - thing, "models/ldm/stable-diffusion-v1" - ) # _resolve_path does a download if needed - - elif Path(thing).is_file() and thing.endswith((".ckpt", ".safetensors")): - if Path(thing).stem in ["model", "diffusion_pytorch_model"]: - self.logger.debug(f"{Path(thing).name} appears to be part of a diffusers model. Skipping import") - return - else: - self.logger.debug(f"{thing} appears to be a checkpoint file on disk") - model_path = self._resolve_path(thing, "models/ldm/stable-diffusion-v1") - - elif Path(thing).is_dir() and Path(thing, "model_index.json").exists(): - self.logger.debug(f"{thing} appears to be a diffusers file on disk") - model_name = self.import_diffuser_model( - thing, - vae=dict(repo_id="stabilityai/sd-vae-ft-mse"), - model_name=model_name, - description=description, - commit_to_conf=commit_to_conf, - ) - - elif Path(thing).is_dir(): - if (Path(thing) / "model_index.json").exists(): - self.logger.debug(f"{thing} appears to be a diffusers model.") - model_name = self.import_diffuser_model( - thing, commit_to_conf=commit_to_conf - ) - else: - self.logger.debug(f"{thing} appears to be a directory. Will scan for models to import") - for m in list(Path(thing).rglob("*.ckpt")) + list( - Path(thing).rglob("*.safetensors") - ): - if model_name := self.heuristic_import( - str(m), commit_to_conf=commit_to_conf - ): - self.logger.info(f"{model_name} successfully imported") - return model_name - - elif re.match(r"^[\w.+-]+/[\w.+-]+$", thing): - self.logger.debug(f"{thing} appears to be a HuggingFace diffusers repo_id") - model_name = self.import_diffuser_model( - thing, commit_to_conf=commit_to_conf - ) - pipeline, _, _, _ = self._load_diffusers_model(self.config[model_name]) - return model_name - else: - self.logger.warning(f"{thing}: Unknown thing. Please provide a URL, file path, directory or HuggingFace repo_id") - - # Model_path is set in the event of a legacy checkpoint file. - # If not set, we're all done - if not model_path: - return - - if model_path.stem in self.config: # already imported - self.logger.debug("Already imported. Skipping") - return model_path.stem - - # another round of heuristics to guess the correct config file. - checkpoint = None - if model_path.suffix in [".ckpt", ".pt"]: - self.scan_model(model_path, model_path) - checkpoint = torch.load(model_path) - else: - checkpoint = safetensors.torch.load_file(model_path) - - # additional probing needed if no config file provided - if model_config_file is None: - # look for a like-named .yaml file in same directory - if model_path.with_suffix(".yaml").exists(): - model_config_file = model_path.with_suffix(".yaml") - self.logger.debug(f"Using config file {model_config_file.name}") - - else: - model_type = self.probe_model_type(checkpoint) - if model_type == SDLegacyType.V1: - self.logger.debug("SD-v1 model detected") - model_config_file = self.globals.legacy_conf_path / "v1-inference.yaml" - elif model_type == SDLegacyType.V1_INPAINT: - self.logger.debug("SD-v1 inpainting model detected") - model_config_file = self.globals.legacy_conf_path / "v1-inpainting-inference.yaml", - elif model_type == SDLegacyType.V2_v: - self.logger.debug("SD-v2-v model detected") - model_config_file = self.globals.legacy_conf_path / "v2-inference-v.yaml" - elif model_type == SDLegacyType.V2_e: - self.logger.debug("SD-v2-e model detected") - model_config_file = self.globals.legacy_conf_path / "v2-inference.yaml" - elif model_type == SDLegacyType.V2: - self.logger.warning( - f"{thing} is a V2 checkpoint file, but its parameterization cannot be determined. Please provide configuration file path." - ) - return - else: - self.logger.warning( - f"{thing} is a legacy checkpoint file but not a known Stable Diffusion model. Please provide configuration file path." - ) - return - - if not model_config_file and config_file_callback: - model_config_file = config_file_callback(model_path) - - # despite our best efforts, we could not find a model config file, so give up - if not model_config_file: - return - - # look for a custom vae, a like-named file ending with .vae in the same directory - vae_path = None - for suffix in ["pt", "ckpt", "safetensors"]: - if (model_path.with_suffix(f".vae.{suffix}")).exists(): - vae_path = model_path.with_suffix(f".vae.{suffix}") - self.logger.debug(f"Using VAE file {vae_path.name}") - vae = None if vae_path else dict(repo_id="stabilityai/sd-vae-ft-mse") - - diffuser_path = self.globals.root_dir / "models/converted_ckpts" / model_path.stem - model_name = self.convert_and_import( - model_path, - diffusers_path=diffuser_path, - vae=vae, - vae_path=str(vae_path), - model_name=model_name, - model_description=description, - original_config_file=model_config_file, - commit_to_conf=commit_to_conf, - scan_needed=False, - ) - return model_name - - def convert_and_import( - self, - ckpt_path: Path, - diffusers_path: Path, - model_name=None, - model_description=None, - vae: dict = None, - vae_path: Path = None, - original_config_file: Path = None, - commit_to_conf: Path = None, - scan_needed: bool = True, - ) -> str: - """ - Convert a legacy ckpt weights file to diffuser model and import - into models.yaml. - """ - ckpt_path = self._resolve_path(ckpt_path, "models/ldm/stable-diffusion-v1") - if original_config_file: - original_config_file = self._resolve_path( - original_config_file, "configs/stable-diffusion" - ) - - new_config = None - - from . import convert_ckpt_to_diffusers - - if diffusers_path.exists(): - self.logger.error( - f"The path {str(diffusers_path)} already exists. Please move or remove it and try again." - ) - return - - model_name = model_name or diffusers_path.name - model_description = model_description or f"Converted version of {model_name}" - self.logger.debug(f"Converting {model_name} to diffusers (30-60s)") - try: - # By passing the specified VAE to the conversion function, the autoencoder - # will be built into the model rather than tacked on afterward via the config file - vae_model = None - if vae: - vae_model = self._load_vae(vae) - vae_path = None - convert_ckpt_to_diffusers( - ckpt_path, - diffusers_path, - extract_ema=True, - original_config_file=original_config_file, - vae=vae_model, - vae_path=vae_path, - scan_needed=scan_needed, - ) - self.logger.debug( - f"Success. Converted model is now located at {str(diffusers_path)}" - ) - self.logger.debug(f"Writing new config file entry for {model_name}") - new_config = dict( - path=str(diffusers_path), - description=model_description, - format="diffusers", - ) - if model_name in self.config: - self.del_model(model_name) - self.add_model(model_name, new_config, True) - if commit_to_conf: - self.commit(commit_to_conf) - self.logger.debug("Conversion succeeded") - except Exception as e: - self.logger.warning(f"Conversion failed: {str(e)}") - self.logger.warning( - "If you are trying to convert an inpainting or 2.X model, please indicate the correct config file (e.g. v1-inpainting-inference.yaml)" - ) - - return model_name + self.models[model_key] = model_config def search_models(self, search_folder): self.logger.info(f"Finding Models In: {search_folder}") @@ -1001,31 +628,26 @@ class ModelManager(object): return search_folder, found_models - def _make_cache_room(self) -> None: - num_loaded_models = len(self.models) - if num_loaded_models >= self.max_loaded_models: - least_recent_model = self._pop_oldest_model() - self.logger.info( - f"Cache limit (max={self.max_loaded_models}) reached. Purging {least_recent_model}" - ) - if least_recent_model is not None: - del self.models[least_recent_model] - gc.collect() - - def print_vram_usage(self) -> None: - if self._has_cuda: - self.logger.info( - "Current VRAM usage:"+ - "%4.2fG" % (torch.cuda.memory_allocated() / 1e9), - ) - - def commit(self, config_file_path: str) -> None: + def commit(self, conf_file: Path=None) -> None: """ Write current configuration out to the indicated file. """ - yaml_str = OmegaConf.to_yaml(self.config) - if not os.path.isabs(config_file_path): - config_file_path = self.globals.model_conf_path + data_to_save = dict() + data_to_save["__metadata__"] = self.config_meta.dict() + + for model_key, model_config in self.models.items(): + model_name, base_model, model_type = self.parse_key(model_key) + model_class = MODEL_CLASSES[base_model][model_type] + if model_class.save_to_config: + # TODO: or exclude_unset better fits here? + data_to_save[model_key] = model_config.dict(exclude_defaults=True, exclude={"error"}) + # alias for config file + data_to_save[model_key]["format"] = data_to_save[model_key].pop("model_format") + + yaml_str = OmegaConf.to_yaml(data_to_save) + config_file_path = conf_file or self.config_path + assert config_file_path is not None,'no config file path to write to' + config_file_path = self.app_config.root_path / config_file_path tmpfile = os.path.join(os.path.dirname(config_file_path), "new_config.tmp") with open(tmpfile, "w", encoding="utf-8") as outfile: outfile.write(self.preamble()) @@ -1048,269 +670,152 @@ class ModelManager(object): """ ) - @classmethod - def migrate_models(cls): - """ - Migrate the ~/invokeai/models directory from the legacy format used through 2.2.5 - to the 2.3.0 "diffusers" version. This should be a one-time operation, called at - script startup time. - """ - # Three transformer models to check: bert, clip and safety checker, and - # the diffusers as well - config = get_invokeai_config() - models_dir = config.root_dir / "models" - legacy_locations = [ - Path( - models_dir, - "CompVis/stable-diffusion-safety-checker/models--CompVis--stable-diffusion-safety-checker", - ), - Path(models_dir, "bert-base-uncased/models--bert-base-uncased"), - Path( - models_dir, - "openai/clip-vit-large-patch14/models--openai--clip-vit-large-patch14", - ), - ] - legacy_cache_dir = config.cache_dir / "../diffusers" - legacy_locations.extend(list(legacy_cache_dir.glob("*"))) - legacy_layout = False - for model in legacy_locations: - legacy_layout = legacy_layout or model.exists() - if not legacy_layout: - return + def scan_models_directory( + self, + base_model: Optional[BaseModelType] = None, + model_type: Optional[ModelType] = None, + ): - print( - """ ->> ALERT: ->> The location of your previously-installed diffusers models needs to move from ->> invokeai/models/diffusers to invokeai/models/hub due to a change introduced by ->> diffusers version 0.14. InvokeAI will now move all models from the "diffusers" directory ->> into "hub" and then remove the diffusers directory. This is a quick, safe, one-time ->> operation. However if you have customized either of these directories and need to ->> make adjustments, please press ctrl-C now to abort and relaunch InvokeAI when you are ready. ->> Otherwise press to continue.""" - ) - input("continue> ") + loaded_files = set() + new_models_found = False - # transformer files get moved into the hub directory - if cls._is_huggingface_hub_directory_present(): - hub = config.cache_dir - else: - hub = models_dir / "hub" - - os.makedirs(hub, exist_ok=True) - for model in legacy_locations: - source = models_dir / model - dest = hub / model.stem - if dest.exists() and not source.exists(): - continue - cls.logger.info(f"{source} => {dest}") - if source.exists(): - if dest.is_symlink(): - logger.warning(f"Found symlink at {dest.name}. Not migrating.") - elif dest.exists(): - if source.is_dir(): - rmtree(source) + self.logger.info(f'scanning {self.app_config.models_path} for new models') + with Chdir(self.app_config.root_path): + for model_key, model_config in list(self.models.items()): + model_name, cur_base_model, cur_model_type = self.parse_key(model_key) + model_path = self.app_config.root_path.absolute() / model_config.path + if not model_path.exists(): + model_class = MODEL_CLASSES[cur_base_model][cur_model_type] + if model_class.save_to_config: + model_config.error = ModelError.NotFound else: - source.unlink() + self.models.pop(model_key, None) else: - move(source, dest) + loaded_files.add(model_path) - # now clean up by removing any empty directories - empty = [ - root - for root, dirs, files, in os.walk(models_dir) - if not len(dirs) and not len(files) - ] - for d in empty: - os.rmdir(d) - cls.logger.info("Migration is done. Continuing...") + for cur_base_model in BaseModelType: + if base_model is not None and cur_base_model != base_model: + continue - def _resolve_path( - self, source: Union[str, Path], dest_directory: str - ) -> Optional[Path]: - resolved_path = None - if str(source).startswith(("http:", "https:", "ftp:")): - dest_directory = Path(dest_directory) - if not dest_directory.is_absolute(): - dest_directory = self.globals.root_dir / dest_directory - dest_directory.mkdir(parents=True, exist_ok=True) - resolved_path = download_with_resume(str(source), dest_directory) - else: - source = self.globals.root_dir / source - resolved_path = source - return resolved_path + for cur_model_type in ModelType: + if model_type is not None and cur_model_type != model_type: + continue + model_class = MODEL_CLASSES[cur_base_model][cur_model_type] + models_dir = self.app_config.models_path / cur_base_model.value / cur_model_type.value - def _invalidate_cached_model(self, model_name: str) -> None: - self.offload_model(model_name) - if model_name in self.stack: - self.stack.remove(model_name) - self.models.pop(model_name, None) + if not models_dir.exists(): + continue # TODO: or create all folders? - def _pop_oldest_model(self): - """ - Remove the first element of the FIFO, which ought - to be the least recently accessed model. Do not - pop the last one, because it is in active use! - """ - return self.stack.pop(0) + for model_path in models_dir.iterdir(): + if model_path not in loaded_files: # TODO: check + model_name = model_path.name if model_path.is_dir() else model_path.stem + model_key = self.create_key(model_name, cur_base_model, cur_model_type) - def _push_newest_model(self, model_name: str) -> None: - """ - Maintain a simple FIFO. First element is always the - least recent, and last element is always the most recent. - """ - with contextlib.suppress(ValueError): - self.stack.remove(model_name) - self.stack.append(model_name) + if model_key in self.models: + raise Exception(f"Model with key {model_key} added twice") - def _add_embeddings_to_model(self, model: StableDiffusionGeneratorPipeline): - if self.embedding_path is not None: - self.logger.info(f"Loading embeddings from {self.embedding_path}") - for root, _, files in os.walk(self.embedding_path): - for name in files: - ti_path = os.path.join(root, name) - model.textual_inversion_manager.load_textual_inversion( - ti_path, defer_injecting_tokens=True - ) - self.logger.info( - f'Textual inversion triggers: {", ".join(sorted(model.textual_inversion_manager.get_all_trigger_strings()))}' - ) + if model_path.is_relative_to(self.app_config.root_path): + model_path = model_path.relative_to(self.app_config.root_path) + try: + model_config: ModelConfigBase = model_class.probe_config(str(model_path)) + self.models[model_key] = model_config + new_models_found = True + except NotImplementedError as e: + self.logger.warning(e) - def _has_cuda(self) -> bool: - return self.device.type == "cuda" + imported_models = self.autoimport() - def _diffuser_sha256( - self, name_or_path: Union[str, Path], chunksize=16777216 - ) -> Union[str, bytes]: - path = None - if isinstance(name_or_path, Path): - path = name_or_path - else: - owner, repo = name_or_path.split("/") - path = self.globals.cache_dir / f"models--{owner}--{repo}" - if not path.exists(): - return None - hashpath = path / "checksum.sha256" - if hashpath.exists() and path.stat().st_mtime <= hashpath.stat().st_mtime: - with open(hashpath) as f: - hash = f.read() - return hash - self.logger.debug("Calculating sha256 hash of model files") - tic = time.time() - sha = hashlib.sha256() - count = 0 - for root, dirs, files in os.walk(path, followlinks=False): - for name in files: - count += 1 - with open(os.path.join(root, name), "rb") as f: - while chunk := f.read(chunksize): - sha.update(chunk) - hash = sha.hexdigest() - toc = time.time() - self.logger.debug(f"sha256 = {hash} ({count} files hashed in {toc - tic:4.2f}s)") - with open(hashpath, "w") as f: - f.write(hash) - return hash + if (new_models_found or imported_models) and self.config_path: + self.commit() - def _cached_sha256(self, path, data) -> Union[str, bytes]: - dirname = os.path.dirname(path) - basename = os.path.basename(path) - base, _ = os.path.splitext(basename) - hashpath = os.path.join(dirname, base + ".sha256") + def autoimport(self)->set[Path]: + ''' + Scan the autoimport directory (if defined) and import new models, delete defunct models. + ''' + # avoid circular import + from invokeai.backend.install.model_install_backend import ModelInstall + from invokeai.frontend.install.model_install import ask_user_for_prediction_type + + installer = ModelInstall(config = self.app_config, + model_manager = self, + prediction_type_helper = ask_user_for_prediction_type, + ) + + installed = set() + scanned_dirs = set() + + config = self.app_config + known_paths = {(self.app_config.root_path / x['path']) for x in self.list_models()} - if os.path.exists(hashpath) and os.path.getmtime(path) <= os.path.getmtime( - hashpath - ): - with open(hashpath) as f: - hash = f.read() - return hash + for autodir in [config.autoimport_dir, + config.lora_dir, + config.embedding_dir, + config.controlnet_dir]: + if autodir is None: + continue - self.logger.debug("Calculating sha256 hash of weights file") - tic = time.time() - sha = hashlib.sha256() - sha.update(data) - hash = sha.hexdigest() - toc = time.time() - self.logger.debug(f"sha256 = {hash} "+"(%4.2fs)" % (toc - tic)) + self.logger.info(f'Scanning {autodir} for models to import') + + autodir = self.app_config.root_path / autodir + if not autodir.exists(): + continue - with open(hashpath, "w") as f: - f.write(hash) - return hash + items_scanned = 0 + new_models_found = set() + + for root, dirs, files in os.walk(autodir): + items_scanned += len(dirs) + len(files) + for d in dirs: + path = Path(root) / d + if path in known_paths or path.parent in scanned_dirs: + scanned_dirs.add(path) + continue + if any([(path/x).exists() for x in {'config.json','model_index.json','learned_embeds.bin'}]): + new_models_found.update(installer.heuristic_install(path)) + scanned_dirs.add(path) - def _load_vae(self, vae_config) -> AutoencoderKL: - vae_args = {} - try: - name_or_path = self.model_name_or_path(vae_config) - except Exception: - return None - if name_or_path is None: - return None - using_fp16 = self.precision == "float16" + for f in files: + path = Path(root) / f + if path in known_paths or path.parent in scanned_dirs: + continue + if path.suffix in {'.ckpt','.bin','.pth','.safetensors','.pt'}: + new_models_found.update(installer.heuristic_install(path)) - vae_args.update( - cache_dir=self.globals.cache_dir, - local_files_only=not self.globals.internet_available, - ) + self.logger.info(f'Scanned {items_scanned} files and directories, imported {len(new_models_found)} models') + installed.update(new_models_found) - self.logger.debug(f"Loading diffusers VAE from {name_or_path}") - if using_fp16: - vae_args.update(torch_dtype=torch.float16) - fp_args_list = [{"revision": "fp16"}, {}] - else: - self.logger.debug("Using more accurate float32 precision") - fp_args_list = [{}] + return installed - vae = None - deferred_error = None + def heuristic_import(self, + items_to_import: Set[str], + prediction_type_helper: Callable[[Path],SchedulerPredictionType]=None, + )->Set[str]: + '''Import a list of paths, repo_ids or URLs. Returns the set of + successfully imported items. + :param items_to_import: Set of strings corresponding to models to be imported. + :param prediction_type_helper: A callback that receives the Path of a Stable Diffusion 2 checkpoint model and returns a SchedulerPredictionType. - # A VAE may be in a subfolder of a model's repository. - if "subfolder" in vae_config: - vae_args["subfolder"] = vae_config["subfolder"] + The prediction type helper is necessary to distinguish between + models based on Stable Diffusion 2 Base (requiring + SchedulerPredictionType.Epsilson) and Stable Diffusion 768 + (requiring SchedulerPredictionType.VPrediction). It is + generally impossible to do this programmatically, so the + prediction_type_helper usually asks the user to choose. - for fp_args in fp_args_list: - # At some point we might need to be able to use different classes here? But for now I think - # all Stable Diffusion VAE are AutoencoderKL. + ''' + # avoid circular import here + from invokeai.backend.install.model_install_backend import ModelInstall + successfully_installed = set() + + installer = ModelInstall(config = self.app_config, + prediction_type_helper = prediction_type_helper, + model_manager = self) + for thing in items_to_import: try: - vae = AutoencoderKL.from_pretrained(name_or_path, **vae_args, **fp_args) - except OSError as e: - if str(e).startswith("fp16 is not a valid"): - pass - else: - deferred_error = e - if vae: - break - - if not vae and deferred_error: - self.logger.warning(f"Could not load VAE {name_or_path}: {str(deferred_error)}") - - return vae - - @classmethod - def _delete_model_from_cache(cls,repo_id): - cache_info = scan_cache_dir(get_invokeai_config().cache_dir) - - # I'm sure there is a way to do this with comprehensions - # but the code quickly became incomprehensible! - hashes_to_delete = set() - for repo in cache_info.repos: - if repo.repo_id == repo_id: - for revision in repo.revisions: - hashes_to_delete.add(revision.commit_hash) - strategy = cache_info.delete_revisions(*hashes_to_delete) - cls.logger.warning( - f"Deletion of this model is expected to free {strategy.expected_freed_size_str}" - ) - strategy.execute() - - @staticmethod - def _abs_path(path: str | Path) -> Path: - globals = get_invokeai_config() - if path is None or Path(path).is_absolute(): - return path - return Path(globals.root_dir, path).resolve() - - @staticmethod - def _is_huggingface_hub_directory_present() -> bool: - return ( - os.getenv("HF_HOME") is not None or os.getenv("XDG_CACHE_HOME") is not None - ) + installed = installer.heuristic_install(thing) + successfully_installed.update(installed) + except Exception as e: + self.logger.warning(f'{thing} could not be imported: {str(e)}') + + self.commit() + return successfully_installed diff --git a/invokeai/backend/model_management/model_probe.py b/invokeai/backend/model_management/model_probe.py new file mode 100644 index 0000000000..2828cc7ab1 --- /dev/null +++ b/invokeai/backend/model_management/model_probe.py @@ -0,0 +1,450 @@ +import json +import torch +import safetensors.torch + +from dataclasses import dataclass + +from diffusers import ModelMixin, ConfigMixin +from pathlib import Path +from typing import Callable, Literal, Union, Dict +from picklescan.scanner import scan_file_path + +from .models import ( + BaseModelType, ModelType, ModelVariantType, + SchedulerPredictionType, SilenceWarnings, +) +from .models.base import read_checkpoint_meta + +@dataclass +class ModelProbeInfo(object): + model_type: ModelType + base_type: BaseModelType + variant_type: ModelVariantType + prediction_type: SchedulerPredictionType + upcast_attention: bool + format: Literal['diffusers','checkpoint', 'lycoris'] + image_size: int + +class ProbeBase(object): + '''forward declaration''' + pass + +class ModelProbe(object): + + PROBES = { + 'diffusers': { }, + 'checkpoint': { }, + } + + CLASS2TYPE = { + 'StableDiffusionPipeline' : ModelType.Main, + 'AutoencoderKL' : ModelType.Vae, + 'ControlNetModel' : ModelType.ControlNet, + } + + @classmethod + def register_probe(cls, + format: Literal['diffusers','checkpoint'], + model_type: ModelType, + probe_class: ProbeBase): + cls.PROBES[format][model_type] = probe_class + + @classmethod + def heuristic_probe(cls, + model: Union[Dict, ModelMixin, Path], + prediction_type_helper: Callable[[Path],SchedulerPredictionType]=None, + )->ModelProbeInfo: + if isinstance(model,Path): + return cls.probe(model_path=model,prediction_type_helper=prediction_type_helper) + elif isinstance(model,(dict,ModelMixin,ConfigMixin)): + return cls.probe(model_path=None, model=model, prediction_type_helper=prediction_type_helper) + else: + raise Exception("model parameter {model} is neither a Path, nor a model") + + @classmethod + def probe(cls, + model_path: Path, + model: Union[Dict, ModelMixin] = None, + prediction_type_helper: Callable[[Path],SchedulerPredictionType] = None)->ModelProbeInfo: + ''' + Probe the model at model_path and return sufficient information about it + to place it somewhere in the models directory hierarchy. If the model is + already loaded into memory, you may provide it as model in order to avoid + opening it a second time. The prediction_type_helper callable is a function that receives + the path to the model and returns the BaseModelType. It is called to distinguish + between V2-Base and V2-768 SD models. + ''' + if model_path: + format_type = 'diffusers' if model_path.is_dir() else 'checkpoint' + else: + format_type = 'diffusers' if isinstance(model,(ConfigMixin,ModelMixin)) else 'checkpoint' + + model_info = None + try: + model_type = cls.get_model_type_from_folder(model_path, model) \ + if format_type == 'diffusers' \ + else cls.get_model_type_from_checkpoint(model_path, model) + probe_class = cls.PROBES[format_type].get(model_type) + if not probe_class: + return None + probe = probe_class(model_path, model, prediction_type_helper) + base_type = probe.get_base_type() + variant_type = probe.get_variant_type() + prediction_type = probe.get_scheduler_prediction_type() + format = probe.get_format() + model_info = ModelProbeInfo( + model_type = model_type, + base_type = base_type, + variant_type = variant_type, + prediction_type = prediction_type, + upcast_attention = (base_type==BaseModelType.StableDiffusion2 \ + and prediction_type==SchedulerPredictionType.VPrediction), + format = format, + image_size = 768 if (base_type==BaseModelType.StableDiffusion2 \ + and prediction_type==SchedulerPredictionType.VPrediction \ + ) else 512, + ) + except Exception: + return None + + return model_info + + @classmethod + def get_model_type_from_checkpoint(cls, model_path: Path, checkpoint: dict) -> ModelType: + if model_path.suffix not in ('.bin','.pt','.ckpt','.safetensors','.pth'): + return None + + if model_path.name == "learned_embeds.bin": + return ModelType.TextualInversion + + ckpt = checkpoint if checkpoint else read_checkpoint_meta(model_path, scan=True) + ckpt = ckpt.get("state_dict", ckpt) + + for key in ckpt.keys(): + if any(key.startswith(v) for v in {"cond_stage_model.", "first_stage_model.", "model.diffusion_model."}): + return ModelType.Main + elif any(key.startswith(v) for v in {"encoder.conv_in", "decoder.conv_in"}): + return ModelType.Vae + elif any(key.startswith(v) for v in {"lora_te_", "lora_unet_"}): + return ModelType.Lora + elif any(key.startswith(v) for v in {"control_model", "input_blocks"}): + return ModelType.ControlNet + elif key in {"emb_params", "string_to_param"}: + return ModelType.TextualInversion + + else: + # diffusers-ti + if len(ckpt) < 10 and all(isinstance(v, torch.Tensor) for v in ckpt.values()): + return ModelType.TextualInversion + + raise ValueError("Unable to determine model type") + + @classmethod + def get_model_type_from_folder(cls, folder_path: Path, model: ModelMixin)->ModelType: + ''' + Get the model type of a hugging-face style folder. + ''' + class_name = None + if model: + class_name = model.__class__.__name__ + else: + if (folder_path / 'learned_embeds.bin').exists(): + return ModelType.TextualInversion + + if (folder_path / 'pytorch_lora_weights.bin').exists(): + return ModelType.Lora + + i = folder_path / 'model_index.json' + c = folder_path / 'config.json' + config_path = i if i.exists() else c if c.exists() else None + + if config_path: + with open(config_path,'r') as file: + conf = json.load(file) + class_name = conf['_class_name'] + + if class_name and (type := cls.CLASS2TYPE.get(class_name)): + return type + + # give up + raise ValueError("Unable to determine model type") + + @classmethod + def _scan_and_load_checkpoint(cls,model_path: Path)->dict: + with SilenceWarnings(): + if model_path.suffix.endswith((".ckpt", ".pt", ".bin")): + cls._scan_model(model_path, model_path) + return torch.load(model_path) + else: + return safetensors.torch.load_file(model_path) + + @classmethod + def _scan_model(cls, model_name, checkpoint): + """ + Apply picklescanner to the indicated checkpoint and issue a warning + and option to exit if an infected file is identified. + """ + # scan model + scan_result = scan_file_path(checkpoint) + if scan_result.infected_files != 0: + raise "The model {model_name} is potentially infected by malware. Aborting import." + +###################################################3 +# Checkpoint probing +###################################################3 +class ProbeBase(object): + def get_base_type(self)->BaseModelType: + pass + + def get_variant_type(self)->ModelVariantType: + pass + + def get_scheduler_prediction_type(self)->SchedulerPredictionType: + pass + + def get_format(self)->str: + pass + +class CheckpointProbeBase(ProbeBase): + def __init__(self, + checkpoint_path: Path, + checkpoint: dict, + helper: Callable[[Path],SchedulerPredictionType] = None + )->BaseModelType: + self.checkpoint = checkpoint or ModelProbe._scan_and_load_checkpoint(checkpoint_path) + self.checkpoint_path = checkpoint_path + self.helper = helper + + def get_base_type(self)->BaseModelType: + pass + + def get_format(self)->str: + return 'checkpoint' + + def get_variant_type(self)-> ModelVariantType: + model_type = ModelProbe.get_model_type_from_checkpoint(self.checkpoint_path,self.checkpoint) + if model_type != ModelType.Main: + return ModelVariantType.Normal + state_dict = self.checkpoint.get('state_dict') or self.checkpoint + in_channels = state_dict[ + "model.diffusion_model.input_blocks.0.0.weight" + ].shape[1] + if in_channels == 9: + return ModelVariantType.Inpaint + elif in_channels == 5: + return ModelVariantType.Depth + elif in_channels == 4: + return ModelVariantType.Normal + else: + raise Exception("Cannot determine variant type") + +class PipelineCheckpointProbe(CheckpointProbeBase): + def get_base_type(self)->BaseModelType: + checkpoint = self.checkpoint + state_dict = self.checkpoint.get('state_dict') or checkpoint + key_name = "model.diffusion_model.input_blocks.2.1.transformer_blocks.0.attn2.to_k.weight" + if key_name in state_dict and state_dict[key_name].shape[-1] == 768: + return BaseModelType.StableDiffusion1 + if key_name in state_dict and state_dict[key_name].shape[-1] == 1024: + return BaseModelType.StableDiffusion2 + raise Exception("Cannot determine base type") + + def get_scheduler_prediction_type(self)->SchedulerPredictionType: + type = self.get_base_type() + if type == BaseModelType.StableDiffusion1: + return SchedulerPredictionType.Epsilon + checkpoint = self.checkpoint + state_dict = self.checkpoint.get('state_dict') or checkpoint + key_name = "model.diffusion_model.input_blocks.2.1.transformer_blocks.0.attn2.to_k.weight" + if key_name in state_dict and state_dict[key_name].shape[-1] == 1024: + if 'global_step' in checkpoint: + if checkpoint['global_step'] == 220000: + return SchedulerPredictionType.Epsilon + elif checkpoint["global_step"] == 110000: + return SchedulerPredictionType.VPrediction + if self.checkpoint_path and self.helper \ + and not self.checkpoint_path.with_suffix('.yaml').exists(): # if a .yaml config file exists, then this step not needed + return self.helper(self.checkpoint_path) + else: + return None + +class VaeCheckpointProbe(CheckpointProbeBase): + def get_base_type(self)->BaseModelType: + # I can't find any standalone 2.X VAEs to test with! + return BaseModelType.StableDiffusion1 + +class LoRACheckpointProbe(CheckpointProbeBase): + def get_format(self)->str: + return 'lycoris' + + def get_base_type(self)->BaseModelType: + checkpoint = self.checkpoint + key1 = "lora_te_text_model_encoder_layers_0_mlp_fc1.lora_down.weight" + key2 = "lora_te_text_model_encoder_layers_0_self_attn_k_proj.hada_w1_a" + lora_token_vector_length = ( + checkpoint[key1].shape[1] + if key1 in checkpoint + else checkpoint[key2].shape[0] + if key2 in checkpoint + else 768 + ) + if lora_token_vector_length == 768: + return BaseModelType.StableDiffusion1 + elif lora_token_vector_length == 1024: + return BaseModelType.StableDiffusion2 + else: + return None + +class TextualInversionCheckpointProbe(CheckpointProbeBase): + def get_format(self)->str: + return None + + def get_base_type(self)->BaseModelType: + checkpoint = self.checkpoint + if 'string_to_token' in checkpoint: + token_dim = list(checkpoint['string_to_param'].values())[0].shape[-1] + elif 'emb_params' in checkpoint: + token_dim = checkpoint['emb_params'].shape[-1] + else: + token_dim = list(checkpoint.values())[0].shape[0] + if token_dim == 768: + return BaseModelType.StableDiffusion1 + elif token_dim == 1024: + return BaseModelType.StableDiffusion2 + else: + return None + +class ControlNetCheckpointProbe(CheckpointProbeBase): + def get_base_type(self)->BaseModelType: + checkpoint = self.checkpoint + for key_name in ('control_model.input_blocks.2.1.transformer_blocks.0.attn2.to_k.weight', + 'input_blocks.2.1.transformer_blocks.0.attn2.to_k.weight' + ): + if key_name not in checkpoint: + continue + if checkpoint[key_name].shape[-1] == 768: + return BaseModelType.StableDiffusion1 + elif checkpoint[key_name].shape[-1] == 1024: + return BaseModelType.StableDiffusion2 + elif self.checkpoint_path and self.helper: + return self.helper(self.checkpoint_path) + raise Exception("Unable to determine base type for {self.checkpoint_path}") + +######################################################## +# classes for probing folders +####################################################### +class FolderProbeBase(ProbeBase): + def __init__(self, + folder_path: Path, + model: ModelMixin = None, + helper: Callable=None # not used + ): + self.model = model + self.folder_path = folder_path + + def get_variant_type(self)->ModelVariantType: + return ModelVariantType.Normal + + def get_format(self)->str: + return 'diffusers' + +class PipelineFolderProbe(FolderProbeBase): + def get_base_type(self)->BaseModelType: + if self.model: + unet_conf = self.model.unet.config + else: + with open(self.folder_path / 'unet' / 'config.json','r') as file: + unet_conf = json.load(file) + if unet_conf['cross_attention_dim'] == 768: + return BaseModelType.StableDiffusion1 + elif unet_conf['cross_attention_dim'] == 1024: + return BaseModelType.StableDiffusion2 + else: + raise ValueError(f'Unknown base model for {self.folder_path}') + + def get_scheduler_prediction_type(self)->SchedulerPredictionType: + if self.model: + scheduler_conf = self.model.scheduler.config + else: + with open(self.folder_path / 'scheduler' / 'scheduler_config.json','r') as file: + scheduler_conf = json.load(file) + if scheduler_conf['prediction_type'] == "v_prediction": + return SchedulerPredictionType.VPrediction + elif scheduler_conf['prediction_type'] == 'epsilon': + return SchedulerPredictionType.Epsilon + else: + return None + + def get_variant_type(self)->ModelVariantType: + # This only works for pipelines! Any kind of + # exception results in our returning the + # "normal" variant type + try: + if self.model: + conf = self.model.unet.config + else: + config_file = self.folder_path / 'unet' / 'config.json' + with open(config_file,'r') as file: + conf = json.load(file) + + in_channels = conf['in_channels'] + if in_channels == 9: + return ModelVariantType.Inpainting + elif in_channels == 5: + return ModelVariantType.Depth + elif in_channels == 4: + return ModelVariantType.Normal + except: + pass + return ModelVariantType.Normal + +class VaeFolderProbe(FolderProbeBase): + def get_base_type(self)->BaseModelType: + return BaseModelType.StableDiffusion1 + +class TextualInversionFolderProbe(FolderProbeBase): + def get_format(self)->str: + return None + + def get_base_type(self)->BaseModelType: + path = self.folder_path / 'learned_embeds.bin' + if not path.exists(): + return None + checkpoint = ModelProbe._scan_and_load_checkpoint(path) + return TextualInversionCheckpointProbe(None,checkpoint=checkpoint).get_base_type() + +class ControlNetFolderProbe(FolderProbeBase): + def get_base_type(self)->BaseModelType: + config_file = self.folder_path / 'config.json' + if not config_file.exists(): + raise Exception(f"Cannot determine base type for {self.folder_path}") + with open(config_file,'r') as file: + config = json.load(file) + # no obvious way to distinguish between sd2-base and sd2-768 + return BaseModelType.StableDiffusion1 \ + if config['cross_attention_dim']==768 \ + else BaseModelType.StableDiffusion2 + +class LoRAFolderProbe(FolderProbeBase): + def get_base_type(self)->BaseModelType: + model_file = None + for suffix in ['safetensors','bin']: + base_file = self.folder_path / f'pytorch_lora_weights.{suffix}' + if base_file.exists(): + model_file = base_file + break + if not model_file: + raise Exception('Unknown LoRA format encountered') + return LoRACheckpointProbe(model_file,None).get_base_type() + +############## register probe classes ###### +ModelProbe.register_probe('diffusers', ModelType.Main, PipelineFolderProbe) +ModelProbe.register_probe('diffusers', ModelType.Vae, VaeFolderProbe) +ModelProbe.register_probe('diffusers', ModelType.Lora, LoRAFolderProbe) +ModelProbe.register_probe('diffusers', ModelType.TextualInversion, TextualInversionFolderProbe) +ModelProbe.register_probe('diffusers', ModelType.ControlNet, ControlNetFolderProbe) +ModelProbe.register_probe('checkpoint', ModelType.Main, PipelineCheckpointProbe) +ModelProbe.register_probe('checkpoint', ModelType.Vae, VaeCheckpointProbe) +ModelProbe.register_probe('checkpoint', ModelType.Lora, LoRACheckpointProbe) +ModelProbe.register_probe('checkpoint', ModelType.TextualInversion, TextualInversionCheckpointProbe) +ModelProbe.register_probe('checkpoint', ModelType.ControlNet, ControlNetCheckpointProbe) diff --git a/invokeai/backend/model_management/models/__init__.py b/invokeai/backend/model_management/models/__init__.py new file mode 100644 index 0000000000..87b0ad3c4e --- /dev/null +++ b/invokeai/backend/model_management/models/__init__.py @@ -0,0 +1,95 @@ +import inspect +from enum import Enum +from pydantic import BaseModel +from typing import Literal, get_origin +from .base import BaseModelType, ModelType, SubModelType, ModelBase, ModelConfigBase, ModelVariantType, SchedulerPredictionType, ModelError, SilenceWarnings +from .stable_diffusion import StableDiffusion1Model, StableDiffusion2Model +from .vae import VaeModel +from .lora import LoRAModel +from .controlnet import ControlNetModel # TODO: +from .textual_inversion import TextualInversionModel + +MODEL_CLASSES = { + BaseModelType.StableDiffusion1: { + ModelType.Main: StableDiffusion1Model, + ModelType.Vae: VaeModel, + ModelType.Lora: LoRAModel, + ModelType.ControlNet: ControlNetModel, + ModelType.TextualInversion: TextualInversionModel, + }, + BaseModelType.StableDiffusion2: { + ModelType.Main: StableDiffusion2Model, + ModelType.Vae: VaeModel, + ModelType.Lora: LoRAModel, + ModelType.ControlNet: ControlNetModel, + ModelType.TextualInversion: TextualInversionModel, + }, + #BaseModelType.Kandinsky2_1: { + # ModelType.Main: Kandinsky2_1Model, + # ModelType.MoVQ: MoVQModel, + # ModelType.Lora: LoRAModel, + # ModelType.ControlNet: ControlNetModel, + # ModelType.TextualInversion: TextualInversionModel, + #}, +} + +MODEL_CONFIGS = list() +OPENAPI_MODEL_CONFIGS = list() + +class OpenAPIModelInfoBase(BaseModel): + name: str + base_model: BaseModelType + type: ModelType + + +for base_model, models in MODEL_CLASSES.items(): + for model_type, model_class in models.items(): + model_configs = set(model_class._get_configs().values()) + model_configs.discard(None) + MODEL_CONFIGS.extend(model_configs) + + for cfg in model_configs: + model_name, cfg_name = cfg.__qualname__.split('.')[-2:] + openapi_cfg_name = model_name + cfg_name + if openapi_cfg_name in vars(): + continue + + api_wrapper = type(openapi_cfg_name, (cfg, OpenAPIModelInfoBase), dict( + __annotations__ = dict( + type=Literal[model_type.value], + ), + )) + + #globals()[openapi_cfg_name] = api_wrapper + vars()[openapi_cfg_name] = api_wrapper + OPENAPI_MODEL_CONFIGS.append(api_wrapper) + +def get_model_config_enums(): + enums = list() + + for model_config in MODEL_CONFIGS: + fields = inspect.get_annotations(model_config) + try: + field = fields["model_format"] + except: + raise Exception("format field not found") + + # model_format: None + # model_format: SomeModelFormat + # model_format: Literal[SomeModelFormat.Diffusers] + # model_format: Literal[SomeModelFormat.Diffusers, SomeModelFormat.Checkpoint] + + if isinstance(field, type) and issubclass(field, str) and issubclass(field, Enum): + enums.append(field) + + elif get_origin(field) is Literal and all(isinstance(arg, str) and isinstance(arg, Enum) for arg in field.__args__): + enums.append(type(field.__args__[0])) + + elif field is None: + pass + + else: + raise Exception(f"Unsupported format definition in {model_configs.__qualname__}") + + return enums + diff --git a/invokeai/backend/model_management/models/base.py b/invokeai/backend/model_management/models/base.py new file mode 100644 index 0000000000..afa62b2e4f --- /dev/null +++ b/invokeai/backend/model_management/models/base.py @@ -0,0 +1,423 @@ +import json +import os +import sys +import typing +import inspect +from enum import Enum +from abc import ABCMeta, abstractmethod +from pathlib import Path +from picklescan.scanner import scan_file_path +import torch +import safetensors.torch +from diffusers import DiffusionPipeline, ConfigMixin + +from contextlib import suppress +from pydantic import BaseModel, Field +from typing import List, Dict, Optional, Type, Literal, TypeVar, Generic, Callable, Any, Union + +class BaseModelType(str, Enum): + StableDiffusion1 = "sd-1" + StableDiffusion2 = "sd-2" + #Kandinsky2_1 = "kandinsky-2.1" + +class ModelType(str, Enum): + Main = "main" + Vae = "vae" + Lora = "lora" + ControlNet = "controlnet" # used by model_probe + TextualInversion = "embedding" + +class SubModelType(str, Enum): + UNet = "unet" + TextEncoder = "text_encoder" + Tokenizer = "tokenizer" + Vae = "vae" + Scheduler = "scheduler" + SafetyChecker = "safety_checker" + #MoVQ = "movq" + +class ModelVariantType(str, Enum): + Normal = "normal" + Inpaint = "inpaint" + Depth = "depth" + +class SchedulerPredictionType(str, Enum): + Epsilon = "epsilon" + VPrediction = "v_prediction" + Sample = "sample" + +class ModelError(str, Enum): + NotFound = "not_found" + +class ModelConfigBase(BaseModel): + path: str # or Path + description: Optional[str] = Field(None) + model_format: Optional[str] = Field(None) + # do not save to config + error: Optional[ModelError] = Field(None) + + class Config: + use_enum_values = True + +class EmptyConfigLoader(ConfigMixin): + @classmethod + def load_config(cls, *args, **kwargs): + cls.config_name = kwargs.pop("config_name") + return super().load_config(*args, **kwargs) + +T_co = TypeVar('T_co', covariant=True) +class classproperty(Generic[T_co]): + def __init__(self, fget: Callable[[Any], T_co]) -> None: + self.fget = fget + + def __get__(self, instance: Optional[Any], owner: Type[Any]) -> T_co: + return self.fget(owner) + + def __set__(self, instance: Optional[Any], value: Any) -> None: + raise AttributeError('cannot set attribute') + +class ModelBase(metaclass=ABCMeta): + #model_path: str + #base_model: BaseModelType + #model_type: ModelType + + def __init__( + self, + model_path: str, + base_model: BaseModelType, + model_type: ModelType, + ): + self.model_path = model_path + self.base_model = base_model + self.model_type = model_type + + def _hf_definition_to_type(self, subtypes: List[str]) -> Type: + if len(subtypes) < 2: + raise Exception("Invalid subfolder definition!") + if all(t is None for t in subtypes): + return None + elif any(t is None for t in subtypes): + raise Exception(f"Unsupported definition: {subtypes}") + + if subtypes[0] in ["diffusers", "transformers"]: + res_type = sys.modules[subtypes[0]] + subtypes = subtypes[1:] + + else: + res_type = sys.modules["diffusers"] + res_type = getattr(res_type, "pipelines") + + + for subtype in subtypes: + res_type = getattr(res_type, subtype) + return res_type + + @classmethod + def _get_configs(cls): + with suppress(Exception): + return cls.__configs + + configs = dict() + for name in dir(cls): + if name.startswith("__"): + continue + + value = getattr(cls, name) + if not isinstance(value, type) or not issubclass(value, ModelConfigBase): + continue + + if hasattr(inspect,'get_annotations'): + fields = inspect.get_annotations(value) + else: + fields = value.__annotations__ + try: + field = fields["model_format"] + except: + raise Exception(f"Invalid config definition - format field not found({cls.__qualname__})") + + if isinstance(field, type) and issubclass(field, str) and issubclass(field, Enum): + for model_format in field: + configs[model_format.value] = value + + elif typing.get_origin(field) is Literal and all(isinstance(arg, str) and isinstance(arg, Enum) for arg in field.__args__): + for model_format in field.__args__: + configs[model_format.value] = value + + elif field is None: + configs[None] = value + + else: + raise Exception(f"Unsupported format definition in {cls.__qualname__}") + + cls.__configs = configs + return cls.__configs + + @classmethod + def create_config(cls, **kwargs) -> ModelConfigBase: + if "model_format" not in kwargs: + raise Exception("Field 'model_format' not found in model config") + + configs = cls._get_configs() + return configs[kwargs["model_format"]](**kwargs) + + @classmethod + def probe_config(cls, path: str, **kwargs) -> ModelConfigBase: + return cls.create_config( + path=path, + model_format=cls.detect_format(path), + ) + + @classmethod + @abstractmethod + def detect_format(cls, path: str) -> str: + raise NotImplementedError() + + @classproperty + @abstractmethod + def save_to_config(cls) -> bool: + raise NotImplementedError() + + @abstractmethod + def get_size(self, child_type: Optional[SubModelType] = None) -> int: + raise NotImplementedError() + + @abstractmethod + def get_model( + self, + torch_dtype: Optional[torch.dtype], + child_type: Optional[SubModelType] = None, + ) -> Any: + raise NotImplementedError() + + +class DiffusersModel(ModelBase): + #child_types: Dict[str, Type] + #child_sizes: Dict[str, int] + + def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): + super().__init__(model_path, base_model, model_type) + + self.child_types: Dict[str, Type] = dict() + self.child_sizes: Dict[str, int] = dict() + + try: + config_data = DiffusionPipeline.load_config(self.model_path) + #config_data = json.loads(os.path.join(self.model_path, "model_index.json")) + except: + raise Exception("Invalid diffusers model! (model_index.json not found or invalid)") + + config_data.pop("_ignore_files", None) + + # retrieve all folder_names that contain relevant files + child_components = [k for k, v in config_data.items() if isinstance(v, list)] + + for child_name in child_components: + child_type = self._hf_definition_to_type(config_data[child_name]) + self.child_types[child_name] = child_type + self.child_sizes[child_name] = calc_model_size_by_fs(self.model_path, subfolder=child_name) + + + def get_size(self, child_type: Optional[SubModelType] = None): + if child_type is None: + return sum(self.child_sizes.values()) + else: + return self.child_sizes[child_type] + + + def get_model( + self, + torch_dtype: Optional[torch.dtype], + child_type: Optional[SubModelType] = None, + ): + # return pipeline in different function to pass more arguments + if child_type is None: + raise Exception("Child model type can't be null on diffusers model") + if child_type not in self.child_types: + return None # TODO: or raise + + if torch_dtype == torch.float16: + variants = ["fp16", None] + else: + variants = [None, "fp16"] + + # TODO: better error handling(differentiate not found from others) + for variant in variants: + try: + # TODO: set cache_dir to /dev/null to be sure that cache not used? + model = self.child_types[child_type].from_pretrained( + self.model_path, + subfolder=child_type.value, + torch_dtype=torch_dtype, + variant=variant, + local_files_only=True, + ) + break + except Exception as e: + #print("====ERR LOAD====") + #print(f"{variant}: {e}") + pass + else: + raise Exception(f"Failed to load {self.base_model}:{self.model_type}:{child_type} model") + + # calc more accurate size + self.child_sizes[child_type] = calc_model_size_by_data(model) + return model + + #def convert_if_required(model_path: str, cache_path: str, config: Optional[dict]) -> str: + + + +def calc_model_size_by_fs( + model_path: str, + subfolder: Optional[str] = None, + variant: Optional[str] = None +): + if subfolder is not None: + model_path = os.path.join(model_path, subfolder) + + # this can happen when, for example, the safety checker + # is not downloaded. + if not os.path.exists(model_path): + return 0 + + all_files = os.listdir(model_path) + all_files = [f for f in all_files if os.path.isfile(os.path.join(model_path, f))] + + fp16_files = set([f for f in all_files if ".fp16." in f or ".fp16-" in f]) + bit8_files = set([f for f in all_files if ".8bit." in f or ".8bit-" in f]) + other_files = set(all_files) - fp16_files - bit8_files + + if variant is None: + files = other_files + elif variant == "fp16": + files = fp16_files + elif variant == "8bit": + files = bit8_files + else: + raise NotImplementedError(f"Unknown variant: {variant}") + + # try read from index if exists + index_postfix = ".index.json" + if variant is not None: + index_postfix = f".index.{variant}.json" + + for file in files: + if not file.endswith(index_postfix): + continue + try: + with open(os.path.join(model_path, file), "r") as f: + index_data = json.loads(f.read()) + return int(index_data["metadata"]["total_size"]) + except: + pass + + # calculate files size if there is no index file + formats = [ + (".safetensors",), # safetensors + (".bin",), # torch + (".onnx", ".pb"), # onnx + (".msgpack",), # flax + (".ckpt",), # tf + (".h5",), # tf2 + ] + + for file_format in formats: + model_files = [f for f in files if f.endswith(file_format)] + if len(model_files) == 0: + continue + + model_size = 0 + for model_file in model_files: + file_stats = os.stat(os.path.join(model_path, model_file)) + model_size += file_stats.st_size + return model_size + + #raise NotImplementedError(f"Unknown model structure! Files: {all_files}") + return 0 # scheduler/feature_extractor/tokenizer - models without loading to gpu + + +def calc_model_size_by_data(model) -> int: + if isinstance(model, DiffusionPipeline): + return _calc_pipeline_by_data(model) + elif isinstance(model, torch.nn.Module): + return _calc_model_by_data(model) + else: + return 0 + + +def _calc_pipeline_by_data(pipeline) -> int: + res = 0 + for submodel_key in pipeline.components.keys(): + submodel = getattr(pipeline, submodel_key) + if submodel is not None and isinstance(submodel, torch.nn.Module): + res += _calc_model_by_data(submodel) + return res + + +def _calc_model_by_data(model) -> int: + mem_params = sum([param.nelement()*param.element_size() for param in model.parameters()]) + mem_bufs = sum([buf.nelement()*buf.element_size() for buf in model.buffers()]) + mem = mem_params + mem_bufs # in bytes + return mem + + +def _fast_safetensors_reader(path: str): + checkpoint = dict() + device = torch.device("meta") + with open(path, "rb") as f: + definition_len = int.from_bytes(f.read(8), 'little') + definition_json = f.read(definition_len) + definition = json.loads(definition_json) + + if "__metadata__" in definition and definition["__metadata__"].get("format", "pt") not in {"pt", "torch", "pytorch"}: + raise Exception("Supported only pytorch safetensors files") + definition.pop("__metadata__", None) + + for key, info in definition.items(): + dtype = { + "I8": torch.int8, + "I16": torch.int16, + "I32": torch.int32, + "I64": torch.int64, + "F16": torch.float16, + "F32": torch.float32, + "F64": torch.float64, + }[info["dtype"]] + + checkpoint[key] = torch.empty(info["shape"], dtype=dtype, device=device) + + return checkpoint + +def read_checkpoint_meta(path: Union[str, Path], scan: bool = False): + if str(path).endswith(".safetensors"): + try: + checkpoint = _fast_safetensors_reader(path) + except: + # TODO: create issue for support "meta"? + checkpoint = safetensors.torch.load_file(path, device="cpu") + else: + if scan: + scan_result = scan_file_path(path) + if scan_result.infected_files != 0: + raise Exception(f"The model file \"{path}\" is potentially infected by malware. Aborting import.") + checkpoint = torch.load(path, map_location=torch.device("meta")) + return checkpoint + +import warnings +from diffusers import logging as diffusers_logging +from transformers import logging as transformers_logging + +class SilenceWarnings(object): + def __init__(self): + self.transformers_verbosity = transformers_logging.get_verbosity() + self.diffusers_verbosity = diffusers_logging.get_verbosity() + + def __enter__(self): + transformers_logging.set_verbosity_error() + diffusers_logging.set_verbosity_error() + warnings.simplefilter('ignore') + + def __exit__(self, type, value, traceback): + transformers_logging.set_verbosity(self.transformers_verbosity) + diffusers_logging.set_verbosity(self.diffusers_verbosity) + warnings.simplefilter('default') diff --git a/invokeai/backend/model_management/models/controlnet.py b/invokeai/backend/model_management/models/controlnet.py new file mode 100644 index 0000000000..9563f87afd --- /dev/null +++ b/invokeai/backend/model_management/models/controlnet.py @@ -0,0 +1,92 @@ +import os +import torch +from enum import Enum +from pathlib import Path +from typing import Optional, Union, Literal +from .base import ( + ModelBase, + ModelConfigBase, + BaseModelType, + ModelType, + SubModelType, + EmptyConfigLoader, + calc_model_size_by_fs, + calc_model_size_by_data, + classproperty, +) + +class ControlNetModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + +class ControlNetModel(ModelBase): + #model_class: Type + #model_size: int + + class Config(ModelConfigBase): + model_format: ControlNetModelFormat + + def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): + assert model_type == ModelType.ControlNet + super().__init__(model_path, base_model, model_type) + + try: + config = EmptyConfigLoader.load_config(self.model_path, config_name="config.json") + #config = json.loads(os.path.join(self.model_path, "config.json")) + except: + raise Exception("Invalid controlnet model! (config.json not found or invalid)") + + model_class_name = config.get("_class_name", None) + if model_class_name not in {"ControlNetModel"}: + raise Exception(f"Invalid ControlNet model! Unknown _class_name: {model_class_name}") + + try: + self.model_class = self._hf_definition_to_type(["diffusers", model_class_name]) + self.model_size = calc_model_size_by_fs(self.model_path) + except: + raise Exception("Invalid ControlNet model!") + + def get_size(self, child_type: Optional[SubModelType] = None): + if child_type is not None: + raise Exception("There is no child models in controlnet model") + return self.model_size + + def get_model( + self, + torch_dtype: Optional[torch.dtype], + child_type: Optional[SubModelType] = None, + ): + if child_type is not None: + raise Exception("There is no child models in controlnet model") + + model = self.model_class.from_pretrained( + self.model_path, + torch_dtype=torch_dtype, + ) + # calc more accurate size + self.model_size = calc_model_size_by_data(model) + return model + + @classproperty + def save_to_config(cls) -> bool: + return False + + @classmethod + def detect_format(cls, path: str): + if os.path.isdir(path): + return ControlNetModelFormat.Diffusers + else: + return ControlNetModelFormat.Checkpoint + + @classmethod + def convert_if_required( + cls, + model_path: str, + output_path: str, + config: ModelConfigBase, # empty config or config of parent model + base_model: BaseModelType, + ) -> str: + if cls.detect_format(model_path) != ControlNetModelFormat.Diffusers: + raise NotImplementedError("Checkpoint controlnet models currently unsupported") + else: + return model_path diff --git a/invokeai/backend/model_management/models/lora.py b/invokeai/backend/model_management/models/lora.py new file mode 100644 index 0000000000..59feacde06 --- /dev/null +++ b/invokeai/backend/model_management/models/lora.py @@ -0,0 +1,76 @@ +import os +import torch +from enum import Enum +from typing import Optional, Union, Literal +from .base import ( + ModelBase, + ModelConfigBase, + BaseModelType, + ModelType, + SubModelType, + classproperty, +) +# TODO: naming +from ..lora import LoRAModel as LoRAModelRaw + +class LoRAModelFormat(str, Enum): + LyCORIS = "lycoris" + Diffusers = "diffusers" + +class LoRAModel(ModelBase): + #model_size: int + + class Config(ModelConfigBase): + model_format: LoRAModelFormat # TODO: + + def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): + assert model_type == ModelType.Lora + super().__init__(model_path, base_model, model_type) + + self.model_size = os.path.getsize(self.model_path) + + def get_size(self, child_type: Optional[SubModelType] = None): + if child_type is not None: + raise Exception("There is no child models in lora") + return self.model_size + + def get_model( + self, + torch_dtype: Optional[torch.dtype], + child_type: Optional[SubModelType] = None, + ): + if child_type is not None: + raise Exception("There is no child models in lora") + + model = LoRAModelRaw.from_checkpoint( + file_path=self.model_path, + dtype=torch_dtype, + ) + + self.model_size = model.calc_size() + return model + + @classproperty + def save_to_config(cls) -> bool: + return False + + @classmethod + def detect_format(cls, path: str): + if os.path.isdir(path): + return LoRAModelFormat.Diffusers + else: + return LoRAModelFormat.LyCORIS + + @classmethod + def convert_if_required( + cls, + model_path: str, + output_path: str, + config: ModelConfigBase, + base_model: BaseModelType, + ) -> str: + if cls.detect_format(model_path) == LoRAModelFormat.Diffusers: + # TODO: add diffusers lora when it stabilizes a bit + raise NotImplementedError("Diffusers lora not supported") + else: + return model_path diff --git a/invokeai/backend/model_management/models/stable_diffusion.py b/invokeai/backend/model_management/models/stable_diffusion.py new file mode 100644 index 0000000000..a5d43c98a2 --- /dev/null +++ b/invokeai/backend/model_management/models/stable_diffusion.py @@ -0,0 +1,282 @@ +import os +import json +from enum import Enum +from pydantic import Field +from pathlib import Path +from typing import Literal, Optional, Union +from .base import ( + ModelBase, + ModelConfigBase, + BaseModelType, + ModelType, + SubModelType, + ModelVariantType, + DiffusersModel, + SchedulerPredictionType, + SilenceWarnings, + read_checkpoint_meta, + classproperty, +) +from invokeai.app.services.config import InvokeAIAppConfig +from omegaconf import OmegaConf + +class StableDiffusion1ModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + +class StableDiffusion1Model(DiffusersModel): + + class DiffusersConfig(ModelConfigBase): + model_format: Literal[StableDiffusion1ModelFormat.Diffusers] + vae: Optional[str] = Field(None) + variant: ModelVariantType + + class CheckpointConfig(ModelConfigBase): + model_format: Literal[StableDiffusion1ModelFormat.Checkpoint] + vae: Optional[str] = Field(None) + config: str + variant: ModelVariantType + + + def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): + assert base_model == BaseModelType.StableDiffusion1 + assert model_type == ModelType.Main + super().__init__( + model_path=model_path, + base_model=BaseModelType.StableDiffusion1, + model_type=ModelType.Main, + ) + + @classmethod + def probe_config(cls, path: str, **kwargs): + model_format = cls.detect_format(path) + ckpt_config_path = kwargs.get("config", None) + if model_format == StableDiffusion1ModelFormat.Checkpoint: + if ckpt_config_path: + ckpt_config = OmegaConf.load(ckpt_config_path) + ckpt_config["model"]["params"]["unet_config"]["params"]["in_channels"] + + else: + checkpoint = read_checkpoint_meta(path) + checkpoint = checkpoint.get('state_dict', checkpoint) + in_channels = checkpoint["model.diffusion_model.input_blocks.0.0.weight"].shape[1] + + elif model_format == StableDiffusion1ModelFormat.Diffusers: + unet_config_path = os.path.join(path, "unet", "config.json") + if os.path.exists(unet_config_path): + with open(unet_config_path, "r") as f: + unet_config = json.loads(f.read()) + in_channels = unet_config['in_channels'] + + else: + raise NotImplementedError(f"{path} is not a supported stable diffusion diffusers format") + + else: + raise NotImplementedError(f"Unknown stable diffusion 1.* format: {model_format}") + + if in_channels == 9: + variant = ModelVariantType.Inpaint + elif in_channels == 4: + variant = ModelVariantType.Normal + else: + raise Exception("Unkown stable diffusion 1.* model format") + + if ckpt_config_path is None: + ckpt_config_path = _select_ckpt_config(BaseModelType.StableDiffusion1, variant) + + return cls.create_config( + path=path, + model_format=model_format, + + config=ckpt_config_path, + variant=variant, + ) + + @classproperty + def save_to_config(cls) -> bool: + return True + + @classmethod + def detect_format(cls, model_path: str): + if os.path.isdir(model_path): + return StableDiffusion1ModelFormat.Diffusers + else: + return StableDiffusion1ModelFormat.Checkpoint + + @classmethod + def convert_if_required( + cls, + model_path: str, + output_path: str, + config: ModelConfigBase, + base_model: BaseModelType, + ) -> str: + if isinstance(config, cls.CheckpointConfig): + return _convert_ckpt_and_cache( + version=BaseModelType.StableDiffusion1, + model_config=config, + output_path=output_path, + ) + else: + return model_path + +class StableDiffusion2ModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + +class StableDiffusion2Model(DiffusersModel): + + # TODO: check that configs overwriten properly + class DiffusersConfig(ModelConfigBase): + model_format: Literal[StableDiffusion2ModelFormat.Diffusers] + vae: Optional[str] = Field(None) + variant: ModelVariantType + + class CheckpointConfig(ModelConfigBase): + model_format: Literal[StableDiffusion2ModelFormat.Checkpoint] + vae: Optional[str] = Field(None) + config: str + variant: ModelVariantType + + def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): + assert base_model == BaseModelType.StableDiffusion2 + assert model_type == ModelType.Main + super().__init__( + model_path=model_path, + base_model=BaseModelType.StableDiffusion2, + model_type=ModelType.Main, + ) + + @classmethod + def probe_config(cls, path: str, **kwargs): + model_format = cls.detect_format(path) + ckpt_config_path = kwargs.get("config", None) + if model_format == StableDiffusion2ModelFormat.Checkpoint: + if ckpt_config_path: + ckpt_config = OmegaConf.load(ckpt_config_path) + ckpt_config["model"]["params"]["unet_config"]["params"]["in_channels"] + + else: + checkpoint = read_checkpoint_meta(path) + checkpoint = checkpoint.get('state_dict', checkpoint) + in_channels = checkpoint["model.diffusion_model.input_blocks.0.0.weight"].shape[1] + + elif model_format == StableDiffusion2ModelFormat.Diffusers: + unet_config_path = os.path.join(path, "unet", "config.json") + if os.path.exists(unet_config_path): + with open(unet_config_path, "r") as f: + unet_config = json.loads(f.read()) + in_channels = unet_config['in_channels'] + + else: + raise Exception("Not supported stable diffusion diffusers format(possibly onnx?)") + + else: + raise NotImplementedError(f"Unknown stable diffusion 2.* format: {model_format}") + + if in_channels == 9: + variant = ModelVariantType.Inpaint + elif in_channels == 5: + variant = ModelVariantType.Depth + elif in_channels == 4: + variant = ModelVariantType.Normal + else: + raise Exception("Unkown stable diffusion 2.* model format") + + if ckpt_config_path is None: + ckpt_config_path = _select_ckpt_config(BaseModelType.StableDiffusion2, variant) + + return cls.create_config( + path=path, + model_format=model_format, + + config=ckpt_config_path, + variant=variant, + ) + + @classproperty + def save_to_config(cls) -> bool: + return True + + @classmethod + def detect_format(cls, model_path: str): + if os.path.isdir(model_path): + return StableDiffusion2ModelFormat.Diffusers + else: + return StableDiffusion2ModelFormat.Checkpoint + + @classmethod + def convert_if_required( + cls, + model_path: str, + output_path: str, + config: ModelConfigBase, + base_model: BaseModelType, + ) -> str: + if isinstance(config, cls.CheckpointConfig): + return _convert_ckpt_and_cache( + version=BaseModelType.StableDiffusion2, + model_config=config, + output_path=output_path, + ) + else: + return model_path + +def _select_ckpt_config(version: BaseModelType, variant: ModelVariantType): + ckpt_configs = { + BaseModelType.StableDiffusion1: { + ModelVariantType.Normal: "v1-inference.yaml", + ModelVariantType.Inpaint: "v1-inpainting-inference.yaml", + }, + BaseModelType.StableDiffusion2: { + ModelVariantType.Normal: "v2-inference-v.yaml", # best guess, as we can't differentiate with base(512) + ModelVariantType.Inpaint: "v2-inpainting-inference.yaml", + ModelVariantType.Depth: "v2-midas-inference.yaml", + } + } + + app_config = InvokeAIAppConfig.get_config() + try: + config_path = app_config.legacy_conf_path / ckpt_configs[version][variant] + if config_path.is_relative_to(app_config.root_path): + config_path = config_path.relative_to(app_config.root_path) + return str(config_path) + + except: + return None + + +# TODO: rework +def _convert_ckpt_and_cache( + version: BaseModelType, + model_config: Union[StableDiffusion1Model.CheckpointConfig, StableDiffusion2Model.CheckpointConfig], + output_path: str, +) -> str: + """ + Convert the checkpoint model indicated in mconfig into a + diffusers, cache it to disk, and return Path to converted + file. If already on disk then just returns Path. + """ + app_config = InvokeAIAppConfig.get_config() + + weights = app_config.root_path / model_config.path + config_file = app_config.root_path / model_config.config + output_path = Path(output_path) + + # return cached version if it exists + if output_path.exists(): + return output_path + + # to avoid circular import errors + from ..convert_ckpt_to_diffusers import convert_ckpt_to_diffusers + with SilenceWarnings(): + convert_ckpt_to_diffusers( + weights, + output_path, + model_version=version, + model_variant=model_config.variant, + original_config_file=config_file, + extract_ema=True, + scan_needed=True, + ) + return output_path diff --git a/invokeai/backend/model_management/models/textual_inversion.py b/invokeai/backend/model_management/models/textual_inversion.py new file mode 100644 index 0000000000..9a032218f0 --- /dev/null +++ b/invokeai/backend/model_management/models/textual_inversion.py @@ -0,0 +1,64 @@ +import os +import torch +from typing import Optional +from .base import ( + ModelBase, + ModelConfigBase, + BaseModelType, + ModelType, + SubModelType, + classproperty, +) +# TODO: naming +from ..lora import TextualInversionModel as TextualInversionModelRaw + +class TextualInversionModel(ModelBase): + #model_size: int + + class Config(ModelConfigBase): + model_format: None + + def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): + assert model_type == ModelType.TextualInversion + super().__init__(model_path, base_model, model_type) + + self.model_size = os.path.getsize(self.model_path) + + def get_size(self, child_type: Optional[SubModelType] = None): + if child_type is not None: + raise Exception("There is no child models in textual inversion") + return self.model_size + + def get_model( + self, + torch_dtype: Optional[torch.dtype], + child_type: Optional[SubModelType] = None, + ): + if child_type is not None: + raise Exception("There is no child models in textual inversion") + + model = TextualInversionModelRaw.from_checkpoint( + file_path=self.model_path, + dtype=torch_dtype, + ) + + self.model_size = model.embedding.nelement() * model.embedding.element_size() + return model + + @classproperty + def save_to_config(cls) -> bool: + return False + + @classmethod + def detect_format(cls, path: str): + return None + + @classmethod + def convert_if_required( + cls, + model_path: str, + output_path: str, + config: ModelConfigBase, + base_model: BaseModelType, + ) -> str: + return model_path diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py new file mode 100644 index 0000000000..3f0d226687 --- /dev/null +++ b/invokeai/backend/model_management/models/vae.py @@ -0,0 +1,165 @@ +import os +import torch +import safetensors +from enum import Enum +from pathlib import Path +from typing import Optional, Union, Literal +from .base import ( + ModelBase, + ModelConfigBase, + BaseModelType, + ModelType, + SubModelType, + ModelVariantType, + EmptyConfigLoader, + calc_model_size_by_fs, + calc_model_size_by_data, + classproperty, +) +from invokeai.app.services.config import InvokeAIAppConfig +from diffusers.utils import is_safetensors_available +from omegaconf import OmegaConf + +class VaeModelFormat(str, Enum): + Checkpoint = "checkpoint" + Diffusers = "diffusers" + +class VaeModel(ModelBase): + #vae_class: Type + #model_size: int + + class Config(ModelConfigBase): + model_format: VaeModelFormat + + def __init__(self, model_path: str, base_model: BaseModelType, model_type: ModelType): + assert model_type == ModelType.Vae + super().__init__(model_path, base_model, model_type) + + try: + config = EmptyConfigLoader.load_config(self.model_path, config_name="config.json") + #config = json.loads(os.path.join(self.model_path, "config.json")) + except: + raise Exception("Invalid vae model! (config.json not found or invalid)") + + try: + vae_class_name = config.get("_class_name", "AutoencoderKL") + self.vae_class = self._hf_definition_to_type(["diffusers", vae_class_name]) + self.model_size = calc_model_size_by_fs(self.model_path) + except: + raise Exception("Invalid vae model! (Unkown vae type)") + + def get_size(self, child_type: Optional[SubModelType] = None): + if child_type is not None: + raise Exception("There is no child models in vae model") + return self.model_size + + def get_model( + self, + torch_dtype: Optional[torch.dtype], + child_type: Optional[SubModelType] = None, + ): + if child_type is not None: + raise Exception("There is no child models in vae model") + + model = self.vae_class.from_pretrained( + self.model_path, + torch_dtype=torch_dtype, + ) + # calc more accurate size + self.model_size = calc_model_size_by_data(model) + return model + + @classproperty + def save_to_config(cls) -> bool: + return False + + @classmethod + def detect_format(cls, path: str): + if os.path.isdir(path): + return VaeModelFormat.Diffusers + else: + return VaeModelFormat.Checkpoint + + @classmethod + def convert_if_required( + cls, + model_path: str, + output_path: str, + config: ModelConfigBase, # empty config or config of parent model + base_model: BaseModelType, + ) -> str: + if cls.detect_format(model_path) == VaeModelFormat.Checkpoint: + return _convert_vae_ckpt_and_cache( + weights_path=model_path, + output_path=output_path, + base_model=base_model, + model_config=config, + ) + else: + return model_path + +# TODO: rework +def _convert_vae_ckpt_and_cache( + weights_path: str, + output_path: str, + base_model: BaseModelType, + model_config: ModelConfigBase, +) -> str: + """ + Convert the VAE indicated in mconfig into a diffusers AutoencoderKL + object, cache it to disk, and return Path to converted + file. If already on disk then just returns Path. + """ + app_config = InvokeAIAppConfig.get_config() + weights_path = app_config.root_dir / weights_path + output_path = Path(output_path) + + """ + this size used only in when tiling enabled to separate input in tiles + sizes in configs from stable diffusion githubs(1 and 2) set to 256 + on huggingface it: + 1.5 - 512 + 1.5-inpainting - 256 + 2-inpainting - 512 + 2-depth - 256 + 2-base - 512 + 2 - 768 + 2.1-base - 768 + 2.1 - 768 + """ + image_size = 512 + + # return cached version if it exists + if output_path.exists(): + return output_path + + if base_model in {BaseModelType.StableDiffusion1, BaseModelType.StableDiffusion2}: + from .stable_diffusion import _select_ckpt_config + # all sd models use same vae settings + config_file = _select_ckpt_config(base_model, ModelVariantType.Normal) + else: + raise Exception(f"Vae conversion not supported for model type: {base_model}") + + # this avoids circular import error + from ..convert_ckpt_to_diffusers import convert_ldm_vae_to_diffusers + if weights_path.suffix == '.safetensors': + checkpoint = safetensors.torch.load_file(weights_path, device="cpu") + else: + checkpoint = torch.load(weights_path, map_location="cpu") + + # sometimes weights are hidden under "state_dict", and sometimes not + if "state_dict" in checkpoint: + checkpoint = checkpoint["state_dict"] + + config = OmegaConf.load(app_config.root_path/config_file) + + vae_model = convert_ldm_vae_to_diffusers( + checkpoint = checkpoint, + vae_config = config, + image_size = image_size, + ) + vae_model.save_pretrained( + output_path, + safe_serialization=is_safetensors_available() + ) + return output_path diff --git a/invokeai/backend/prompting/__init__.py b/invokeai/backend/prompting/__init__.py deleted file mode 100644 index b52206dd94..0000000000 --- a/invokeai/backend/prompting/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Initialization file for invokeai.backend.prompting -""" -from .conditioning import ( - get_prompt_structure, - get_tokens_for_prompt_object, - get_uc_and_c_and_ec, - split_weighted_subprompts, -) diff --git a/invokeai/backend/prompting/conditioning.py b/invokeai/backend/prompting/conditioning.py deleted file mode 100644 index 2e62853872..0000000000 --- a/invokeai/backend/prompting/conditioning.py +++ /dev/null @@ -1,294 +0,0 @@ -""" -This module handles the generation of the conditioning tensors. - -Useful function exports: - -get_uc_and_c_and_ec() get the conditioned and unconditioned latent, and edited conditioning if we're doing cross-attention control - -""" -import re -from typing import Optional, Union - -from compel import Compel -from compel.prompt_parser import ( - Blend, - CrossAttentionControlSubstitute, - FlattenedPrompt, - Fragment, - PromptParser, - Conjunction, -) - -import invokeai.backend.util.logging as logger - -from invokeai.app.services.config import get_invokeai_config -from ..stable_diffusion import InvokeAIDiffuserComponent -from ..util import torch_dtype - -def get_uc_and_c_and_ec(prompt_string, - model: InvokeAIDiffuserComponent, - log_tokens=False, skip_normalize_legacy_blend=False): - # lazy-load any deferred textual inversions. - # this might take a couple of seconds the first time a textual inversion is used. - model.textual_inversion_manager.create_deferred_token_ids_for_any_trigger_terms(prompt_string) - - compel = Compel(tokenizer=model.tokenizer, - text_encoder=model.text_encoder, - textual_inversion_manager=model.textual_inversion_manager, - dtype_for_device_getter=torch_dtype, - truncate_long_prompts=False, - ) - - config = get_invokeai_config() - - # get rid of any newline characters - prompt_string = prompt_string.replace("\n", " ") - positive_prompt_string, negative_prompt_string = split_prompt_to_positive_and_negative(prompt_string) - - legacy_blend = try_parse_legacy_blend(positive_prompt_string, skip_normalize_legacy_blend) - positive_conjunction: Conjunction - if legacy_blend is not None: - positive_conjunction = legacy_blend - else: - positive_conjunction = Compel.parse_prompt_string(positive_prompt_string) - positive_prompt = positive_conjunction.prompts[0] - - negative_conjunction = Compel.parse_prompt_string(negative_prompt_string) - negative_prompt: FlattenedPrompt | Blend = negative_conjunction.prompts[0] - - tokens_count = get_max_token_count(model.tokenizer, positive_prompt) - if log_tokens or config.log_tokenization: - log_tokenization(positive_prompt, negative_prompt, tokenizer=model.tokenizer) - - c, options = compel.build_conditioning_tensor_for_prompt_object(positive_prompt) - uc, _ = compel.build_conditioning_tensor_for_prompt_object(negative_prompt) - [c, uc] = compel.pad_conditioning_tensors_to_same_length([c, uc]) - - ec = InvokeAIDiffuserComponent.ExtraConditioningInfo(tokens_count_including_eos_bos=tokens_count, - cross_attention_control_args=options.get( - 'cross_attention_control', None)) - return uc, c, ec - -def get_prompt_structure( - prompt_string, skip_normalize_legacy_blend: bool = False -) -> (Union[FlattenedPrompt, Blend], FlattenedPrompt): - ( - positive_prompt_string, - negative_prompt_string, - ) = split_prompt_to_positive_and_negative(prompt_string) - legacy_blend = try_parse_legacy_blend( - positive_prompt_string, skip_normalize_legacy_blend - ) - positive_prompt: Conjunction - if legacy_blend is not None: - positive_conjunction = legacy_blend - else: - positive_conjunction = Compel.parse_prompt_string(positive_prompt_string) - positive_prompt = positive_conjunction.prompts[0] - negative_conjunction = Compel.parse_prompt_string(negative_prompt_string) - negative_prompt: FlattenedPrompt|Blend = negative_conjunction.prompts[0] - - return positive_prompt, negative_prompt - -def get_max_token_count( - tokenizer, prompt: Union[FlattenedPrompt, Blend], truncate_if_too_long=False -) -> int: - if type(prompt) is Blend: - blend: Blend = prompt - return max( - [ - get_max_token_count(tokenizer, c, truncate_if_too_long) - for c in blend.prompts - ] - ) - else: - return len( - get_tokens_for_prompt_object(tokenizer, prompt, truncate_if_too_long) - ) - - -def get_tokens_for_prompt_object( - tokenizer, parsed_prompt: FlattenedPrompt, truncate_if_too_long=True -) -> [str]: - if type(parsed_prompt) is Blend: - raise ValueError( - "Blend is not supported here - you need to get tokens for each of its .children" - ) - - text_fragments = [ - x.text - if type(x) is Fragment - else ( - " ".join([f.text for f in x.original]) - if type(x) is CrossAttentionControlSubstitute - else str(x) - ) - for x in parsed_prompt.children - ] - text = " ".join(text_fragments) - tokens = tokenizer.tokenize(text) - if truncate_if_too_long: - max_tokens_length = tokenizer.model_max_length - 2 # typically 75 - tokens = tokens[0:max_tokens_length] - return tokens - - -def split_prompt_to_positive_and_negative(prompt_string_uncleaned: str): - unconditioned_words = "" - unconditional_regex = r"\[(.*?)\]" - unconditionals = re.findall(unconditional_regex, prompt_string_uncleaned) - if len(unconditionals) > 0: - unconditioned_words = " ".join(unconditionals) - - # Remove Unconditioned Words From Prompt - unconditional_regex_compile = re.compile(unconditional_regex) - clean_prompt = unconditional_regex_compile.sub(" ", prompt_string_uncleaned) - prompt_string_cleaned = re.sub(" +", " ", clean_prompt) - else: - prompt_string_cleaned = prompt_string_uncleaned - return prompt_string_cleaned, unconditioned_words - - -def log_tokenization( - positive_prompt: Union[Blend, FlattenedPrompt], - negative_prompt: Union[Blend, FlattenedPrompt], - tokenizer, -): - logger.info(f"[TOKENLOG] Parsed Prompt: {positive_prompt}") - logger.info(f"[TOKENLOG] Parsed Negative Prompt: {negative_prompt}") - - log_tokenization_for_prompt_object(positive_prompt, tokenizer) - log_tokenization_for_prompt_object( - negative_prompt, tokenizer, display_label_prefix="(negative prompt)" - ) - - -def log_tokenization_for_prompt_object( - p: Union[Blend, FlattenedPrompt], tokenizer, display_label_prefix=None -): - display_label_prefix = display_label_prefix or "" - if type(p) is Blend: - blend: Blend = p - for i, c in enumerate(blend.prompts): - log_tokenization_for_prompt_object( - c, - tokenizer, - display_label_prefix=f"{display_label_prefix}(blend part {i + 1}, weight={blend.weights[i]})", - ) - elif type(p) is FlattenedPrompt: - flattened_prompt: FlattenedPrompt = p - if flattened_prompt.wants_cross_attention_control: - original_fragments = [] - edited_fragments = [] - for f in flattened_prompt.children: - if type(f) is CrossAttentionControlSubstitute: - original_fragments += f.original - edited_fragments += f.edited - else: - original_fragments.append(f) - edited_fragments.append(f) - - original_text = " ".join([x.text for x in original_fragments]) - log_tokenization_for_text( - original_text, - tokenizer, - display_label=f"{display_label_prefix}(.swap originals)", - ) - edited_text = " ".join([x.text for x in edited_fragments]) - log_tokenization_for_text( - edited_text, - tokenizer, - display_label=f"{display_label_prefix}(.swap replacements)", - ) - else: - text = " ".join([x.text for x in flattened_prompt.children]) - log_tokenization_for_text( - text, tokenizer, display_label=display_label_prefix - ) - - -def log_tokenization_for_text(text, tokenizer, display_label=None, truncate_if_too_long=False): - """shows how the prompt is tokenized - # usually tokens have '' to indicate end-of-word, - # but for readability it has been replaced with ' ' - """ - tokens = tokenizer.tokenize(text) - tokenized = "" - discarded = "" - usedTokens = 0 - totalTokens = len(tokens) - - for i in range(0, totalTokens): - token = tokens[i].replace("", " ") - # alternate color - s = (usedTokens % 6) + 1 - if truncate_if_too_long and i >= tokenizer.model_max_length: - discarded = discarded + f"\x1b[0;3{s};40m{token}" - else: - tokenized = tokenized + f"\x1b[0;3{s};40m{token}" - usedTokens += 1 - - if usedTokens > 0: - logger.info(f'[TOKENLOG] Tokens {display_label or ""} ({usedTokens}):') - logger.debug(f"{tokenized}\x1b[0m") - - if discarded != "": - logger.info(f"[TOKENLOG] Tokens Discarded ({totalTokens - usedTokens}):") - logger.debug(f"{discarded}\x1b[0m") - -def try_parse_legacy_blend(text: str, skip_normalize: bool = False) -> Optional[Conjunction]: - weighted_subprompts = split_weighted_subprompts(text, skip_normalize=skip_normalize) - if len(weighted_subprompts) <= 1: - return None - strings = [x[0] for x in weighted_subprompts] - - pp = PromptParser() - parsed_conjunctions = [pp.parse_conjunction(x) for x in strings] - flattened_prompts = [] - weights = [] - for i, x in enumerate(parsed_conjunctions): - if len(x.prompts)>0: - flattened_prompts.append(x.prompts[0]) - weights.append(weighted_subprompts[i][1]) - return Conjunction([Blend(prompts=flattened_prompts, weights=weights, normalize_weights=not skip_normalize)]) - -def split_weighted_subprompts(text, skip_normalize=False) -> list: - """ - Legacy blend parsing. - - grabs all text up to the first occurrence of ':' - uses the grabbed text as a sub-prompt, and takes the value following ':' as weight - if ':' has no value defined, defaults to 1.0 - repeats until no text remaining - """ - prompt_parser = re.compile( - """ - (?P # capture group for 'prompt' - (?:\\\:|[^:])+ # match one or more non ':' characters or escaped colons '\:' - ) # end 'prompt' - (?: # non-capture group - :+ # match one or more ':' characters - (?P # capture group for 'weight' - -?\d+(?:\.\d+)? # match positive or negative integer or decimal number - )? # end weight capture group, make optional - \s* # strip spaces after weight - | # OR - $ # else, if no ':' then match end of line - ) # end non-capture group - """, - re.VERBOSE, - ) - parsed_prompts = [ - (match.group("prompt").replace("\\:", ":"), float(match.group("weight") or 1)) - for match in re.finditer(prompt_parser, text) - ] - if skip_normalize: - return parsed_prompts - weight_sum = sum(map(lambda x: x[1], parsed_prompts)) - if weight_sum == 0: - logger.warning( - "Subprompt weights add up to zero. Discarding and using even weights instead." - ) - equal_weight = 1 / max(len(parsed_prompts), 1) - return [(x[0], equal_weight) for x in parsed_prompts] - return [(x[0], x[1] / weight_sum) for x in parsed_prompts] diff --git a/invokeai/backend/restoration/base.py b/invokeai/backend/restoration/base.py index f6f01da17d..956f99cf16 100644 --- a/invokeai/backend/restoration/base.py +++ b/invokeai/backend/restoration/base.py @@ -5,7 +5,7 @@ class Restoration: pass def load_face_restore_models( - self, gfpgan_model_path="./models/gfpgan/GFPGANv1.4.pth" + self, gfpgan_model_path="./models/core/face_restoration/gfpgan/GFPGANv1.4.pth" ): # Load GFPGAN gfpgan = self.load_gfpgan(gfpgan_model_path) diff --git a/invokeai/backend/restoration/codeformer.py b/invokeai/backend/restoration/codeformer.py index b7073f8f8b..92fcd06e0b 100644 --- a/invokeai/backend/restoration/codeformer.py +++ b/invokeai/backend/restoration/codeformer.py @@ -6,7 +6,7 @@ import numpy as np import torch import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig pretrained_model_url = ( "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth" @@ -15,16 +15,16 @@ pretrained_model_url = ( class CodeFormerRestoration: def __init__( - self, codeformer_dir="models/codeformer", codeformer_model_path="codeformer.pth" + self, codeformer_dir="./models/core/face_restoration/codeformer", codeformer_model_path="codeformer.pth" ) -> None: - self.globals = get_invokeai_config() + self.globals = InvokeAIAppConfig.get_config() codeformer_dir = self.globals.root_dir / codeformer_dir self.model_path = codeformer_dir / codeformer_model_path self.codeformer_model_exists = self.model_path.exists() if not self.codeformer_model_exists: - logger.error("NOT FOUND: CodeFormer model not found at " + self.model_path) + logger.error(f"NOT FOUND: CodeFormer model not found at {self.model_path}") sys.path.append(os.path.abspath(codeformer_dir)) def process(self, image, strength, device, seed=None, fidelity=0.75): @@ -71,7 +71,7 @@ class CodeFormerRestoration: upscale_factor=1, use_parse=True, device=device, - model_rootpath = self.globals.root_dir / "gfpgan" / "weights" + model_rootpath = self.globals.model_path / 'core/face_restoration/gfpgan/weights' ) face_helper.clean_all() face_helper.read_image(bgr_image_array) diff --git a/invokeai/backend/restoration/gfpgan.py b/invokeai/backend/restoration/gfpgan.py index 063feaa89a..927d026c0c 100644 --- a/invokeai/backend/restoration/gfpgan.py +++ b/invokeai/backend/restoration/gfpgan.py @@ -7,18 +7,18 @@ import torch from PIL import Image import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig class GFPGAN: def __init__(self, gfpgan_model_path="models/gfpgan/GFPGANv1.4.pth") -> None: - self.globals = get_invokeai_config() + self.globals = InvokeAIAppConfig.get_config() if not os.path.isabs(gfpgan_model_path): gfpgan_model_path = self.globals.root_dir / gfpgan_model_path self.model_path = gfpgan_model_path self.gfpgan_model_exists = os.path.isfile(self.model_path) if not self.gfpgan_model_exists: - logger.error("NOT FOUND: GFPGAN model not found at " + self.model_path) + logger.error(f"NOT FOUND: GFPGAN model not found at {self.model_path}") return None def model_exists(self): diff --git a/invokeai/backend/restoration/realesrgan.py b/invokeai/backend/restoration/realesrgan.py index c6c6d2d3b4..1f29ceadba 100644 --- a/invokeai/backend/restoration/realesrgan.py +++ b/invokeai/backend/restoration/realesrgan.py @@ -6,8 +6,8 @@ from PIL import Image from PIL.Image import Image as ImageType import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config -config = get_invokeai_config() +from invokeai.app.services.config import InvokeAIAppConfig +config = InvokeAIAppConfig.get_config() class ESRGAN: def __init__(self, bg_tile_size=400) -> None: @@ -30,8 +30,8 @@ class ESRGAN: upscale=4, act_type="prelu", ) - model_path = config.root_dir / "models/realesrgan/realesr-general-x4v3.pth" - wdn_model_path = config.root_dir / "models/realesrgan/realesr-general-wdn-x4v3.pth" + model_path = config.models_path / "core/upscaling/realesrgan/realesr-general-x4v3.pth" + wdn_model_path = config.models_path / "core/upscaling/realesrgan/realesr-general-wdn-x4v3.pth" scale = 4 bg_upsampler = RealESRGANer( diff --git a/invokeai/backend/safety_checker.py b/invokeai/backend/safety_checker.py index 55e8eb1987..0a943ad7b6 100644 --- a/invokeai/backend/safety_checker.py +++ b/invokeai/backend/safety_checker.py @@ -15,9 +15,11 @@ from transformers import AutoFeatureExtractor import invokeai.assets.web as web_assets import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig from .util import CPU_DEVICE +config = InvokeAIAppConfig.get_config() + class SafetyChecker(object): CAUTION_IMG = "caution.png" @@ -26,21 +28,12 @@ class SafetyChecker(object): caution = Image.open(path) self.caution_img = caution.resize((caution.width // 2, caution.height // 2)) self.device = device - config = get_invokeai_config() try: - safety_model_id = "CompVis/stable-diffusion-safety-checker" - safety_model_path = config.cache_dir - self.safety_checker = StableDiffusionSafetyChecker.from_pretrained( - safety_model_id, - local_files_only=True, - cache_dir=safety_model_path, - ) - self.safety_feature_extractor = AutoFeatureExtractor.from_pretrained( - safety_model_id, - local_files_only=True, - cache_dir=safety_model_path, - ) + safety_model_id = config.models_path / 'core/convert/stable-diffusion-safety-checker' + feature_extractor_id = config.models_path / 'core/convert/stable-diffusion-safety-checker-extractor' + self.safety_checker = StableDiffusionSafetyChecker.from_pretrained(safety_model_id) + self.safety_feature_extractor = AutoFeatureExtractor.from_pretrained(feature_extractor_id) except Exception: logger.error( "An error was encountered while installing the safety checker:" diff --git a/invokeai/backend/stable_diffusion/__init__.py b/invokeai/backend/stable_diffusion/__init__.py index 55333d3589..37024ccace 100644 --- a/invokeai/backend/stable_diffusion/__init__.py +++ b/invokeai/backend/stable_diffusion/__init__.py @@ -1,7 +1,6 @@ """ Initialization file for the invokeai.backend.stable_diffusion package """ -from .concepts_lib import HuggingFaceConceptsLibrary from .diffusers_pipeline import ( ConditioningData, PipelineIntermediateState, @@ -10,4 +9,3 @@ from .diffusers_pipeline import ( from .diffusion import InvokeAIDiffuserComponent from .diffusion.cross_attention_map_saving import AttentionMapSaver from .diffusion.shared_invokeai_diffusion import PostprocessingSettings -from .textual_inversion_manager import TextualInversionManager diff --git a/invokeai/backend/stable_diffusion/concepts_lib.py b/invokeai/backend/stable_diffusion/concepts_lib.py deleted file mode 100644 index beb884b012..0000000000 --- a/invokeai/backend/stable_diffusion/concepts_lib.py +++ /dev/null @@ -1,274 +0,0 @@ -""" -Query and install embeddings from the HuggingFace SD Concepts Library -at https://huggingface.co/sd-concepts-library. - -The interface is through the Concepts() object. -""" -import os -import re -from typing import Callable -from urllib import error as ul_error -from urllib import request - -from huggingface_hub import ( - HfApi, - HfFolder, - ModelFilter, - hf_hub_url, -) - -import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config - -class HuggingFaceConceptsLibrary(object): - def __init__(self, root=None): - """ - Initialize the Concepts object. May optionally pass a root directory. - """ - self.config = get_invokeai_config() - self.root = root or self.config.root - self.hf_api = HfApi() - self.local_concepts = dict() - self.concept_list = None - self.concepts_loaded = dict() - self.triggers = dict() # concept name to trigger phrase - self.concept_names = dict() # trigger phrase to concept name - self.match_trigger = re.compile( - "(<[\w\- >]+>)" - ) # trigger is slightly less restrictive than HF concept name - self.match_concept = re.compile( - "<([\w\-]+)>" - ) # HF concept name can only contain A-Za-z0-9_- - - def list_concepts(self) -> list: - """ - Return a list of all the concepts by name, without the 'sd-concepts-library' part. - Also adds local concepts in invokeai/embeddings folder. - """ - local_concepts_now = self.get_local_concepts( - os.path.join(self.root, "embeddings") - ) - local_concepts_to_add = set(local_concepts_now).difference( - set(self.local_concepts) - ) - self.local_concepts.update(local_concepts_now) - - if self.concept_list is not None: - if local_concepts_to_add: - self.concept_list.extend(list(local_concepts_to_add)) - return self.concept_list - return self.concept_list - elif self.config.internet_available is True: - try: - models = self.hf_api.list_models( - filter=ModelFilter(model_name="sd-concepts-library/") - ) - self.concept_list = [a.id.split("/")[1] for a in models] - # when init, add all in dir. when not init, add only concepts added between init and now - self.concept_list.extend(list(local_concepts_to_add)) - except Exception as e: - logger.warning( - f"Hugging Face textual inversion concepts libraries could not be loaded. The error was {str(e)}." - ) - logger.warning( - "You may load .bin and .pt file(s) manually using the --embedding_directory argument." - ) - return self.concept_list - else: - return self.concept_list - - def get_concept_model_path(self, concept_name: str) -> str: - """ - Returns the path to the 'learned_embeds.bin' file in - the named concept. Returns None if invalid or cannot - be downloaded. - """ - if not concept_name in self.list_concepts(): - logger.warning( - f"{concept_name} is not a local embedding trigger, nor is it a HuggingFace concept. Generation will continue without the concept." - ) - return None - return self.get_concept_file(concept_name.lower(), "learned_embeds.bin") - - def concept_to_trigger(self, concept_name: str) -> str: - """ - Given a concept name returns its trigger by looking in the - "token_identifier.txt" file. - """ - if concept_name in self.triggers: - return self.triggers[concept_name] - elif self.concept_is_local(concept_name): - trigger = f"<{concept_name}>" - self.triggers[concept_name] = trigger - self.concept_names[trigger] = concept_name - return trigger - - file = self.get_concept_file( - concept_name, "token_identifier.txt", local_only=True - ) - if not file: - return None - with open(file, "r") as f: - trigger = f.readline() - trigger = trigger.strip() - self.triggers[concept_name] = trigger - self.concept_names[trigger] = concept_name - return trigger - - def trigger_to_concept(self, trigger: str) -> str: - """ - Given a trigger phrase, maps it to the concept library name. - Only works if concept_to_trigger() has previously been called - on this library. There needs to be a persistent database for - this. - """ - concept = self.concept_names.get(trigger, None) - return f"<{concept}>" if concept else f"{trigger}" - - def replace_triggers_with_concepts(self, prompt: str) -> str: - """ - Given a prompt string that contains tags, replace these - tags with the concept name. The reason for this is so that the - concept names get stored in the prompt metadata. There is no - controlling of colliding triggers in the SD library, so it is - better to store the concept name (unique) than the concept trigger - (not necessarily unique!) - """ - if not prompt: - return prompt - triggers = self.match_trigger.findall(prompt) - if not triggers: - return prompt - - def do_replace(match) -> str: - return self.trigger_to_concept(match.group(1)) or f"<{match.group(1)}>" - - return self.match_trigger.sub(do_replace, prompt) - - def replace_concepts_with_triggers( - self, - prompt: str, - load_concepts_callback: Callable[[list], any], - excluded_tokens: list[str], - ) -> str: - """ - Given a prompt string that contains `` tags, replace - these tags with the appropriate trigger. - - If any `` tags are found, `load_concepts_callback()` is called with a list - of `concepts_name` strings. - - `excluded_tokens` are any tokens that should not be replaced, typically because they - are trigger tokens from a locally-loaded embedding. - """ - concepts = self.match_concept.findall(prompt) - if not concepts: - return prompt - load_concepts_callback(concepts) - - def do_replace(match) -> str: - if excluded_tokens and f"<{match.group(1)}>" in excluded_tokens: - return f"<{match.group(1)}>" - return self.concept_to_trigger(match.group(1)) or f"<{match.group(1)}>" - - return self.match_concept.sub(do_replace, prompt) - - def get_concept_file( - self, - concept_name: str, - file_name: str = "learned_embeds.bin", - local_only: bool = False, - ) -> str: - if not ( - self.concept_is_downloaded(concept_name) - or self.concept_is_local(concept_name) - or local_only - ): - self.download_concept(concept_name) - - # get local path in invokeai/embeddings if local concept - if self.concept_is_local(concept_name): - concept_path = self._concept_local_path(concept_name) - path = concept_path - else: - concept_path = self._concept_path(concept_name) - path = os.path.join(concept_path, file_name) - return path if os.path.exists(path) else None - - def concept_is_local(self, concept_name) -> bool: - return concept_name in self.local_concepts - - def concept_is_downloaded(self, concept_name) -> bool: - concept_directory = self._concept_path(concept_name) - return os.path.exists(concept_directory) - - def download_concept(self, concept_name) -> bool: - repo_id = self._concept_id(concept_name) - dest = self._concept_path(concept_name) - - access_token = HfFolder.get_token() - header = [("Authorization", f"Bearer {access_token}")] if access_token else [] - opener = request.build_opener() - opener.addheaders = header - request.install_opener(opener) - - os.makedirs(dest, exist_ok=True) - succeeded = True - - bytes = 0 - - def tally_download_size(chunk, size, total): - nonlocal bytes - if chunk == 0: - bytes += total - - logger.info(f"Downloading {repo_id}...", end="") - try: - for file in ( - "README.md", - "learned_embeds.bin", - "token_identifier.txt", - "type_of_concept.txt", - ): - url = hf_hub_url(repo_id, file) - request.urlretrieve( - url, os.path.join(dest, file), reporthook=tally_download_size - ) - except ul_error.HTTPError as e: - if e.code == 404: - logger.warning( - f"Concept {concept_name} is not known to the Hugging Face library. Generation will continue without the concept." - ) - else: - logger.warning( - f"Failed to download {concept_name}/{file} ({str(e)}. Generation will continue without the concept.)" - ) - os.rmdir(dest) - return False - except ul_error.URLError as e: - logger.error( - f"an error occurred while downloading {concept_name}: {str(e)}. This may reflect a network issue. Generation will continue without the concept." - ) - os.rmdir(dest) - return False - logger.info("...{:.2f}Kb".format(bytes / 1024)) - return succeeded - - def _concept_id(self, concept_name: str) -> str: - return f"sd-concepts-library/{concept_name}" - - def _concept_path(self, concept_name: str) -> str: - return os.path.join(self.root, "models", "sd-concepts-library", concept_name) - - def _concept_local_path(self, concept_name: str) -> str: - filename = self.local_concepts[concept_name] - return os.path.join(self.root, "embeddings", filename) - - def get_local_concepts(self, loc_dir: str): - locs_dic = dict() - if os.path.isdir(loc_dir): - for file in os.listdir(loc_dir): - f = os.path.splitext(file) - if f[1] == ".bin" or f[1] == ".pt": - locs_dic[f[0]] = file - return locs_dic diff --git a/invokeai/backend/stable_diffusion/diffusers_pipeline.py b/invokeai/backend/stable_diffusion/diffusers_pipeline.py index ec2902e4d6..8493b4286f 100644 --- a/invokeai/backend/stable_diffusion/diffusers_pipeline.py +++ b/invokeai/backend/stable_diffusion/diffusers_pipeline.py @@ -16,14 +16,13 @@ from accelerate.utils import set_seed import psutil import torch import torchvision.transforms as T -from compel import EmbeddingsProvider from diffusers.models import AutoencoderKL, UNet2DConditionModel from diffusers.models.controlnet import ControlNetModel, ControlNetOutput from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion import ( StableDiffusionPipeline, ) -from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_controlnet import MultiControlNetModel +from diffusers.pipelines.controlnet import MultiControlNetModel from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img import ( StableDiffusionImg2ImgPipeline, @@ -40,7 +39,7 @@ from torchvision.transforms.functional import resize as tv_resize from transformers import CLIPFeatureExtractor, CLIPTextModel, CLIPTokenizer from typing_extensions import ParamSpec -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig from ..util import CPU_DEVICE, normalize_device from .diffusion import ( AttentionMapSaver, @@ -48,7 +47,6 @@ from .diffusion import ( PostprocessingSettings, ) from .offloading import FullyLoadedModelGroup, LazilyLoadedModelGroup, ModelGroup -from .textual_inversion_manager import TextualInversionManager @dataclass class PipelineIntermediateState: @@ -75,10 +73,10 @@ class AddsMaskLatents: initial_image_latents: torch.Tensor def __call__( - self, latents: torch.Tensor, t: torch.Tensor, text_embeddings: torch.Tensor + self, latents: torch.Tensor, t: torch.Tensor, text_embeddings: torch.Tensor, **kwargs, ) -> torch.Tensor: model_input = self.add_mask_channels(latents) - return self.forward(model_input, t, text_embeddings) + return self.forward(model_input, t, text_embeddings, **kwargs) def add_mask_channels(self, latents): batch_size = latents.size(0) @@ -217,16 +215,18 @@ class GeneratorToCallbackinator(Generic[ParamType, ReturnType, CallbackType]): @dataclass class ControlNetData: model: ControlNetModel = Field(default=None) - image_tensor: torch.Tensor= Field(default=None) - weight: float = Field(default=1.0) + image_tensor: torch.Tensor = Field(default=None) + weight: Union[float, List[float]] = Field(default=1.0) begin_step_percent: float = Field(default=0.0) end_step_percent: float = Field(default=1.0) + control_mode: str = Field(default="balanced") + @dataclass(frozen=True) class ConditioningData: unconditioned_embeddings: torch.Tensor text_embeddings: torch.Tensor - guidance_scale: float + guidance_scale: Union[float, List[float]] """ Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). `guidance_scale` is defined as `w` of equation 2. of [Imagen Paper](https://arxiv.org/pdf/2205.11487.pdf). @@ -317,6 +317,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): requires_safety_checker: bool = False, precision: str = "float32", control_model: ControlNetModel = None, + execution_device: Optional[torch.device] = None, ): super().__init__( vae, @@ -341,22 +342,10 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): # control_model=control_model, ) self.invokeai_diffuser = InvokeAIDiffuserComponent( - self.unet, self._unet_forward, is_running_diffusers=True - ) - use_full_precision = precision == "float32" or precision == "autocast" - self.textual_inversion_manager = TextualInversionManager( - tokenizer=self.tokenizer, - text_encoder=self.text_encoder, - full_precision=use_full_precision, - ) - # InvokeAI's interface for text embeddings and whatnot - self.embeddings_provider = EmbeddingsProvider( - tokenizer=self.tokenizer, - text_encoder=self.text_encoder, - textual_inversion_manager=self.textual_inversion_manager, + self.unet, self._unet_forward ) - self._model_group = FullyLoadedModelGroup(self.unet.device) + self._model_group = FullyLoadedModelGroup(execution_device or self.unet.device) self._model_group.install(*self._submodels) self.control_model = control_model @@ -364,7 +353,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): """ if xformers is available, use it, otherwise use sliced attention. """ - config = get_invokeai_config() + config = InvokeAIAppConfig.get_config() if ( torch.cuda.is_available() and is_xformers_available() @@ -404,50 +393,6 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): else: self.disable_attention_slicing() - def enable_offload_submodels(self, device: torch.device): - """ - Offload each submodel when it's not in use. - - Useful for low-vRAM situations where the size of the model in memory is a big chunk of - the total available resource, and you want to free up as much for inference as possible. - - This requires more moving parts and may add some delay as the U-Net is swapped out for the - VAE and vice-versa. - """ - models = self._submodels - if self._model_group is not None: - self._model_group.uninstall(*models) - group = LazilyLoadedModelGroup(device) - group.install(*models) - self._model_group = group - - def disable_offload_submodels(self): - """ - Leave all submodels loaded. - - Appropriate for cases where the size of the model in memory is small compared to the memory - required for inference. Avoids the delay and complexity of shuffling the submodels to and - from the GPU. - """ - models = self._submodels - if self._model_group is not None: - self._model_group.uninstall(*models) - group = FullyLoadedModelGroup(self._model_group.execution_device) - group.install(*models) - self._model_group = group - - def offload_all(self): - """Offload all this pipeline's models to CPU.""" - self._model_group.offload_current() - - def ready(self): - """ - Ready this pipeline's models. - - i.e. preload them to the GPU if appropriate. - """ - self._model_group.ready() - def to(self, torch_device: Optional[Union[str, torch.device]] = None, silence_dtype_warnings=False): # overridden method; types match the superclass. if torch_device is None: @@ -656,40 +601,68 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): # TODO: should this scaling happen here or inside self._unet_forward? # i.e. before or after passing it to InvokeAIDiffuserComponent - latent_model_input = self.scheduler.scale_model_input(latents, timestep) + unet_latent_input = self.scheduler.scale_model_input(latents, timestep) # default is no controlnet, so set controlnet processing output to None down_block_res_samples, mid_block_res_sample = None, None if control_data is not None: - if conditioning_data.guidance_scale > 1.0: - # expand the latents input to control model if doing classifier free guidance - # (which I think for now is always true, there is conditional elsewhere that stops execution if - # classifier_free_guidance is <= 1.0 ?) - latent_control_input = torch.cat([latent_model_input] * 2) - else: - latent_control_input = latent_model_input # control_data should be type List[ControlNetData] # this loop covers both ControlNet (one ControlNetData in list) # and MultiControlNet (multiple ControlNetData in list) for i, control_datum in enumerate(control_data): - # print("controlnet", i, "==>", type(control_datum)) + control_mode = control_datum.control_mode + # soft_injection and cfg_injection are the two ControlNet control_mode booleans + # that are combined at higher level to make control_mode enum + # soft_injection determines whether to do per-layer re-weighting adjustment (if True) + # or default weighting (if False) + soft_injection = (control_mode == "more_prompt" or control_mode == "more_control") + # cfg_injection = determines whether to apply ControlNet to only the conditional (if True) + # or the default both conditional and unconditional (if False) + cfg_injection = (control_mode == "more_control" or control_mode == "unbalanced") + first_control_step = math.floor(control_datum.begin_step_percent * total_step_count) last_control_step = math.ceil(control_datum.end_step_percent * total_step_count) # only apply controlnet if current step is within the controlnet's begin/end step range if step_index >= first_control_step and step_index <= last_control_step: - # print("running controlnet", i, "for step", step_index) + + if cfg_injection: + control_latent_input = unet_latent_input + else: + # expand the latents input to control model if doing classifier free guidance + # (which I think for now is always true, there is conditional elsewhere that stops execution if + # classifier_free_guidance is <= 1.0 ?) + control_latent_input = torch.cat([unet_latent_input] * 2) + + if cfg_injection: # only applying ControlNet to conditional instead of in unconditioned + encoder_hidden_states = torch.cat([conditioning_data.unconditioned_embeddings]) + else: + encoder_hidden_states = torch.cat([conditioning_data.unconditioned_embeddings, + conditioning_data.text_embeddings]) + if isinstance(control_datum.weight, list): + # if controlnet has multiple weights, use the weight for the current step + controlnet_weight = control_datum.weight[step_index] + else: + # if controlnet has a single weight, use it for all steps + controlnet_weight = control_datum.weight + + # controlnet(s) inference down_samples, mid_sample = control_datum.model( - sample=latent_control_input, + sample=control_latent_input, timestep=timestep, - encoder_hidden_states=torch.cat([conditioning_data.unconditioned_embeddings, - conditioning_data.text_embeddings]), + encoder_hidden_states=encoder_hidden_states, controlnet_cond=control_datum.image_tensor, - conditioning_scale=control_datum.weight, - # cross_attention_kwargs, - guess_mode=False, + conditioning_scale=controlnet_weight, # controlnet specific, NOT the guidance scale + guess_mode=soft_injection, # this is still called guess_mode in diffusers ControlNetModel return_dict=False, ) + if cfg_injection: + # Inferred ControlNet only for the conditional batch. + # To apply the output of ControlNet to both the unconditional and conditional batches, + # add 0 to the unconditional batch to keep it unchanged. + down_samples = [torch.cat([torch.zeros_like(d), d]) for d in down_samples] + mid_sample = torch.cat([torch.zeros_like(mid_sample), mid_sample]) + if down_block_res_samples is None and mid_block_res_sample is None: down_block_res_samples, mid_block_res_sample = down_samples, mid_sample else: @@ -702,11 +675,11 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): # predict the noise residual noise_pred = self.invokeai_diffuser.do_diffusion_step( - latent_model_input, - t, - conditioning_data.unconditioned_embeddings, - conditioning_data.text_embeddings, - conditioning_data.guidance_scale, + x=unet_latent_input, + sigma=t, + unconditioning=conditioning_data.unconditioned_embeddings, + conditioning=conditioning_data.text_embeddings, + unconditional_guidance_scale=conditioning_data.guidance_scale, step_index=step_index, total_step_count=total_step_count, down_block_additional_residuals=down_block_res_samples, # from controlnet(s) @@ -983,25 +956,6 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): device = self._model_group.device_for(self.safety_checker) return super().run_safety_checker(image, device, dtype) - @torch.inference_mode() - def get_learned_conditioning( - self, c: List[List[str]], *, return_tokens=True, fragment_weights=None - ): - """ - Compatibility function for invokeai.models.diffusion.ddpm.LatentDiffusion. - """ - return self.embeddings_provider.get_embeddings_for_weighted_prompt_fragments( - text_batch=c, - fragment_weights_batch=fragment_weights, - should_return_tokens=return_tokens, - device=self._model_group.device_for(self.unet), - ) - - @property - def channels(self) -> int: - """Compatible with DiffusionWrapper""" - return self.unet.config.in_channels - def decode_latents(self, latents): # Explicit call to get the vae loaded, since `decode` isn't the forward method. self._model_group.load(self.vae) @@ -1018,8 +972,8 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): # Copied from diffusers pipeline_stable_diffusion_controlnet.py # Returns torch.Tensor of shape (batch_size, 3, height, width) + @staticmethod def prepare_control_image( - self, image, # FIXME: need to fix hardwiring of width and height, change to basing on latents dimensions? # latents, @@ -1030,6 +984,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): device="cuda", dtype=torch.float16, do_classifier_free_guidance=True, + control_mode="balanced" ): if not isinstance(image, torch.Tensor): @@ -1060,6 +1015,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline): repeat_by = num_images_per_prompt image = image.repeat_interleave(repeat_by, dim=0) image = image.to(device=device, dtype=dtype) - if do_classifier_free_guidance: + cfg_injection = (control_mode == "more_control" or control_mode == "unbalanced") + if do_classifier_free_guidance and not cfg_injection: image = torch.cat([image] * 2) return image diff --git a/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py b/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py index d05565c506..f3b09f6a9f 100644 --- a/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py +++ b/invokeai/backend/stable_diffusion/diffusion/shared_invokeai_diffusion.py @@ -1,7 +1,7 @@ from contextlib import contextmanager from dataclasses import dataclass from math import ceil -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Callable, Dict, Optional, Union, List import numpy as np import torch @@ -10,7 +10,7 @@ from diffusers.models.attention_processor import AttentionProcessor from typing_extensions import TypeAlias import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig from .cross_attention_control import ( Arguments, @@ -18,7 +18,6 @@ from .cross_attention_control import ( CrossAttentionType, SwapCrossAttnContext, get_cross_attention_modules, - restore_default_cross_attention, setup_cross_attention_control_attention_processors, ) from .cross_attention_map_saving import AttentionMapSaver @@ -66,16 +65,14 @@ class InvokeAIDiffuserComponent: self, model, model_forward_callback: ModelForwardCallback, - is_running_diffusers: bool = False, ): """ :param model: the unet model to pass through to cross attention control :param model_forward_callback: a lambda with arguments (x, sigma, conditioning_to_apply). will be called repeatedly. most likely, this should simply call model.forward(x, sigma, conditioning) """ - config = get_invokeai_config() + config = InvokeAIAppConfig.get_config() self.conditioning = None self.model = model - self.is_running_diffusers = is_running_diffusers self.model_forward_callback = model_forward_callback self.cross_attention_control_context = None self.sequential_guidance = config.sequential_guidance @@ -112,35 +109,6 @@ class InvokeAIDiffuserComponent: # TODO resuscitate attention map saving # self.remove_attention_map_saving() - def override_cross_attention( - self, conditioning: ExtraConditioningInfo, step_count: int - ) -> Dict[str, AttentionProcessor]: - """ - setup cross attention .swap control. for diffusers this replaces the attention processor, so - the previous attention processor is returned so that the caller can restore it later. - """ - self.conditioning = conditioning - self.cross_attention_control_context = Context( - arguments=self.conditioning.cross_attention_control_args, - step_count=step_count, - ) - return override_cross_attention( - self.model, - self.cross_attention_control_context, - is_running_diffusers=self.is_running_diffusers, - ) - - def restore_default_cross_attention( - self, restore_attention_processor: Optional["AttentionProcessor"] = None - ): - self.conditioning = None - self.cross_attention_control_context = None - restore_default_cross_attention( - self.model, - is_running_diffusers=self.is_running_diffusers, - restore_attention_processor=restore_attention_processor, - ) - def setup_attention_map_saving(self, saver: AttentionMapSaver): def callback(slice, dim, offset, slice_size, key): if dim is not None: @@ -178,7 +146,8 @@ class InvokeAIDiffuserComponent: sigma: torch.Tensor, unconditioning: Union[torch.Tensor, dict], conditioning: Union[torch.Tensor, dict], - unconditional_guidance_scale: float, + # unconditional_guidance_scale: float, + unconditional_guidance_scale: Union[float, List[float]], step_index: Optional[int] = None, total_step_count: Optional[int] = None, **kwargs, @@ -193,12 +162,15 @@ class InvokeAIDiffuserComponent: :return: the new latents after applying the model to x using unscaled unconditioning and CFG-scaled conditioning. """ + if isinstance(unconditional_guidance_scale, list): + guidance_scale = unconditional_guidance_scale[step_index] + else: + guidance_scale = unconditional_guidance_scale + cross_attention_control_types_to_do = [] context: Context = self.cross_attention_control_context if self.cross_attention_control_context is not None: - percent_through = self.calculate_percent_through( - sigma, step_index, total_step_count - ) + percent_through = step_index / total_step_count cross_attention_control_types_to_do = ( context.get_active_cross_attention_control_types_for_step( percent_through @@ -241,7 +213,8 @@ class InvokeAIDiffuserComponent: ) combined_next_x = self._combine( - unconditioned_next_x, conditioned_next_x, unconditional_guidance_scale + # unconditioned_next_x, conditioned_next_x, unconditional_guidance_scale + unconditioned_next_x, conditioned_next_x, guidance_scale ) return combined_next_x @@ -255,9 +228,7 @@ class InvokeAIDiffuserComponent: total_step_count, ) -> torch.Tensor: if postprocessing_settings is not None: - percent_through = self.calculate_percent_through( - sigma, step_index, total_step_count - ) + percent_through = step_index / total_step_count latents = self.apply_threshold( postprocessing_settings, latents, percent_through ) @@ -266,22 +237,6 @@ class InvokeAIDiffuserComponent: ) return latents - def calculate_percent_through(self, sigma, step_index, total_step_count): - if step_index is not None and total_step_count is not None: - # 🧨diffusers codepath - percent_through = ( - step_index / total_step_count - ) # will never reach 1.0 - this is deliberate - else: - # legacy compvis codepath - # TODO remove when compvis codepath support is dropped - if step_index is None and sigma is None: - raise ValueError( - "Either step_index or sigma is required when doing cross attention control, but both are None." - ) - percent_through = self.estimate_percent_through(step_index, sigma) - return percent_through - # methods below are called from do_diffusion_step and should be considered private to this class. def _apply_standard_conditioning(self, x, sigma, unconditioning, conditioning, **kwargs): @@ -314,6 +269,7 @@ class InvokeAIDiffuserComponent: conditioned_next_x = conditioned_next_x.clone() return unconditioned_next_x, conditioned_next_x + # TODO: looks unused def _apply_hybrid_conditioning(self, x, sigma, unconditioning, conditioning, **kwargs): assert isinstance(conditioning, dict) assert isinstance(unconditioning, dict) @@ -341,34 +297,6 @@ class InvokeAIDiffuserComponent: conditioning, cross_attention_control_types_to_do, **kwargs, - ): - if self.is_running_diffusers: - return self._apply_cross_attention_controlled_conditioning__diffusers( - x, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, - ) - else: - return self._apply_cross_attention_controlled_conditioning__compvis( - x, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, - ) - - def _apply_cross_attention_controlled_conditioning__diffusers( - self, - x: torch.Tensor, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, ): context: Context = self.cross_attention_control_context @@ -400,54 +328,6 @@ class InvokeAIDiffuserComponent: ) return unconditioned_next_x, conditioned_next_x - def _apply_cross_attention_controlled_conditioning__compvis( - self, - x: torch.Tensor, - sigma, - unconditioning, - conditioning, - cross_attention_control_types_to_do, - **kwargs, - ): - # print('pct', percent_through, ': doing cross attention control on', cross_attention_control_types_to_do) - # slower non-batched path (20% slower on mac MPS) - # We are only interested in using attention maps for conditioned_next_x, but batching them with generation of - # unconditioned_next_x causes attention maps to *also* be saved for the unconditioned_next_x. - # This messes app their application later, due to mismatched shape of dim 0 (seems to be 16 for batched vs. 8) - # (For the batched invocation the `wrangler` function gets attention tensor with shape[0]=16, - # representing batched uncond + cond, but then when it comes to applying the saved attention, the - # wrangler gets an attention tensor which only has shape[0]=8, representing just self.edited_conditionings.) - # todo: give CrossAttentionControl's `wrangler` function more info so it can work with a batched call as well. - context: Context = self.cross_attention_control_context - - try: - unconditioned_next_x = self.model_forward_callback(x, sigma, unconditioning, **kwargs) - - # process x using the original prompt, saving the attention maps - # print("saving attention maps for", cross_attention_control_types_to_do) - for ca_type in cross_attention_control_types_to_do: - context.request_save_attention_maps(ca_type) - _ = self.model_forward_callback(x, sigma, conditioning, **kwargs,) - context.clear_requests(cleanup=False) - - # process x again, using the saved attention maps to control where self.edited_conditioning will be applied - # print("applying saved attention maps for", cross_attention_control_types_to_do) - for ca_type in cross_attention_control_types_to_do: - context.request_apply_saved_attention_maps(ca_type) - edited_conditioning = ( - self.conditioning.cross_attention_control_args.edited_conditioning - ) - conditioned_next_x = self.model_forward_callback( - x, sigma, edited_conditioning, **kwargs, - ) - context.clear_requests(cleanup=True) - - except: - context.clear_requests(cleanup=True) - raise - - return unconditioned_next_x, conditioned_next_x - def _combine(self, unconditioned_next_x, conditioned_next_x, guidance_scale): # to scale how much effect conditioning has, calculate the changes it does and then scale that scaled_delta = (conditioned_next_x - unconditioned_next_x) * guidance_scale @@ -495,7 +375,7 @@ class InvokeAIDiffuserComponent: logger.debug( f"min, mean, max = {minval:.3f}, {mean:.3f}, {maxval:.3f}\tstd={std}" ) - logger.debug( + logger.debug( f"{outside / latents.numel() * 100:.2f}% values outside threshold" ) diff --git a/invokeai/backend/stable_diffusion/offloading.py b/invokeai/backend/stable_diffusion/offloading.py index 871f994732..5fc3f765ae 100644 --- a/invokeai/backend/stable_diffusion/offloading.py +++ b/invokeai/backend/stable_diffusion/offloading.py @@ -157,7 +157,7 @@ class LazilyLoadedModelGroup(ModelGroup): def offload_current(self): module = self._current_model_ref() if module is not NO_MODEL: - module.to(device=OFFLOAD_DEVICE) + module.to(OFFLOAD_DEVICE) self.clear_current_model() def _load(self, module: torch.nn.Module) -> torch.nn.Module: @@ -228,7 +228,7 @@ class FullyLoadedModelGroup(ModelGroup): def install(self, *models: torch.nn.Module): for model in models: self._models.add(model) - model.to(device=self.execution_device) + model.to(self.execution_device) def uninstall(self, *models: torch.nn.Module): for model in models: @@ -238,11 +238,11 @@ class FullyLoadedModelGroup(ModelGroup): self.uninstall(*self._models) def load(self, model): - model.to(device=self.execution_device) + model.to(self.execution_device) def offload_current(self): for model in self._models: - model.to(device=OFFLOAD_DEVICE) + model.to(OFFLOAD_DEVICE) def ready(self): for model in self._models: @@ -252,7 +252,7 @@ class FullyLoadedModelGroup(ModelGroup): self.execution_device = device for model in self._models: if model.device != OFFLOAD_DEVICE: - model.to(device=device) + model.to(device) def device_for(self, model): if model not in self: diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py index 08f85cf559..77c45d5eb8 100644 --- a/invokeai/backend/stable_diffusion/schedulers/schedulers.py +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -1,13 +1,14 @@ from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, KDPM2DiscreteScheduler, \ KDPM2AncestralDiscreteScheduler, EulerDiscreteScheduler, EulerAncestralDiscreteScheduler, \ HeunDiscreteScheduler, LMSDiscreteScheduler, PNDMScheduler, UniPCMultistepScheduler, \ - DPMSolverSinglestepScheduler, DEISMultistepScheduler, DDPMScheduler + DPMSolverSinglestepScheduler, DEISMultistepScheduler, DDPMScheduler, DPMSolverSDEScheduler SCHEDULER_MAP = dict( ddim=(DDIMScheduler, dict()), ddpm=(DDPMScheduler, dict()), deis=(DEISMultistepScheduler, dict()), - lms=(LMSDiscreteScheduler, dict()), + lms=(LMSDiscreteScheduler, dict(use_karras_sigmas=False)), + lms_k=(LMSDiscreteScheduler, dict(use_karras_sigmas=True)), pndm=(PNDMScheduler, dict()), heun=(HeunDiscreteScheduler, dict(use_karras_sigmas=False)), heun_k=(HeunDiscreteScheduler, dict(use_karras_sigmas=True)), @@ -16,8 +17,13 @@ SCHEDULER_MAP = dict( euler_a=(EulerAncestralDiscreteScheduler, dict()), kdpm_2=(KDPM2DiscreteScheduler, dict()), kdpm_2_a=(KDPM2AncestralDiscreteScheduler, dict()), - dpmpp_2s=(DPMSolverSinglestepScheduler, dict()), + dpmpp_2s=(DPMSolverSinglestepScheduler, dict(use_karras_sigmas=False)), + dpmpp_2s_k=(DPMSolverSinglestepScheduler, dict(use_karras_sigmas=True)), dpmpp_2m=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=False)), dpmpp_2m_k=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=True)), + dpmpp_2m_sde=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=False, algorithm_type='sde-dpmsolver++')), + dpmpp_2m_sde_k=(DPMSolverMultistepScheduler, dict(use_karras_sigmas=True, algorithm_type='sde-dpmsolver++')), + dpmpp_sde=(DPMSolverSDEScheduler, dict(use_karras_sigmas=False, noise_sampler_seed=0)), + dpmpp_sde_k=(DPMSolverSDEScheduler, dict(use_karras_sigmas=True, noise_sampler_seed=0)), unipc=(UniPCMultistepScheduler, dict(cpu_only=True)) ) diff --git a/invokeai/backend/stable_diffusion/textual_inversion_manager.py b/invokeai/backend/stable_diffusion/textual_inversion_manager.py deleted file mode 100644 index 9476c12dc5..0000000000 --- a/invokeai/backend/stable_diffusion/textual_inversion_manager.py +++ /dev/null @@ -1,429 +0,0 @@ -import traceback -from dataclasses import dataclass -from pathlib import Path -from typing import Optional, Union, List - -import safetensors.torch -import torch - -from compel.embeddings_provider import BaseTextualInversionManager -from picklescan.scanner import scan_file_path -from transformers import CLIPTextModel, CLIPTokenizer - -import invokeai.backend.util.logging as logger -from .concepts_lib import HuggingFaceConceptsLibrary - -@dataclass -class EmbeddingInfo: - name: str - embedding: torch.Tensor - num_vectors_per_token: int - token_dim: int - trained_steps: int = None - trained_model_name: str = None - trained_model_checksum: str = None - -@dataclass -class TextualInversion: - trigger_string: str - embedding: torch.Tensor - trigger_token_id: Optional[int] = None - pad_token_ids: Optional[list[int]] = None - - @property - def embedding_vector_length(self) -> int: - return self.embedding.shape[0] - - -class TextualInversionManager(BaseTextualInversionManager): - def __init__( - self, - tokenizer: CLIPTokenizer, - text_encoder: CLIPTextModel, - full_precision: bool = True, - ): - self.tokenizer = tokenizer - self.text_encoder = text_encoder - self.full_precision = full_precision - self.hf_concepts_library = HuggingFaceConceptsLibrary() - self.trigger_to_sourcefile = dict() - default_textual_inversions: list[TextualInversion] = [] - self.textual_inversions = default_textual_inversions - - def load_huggingface_concepts(self, concepts: list[str]): - for concept_name in concepts: - if concept_name in self.hf_concepts_library.concepts_loaded: - continue - trigger = self.hf_concepts_library.concept_to_trigger(concept_name) - if ( - self.has_textual_inversion_for_trigger_string(trigger) - or self.has_textual_inversion_for_trigger_string(concept_name) - or self.has_textual_inversion_for_trigger_string(f"<{concept_name}>") - ): # in case a token with literal angle brackets encountered - logger.info(f"Loaded local embedding for trigger {concept_name}") - continue - bin_file = self.hf_concepts_library.get_concept_model_path(concept_name) - if not bin_file: - continue - logger.info(f"Loaded remote embedding for trigger {concept_name}") - self.load_textual_inversion(bin_file) - self.hf_concepts_library.concepts_loaded[concept_name] = True - - def get_all_trigger_strings(self) -> list[str]: - return [ti.trigger_string for ti in self.textual_inversions] - - def load_textual_inversion( - self, ckpt_path: Union[str, Path], defer_injecting_tokens: bool = False - ): - ckpt_path = Path(ckpt_path) - - if not ckpt_path.is_file(): - return - - if str(ckpt_path).endswith(".DS_Store"): - return - - embedding_list = self._parse_embedding(str(ckpt_path)) - for embedding_info in embedding_list: - if (self.text_encoder.get_input_embeddings().weight.data[0].shape[0] != embedding_info.token_dim): - logger.warning( - f"Notice: {ckpt_path.parents[0].name}/{ckpt_path.name} was trained on a model with an incompatible token dimension: {self.text_encoder.get_input_embeddings().weight.data[0].shape[0]} vs {embedding_info.token_dim}." - ) - continue - - # Resolve the situation in which an earlier embedding has claimed the same - # trigger string. We replace the trigger with '', as we used to. - trigger_str = embedding_info.name - sourcefile = ( - f"{ckpt_path.parent.name}/{ckpt_path.name}" - if ckpt_path.name == "learned_embeds.bin" - else ckpt_path.name - ) - - if trigger_str in self.trigger_to_sourcefile: - replacement_trigger_str = ( - f"<{ckpt_path.parent.name}>" - if ckpt_path.name == "learned_embeds.bin" - else f"<{ckpt_path.stem}>" - ) - logger.info( - f"{sourcefile}: Trigger token '{trigger_str}' is already claimed by '{self.trigger_to_sourcefile[trigger_str]}'. Trigger this concept with {replacement_trigger_str}" - ) - trigger_str = replacement_trigger_str - - try: - self._add_textual_inversion( - trigger_str, - embedding_info.embedding, - defer_injecting_tokens=defer_injecting_tokens, - ) - # remember which source file claims this trigger - self.trigger_to_sourcefile[trigger_str] = sourcefile - - except ValueError as e: - logger.debug(f'Ignoring incompatible embedding {embedding_info["name"]}') - logger.debug(f"The error was {str(e)}") - - def _add_textual_inversion( - self, trigger_str, embedding, defer_injecting_tokens=False - ) -> Optional[TextualInversion]: - """ - Add a textual inversion to be recognised. - :param trigger_str: The trigger text in the prompt that activates this textual inversion. If unknown to the embedder's tokenizer, will be added. - :param embedding: The actual embedding data that will be inserted into the conditioning at the point where the token_str appears. - :return: The token id for the added embedding, either existing or newly-added. - """ - if trigger_str in [ti.trigger_string for ti in self.textual_inversions]: - logger.warning( - f"TextualInversionManager refusing to overwrite already-loaded token '{trigger_str}'" - ) - return - if not self.full_precision: - embedding = embedding.half() - if len(embedding.shape) == 1: - embedding = embedding.unsqueeze(0) - elif len(embedding.shape) > 2: - raise ValueError( - f"** TextualInversionManager cannot add {trigger_str} because the embedding shape {embedding.shape} is incorrect. The embedding must have shape [token_dim] or [V, token_dim] where V is vector length and token_dim is 768 for SD1 or 1280 for SD2." - ) - - try: - ti = TextualInversion(trigger_string=trigger_str, embedding=embedding) - if not defer_injecting_tokens: - self._inject_tokens_and_assign_embeddings(ti) - self.textual_inversions.append(ti) - return ti - - except ValueError as e: - if str(e).startswith("Warning"): - logger.warning(f"{str(e)}") - else: - traceback.print_exc() - logger.error( - f"TextualInversionManager was unable to add a textual inversion with trigger string {trigger_str}." - ) - raise - - def _inject_tokens_and_assign_embeddings(self, ti: TextualInversion) -> int: - if ti.trigger_token_id is not None: - raise ValueError( - f"Tokens already injected for textual inversion with trigger '{ti.trigger_string}'" - ) - - trigger_token_id = self._get_or_create_token_id_and_assign_embedding( - ti.trigger_string, ti.embedding[0] - ) - - if ti.embedding_vector_length > 1: - # for embeddings with vector length > 1 - pad_token_strings = [ - ti.trigger_string + "-!pad-" + str(pad_index) - for pad_index in range(1, ti.embedding_vector_length) - ] - # todo: batched UI for faster loading when vector length >2 - pad_token_ids = [ - self._get_or_create_token_id_and_assign_embedding( - pad_token_str, ti.embedding[1 + i] - ) - for (i, pad_token_str) in enumerate(pad_token_strings) - ] - else: - pad_token_ids = [] - - ti.trigger_token_id = trigger_token_id - ti.pad_token_ids = pad_token_ids - return ti.trigger_token_id - - def has_textual_inversion_for_trigger_string(self, trigger_string: str) -> bool: - try: - ti = self.get_textual_inversion_for_trigger_string(trigger_string) - return ti is not None - except StopIteration: - return False - - def get_textual_inversion_for_trigger_string( - self, trigger_string: str - ) -> TextualInversion: - return next( - ti for ti in self.textual_inversions if ti.trigger_string == trigger_string - ) - - def get_textual_inversion_for_token_id(self, token_id: int) -> TextualInversion: - return next( - ti for ti in self.textual_inversions if ti.trigger_token_id == token_id - ) - - def create_deferred_token_ids_for_any_trigger_terms( - self, prompt_string: str - ) -> list[int]: - injected_token_ids = [] - for ti in self.textual_inversions: - if ti.trigger_token_id is None and ti.trigger_string in prompt_string: - if ti.embedding_vector_length > 1: - logger.info( - f"Preparing tokens for textual inversion {ti.trigger_string}..." - ) - try: - self._inject_tokens_and_assign_embeddings(ti) - except ValueError as e: - logger.debug( - f"Ignoring incompatible embedding trigger {ti.trigger_string}" - ) - logger.debug(f"The error was {str(e)}") - continue - injected_token_ids.append(ti.trigger_token_id) - injected_token_ids.extend(ti.pad_token_ids) - return injected_token_ids - - def expand_textual_inversion_token_ids_if_necessary( - self, prompt_token_ids: list[int] - ) -> list[int]: - """ - Insert padding tokens as necessary into the passed-in list of token ids to match any textual inversions it includes. - - :param prompt_token_ids: The prompt as a list of token ids (`int`s). Should not include bos and eos markers. - :return: The prompt token ids with any necessary padding to account for textual inversions inserted. May be too - long - caller is responsible for prepending/appending eos and bos token ids, and truncating if necessary. - """ - if len(prompt_token_ids) == 0: - return prompt_token_ids - - if prompt_token_ids[0] == self.tokenizer.bos_token_id: - raise ValueError("prompt_token_ids must not start with bos_token_id") - if prompt_token_ids[-1] == self.tokenizer.eos_token_id: - raise ValueError("prompt_token_ids must not end with eos_token_id") - textual_inversion_trigger_token_ids = [ - ti.trigger_token_id for ti in self.textual_inversions - ] - prompt_token_ids = prompt_token_ids.copy() - for i, token_id in reversed(list(enumerate(prompt_token_ids))): - if token_id in textual_inversion_trigger_token_ids: - textual_inversion = next( - ti - for ti in self.textual_inversions - if ti.trigger_token_id == token_id - ) - for pad_idx in range(0, textual_inversion.embedding_vector_length - 1): - prompt_token_ids.insert( - i + pad_idx + 1, textual_inversion.pad_token_ids[pad_idx] - ) - - return prompt_token_ids - - def _get_or_create_token_id_and_assign_embedding( - self, token_str: str, embedding: torch.Tensor - ) -> int: - if len(embedding.shape) != 1: - raise ValueError( - "Embedding has incorrect shape - must be [token_dim] where token_dim is 768 for SD1 or 1280 for SD2" - ) - existing_token_id = self.tokenizer.convert_tokens_to_ids(token_str) - if existing_token_id == self.tokenizer.unk_token_id: - num_tokens_added = self.tokenizer.add_tokens(token_str) - current_embeddings = self.text_encoder.resize_token_embeddings(None) - current_token_count = current_embeddings.num_embeddings - new_token_count = current_token_count + num_tokens_added - # the following call is slow - todo make batched for better performance with vector length >1 - self.text_encoder.resize_token_embeddings(new_token_count) - - token_id = self.tokenizer.convert_tokens_to_ids(token_str) - if token_id == self.tokenizer.unk_token_id: - raise RuntimeError(f"Unable to find token id for token '{token_str}'") - if ( - self.text_encoder.get_input_embeddings().weight.data[token_id].shape - != embedding.shape - ): - raise ValueError( - f"Warning. Cannot load embedding for {token_str}. It was trained on a model with token dimension {embedding.shape[0]}, but the current model has token dimension {self.text_encoder.get_input_embeddings().weight.data[token_id].shape[0]}." - ) - self.text_encoder.get_input_embeddings().weight.data[token_id] = embedding - - return token_id - - - def _parse_embedding(self, embedding_file: str)->List[EmbeddingInfo]: - suffix = Path(embedding_file).suffix - try: - if suffix in [".pt",".ckpt",".bin"]: - scan_result = scan_file_path(embedding_file) - if scan_result.infected_files > 0: - logger.critical( - f"Security Issues Found in Model: {scan_result.issues_count}" - ) - logger.critical("For your safety, InvokeAI will not load this embed.") - return list() - ckpt = torch.load(embedding_file,map_location="cpu") - else: - ckpt = safetensors.torch.load_file(embedding_file) - except Exception as e: - logger.warning(f"Notice: unrecognized embedding file format: {embedding_file}: {e}") - return list() - - # try to figure out what kind of embedding file it is and parse accordingly - keys = list(ckpt.keys()) - if all(x in keys for x in ['string_to_token','string_to_param','name','step']): - return self._parse_embedding_v1(ckpt, embedding_file) # example rem_rezero.pt - - elif all(x in keys for x in ['string_to_token','string_to_param']): - return self._parse_embedding_v2(ckpt, embedding_file) # example midj-strong.pt - - elif 'emb_params' in keys: - return self._parse_embedding_v3(ckpt, embedding_file) # example easynegative.safetensors - - else: - return self._parse_embedding_v4(ckpt, embedding_file) # usually a '.bin' file - - def _parse_embedding_v1(self, embedding_ckpt: dict, file_path: str)->List[EmbeddingInfo]: - basename = Path(file_path).stem - logger.debug(f'Loading v1 embedding file: {basename}') - - embeddings = list() - token_counter = -1 - for token,embedding in embedding_ckpt["string_to_param"].items(): - if token_counter < 0: - trigger = embedding_ckpt["name"] - elif token_counter == 0: - trigger = '' - else: - trigger = f'<{basename}-{int(token_counter:=token_counter)}>' - token_counter += 1 - embedding_info = EmbeddingInfo( - name = trigger, - embedding = embedding, - num_vectors_per_token = embedding.size()[0], - token_dim = embedding.size()[1], - trained_steps = embedding_ckpt["step"], - trained_model_name = embedding_ckpt["sd_checkpoint_name"], - trained_model_checksum = embedding_ckpt["sd_checkpoint"] - ) - embeddings.append(embedding_info) - return embeddings - - def _parse_embedding_v2 ( - self, embedding_ckpt: dict, file_path: str - ) -> List[EmbeddingInfo]: - """ - This handles embedding .pt file variant #2. - """ - basename = Path(file_path).stem - logger.debug(f'Loading v2 embedding file: {basename}') - embeddings = list() - - if isinstance( - list(embedding_ckpt["string_to_token"].values())[0], torch.Tensor - ): - token_counter = 0 - for token,embedding in embedding_ckpt["string_to_param"].items(): - trigger = token if token != '*' \ - else f'<{basename}>' if token_counter == 0 \ - else f'<{basename}-{int(token_counter:=token_counter+1)}>' - embedding_info = EmbeddingInfo( - name = trigger, - embedding = embedding, - num_vectors_per_token = embedding.size()[0], - token_dim = embedding.size()[1], - ) - embeddings.append(embedding_info) - else: - logger.warning(f"{basename}: Unrecognized embedding format") - - return embeddings - - def _parse_embedding_v3(self, embedding_ckpt: dict, file_path: str)->List[EmbeddingInfo]: - """ - Parse 'version 3' of the .pt textual inversion embedding files. - """ - basename = Path(file_path).stem - logger.debug(f'Loading v3 embedding file: {basename}') - embedding = embedding_ckpt['emb_params'] - embedding_info = EmbeddingInfo( - name = f'<{basename}>', - embedding = embedding, - num_vectors_per_token = embedding.size()[0], - token_dim = embedding.size()[1], - ) - return [embedding_info] - - def _parse_embedding_v4(self, embedding_ckpt: dict, filepath: str)->List[EmbeddingInfo]: - """ - Parse 'version 4' of the textual inversion embedding files. This one - is usually associated with .bin files trained by HuggingFace diffusers. - """ - basename = Path(filepath).stem - short_path = Path(filepath).parents[0].name+'/'+Path(filepath).name - - logger.debug(f'Loading v4 embedding file: {short_path}') - - embeddings = list() - if list(embedding_ckpt.keys()) == 0: - logger.warning(f"Invalid embeddings file: {short_path}") - else: - for token,embedding in embedding_ckpt.items(): - embedding_info = EmbeddingInfo( - name = token or f"<{basename}>", - embedding = embedding, - num_vectors_per_token = 1, # All Concepts seem to default to 1 - token_dim = embedding.size()[0], - ) - embeddings.append(embedding_info) - return embeddings diff --git a/invokeai/backend/training/textual_inversion_training.py b/invokeai/backend/training/textual_inversion_training.py index 8c27a6e718..c4290cacb3 100644 --- a/invokeai/backend/training/textual_inversion_training.py +++ b/invokeai/backend/training/textual_inversion_training.py @@ -88,7 +88,7 @@ def save_progress( def parse_args(): - config = InvokeAIAppConfig(argv=[]) + config = InvokeAIAppConfig.get_config() parser = PagingArgumentParser( description="Textual inversion training" ) diff --git a/invokeai/backend/util/__init__.py b/invokeai/backend/util/__init__.py index ca42f86fd6..fadeff4d75 100644 --- a/invokeai/backend/util/__init__.py +++ b/invokeai/backend/util/__init__.py @@ -16,4 +16,7 @@ from .util import ( download_with_resume, instantiate_from_config, url_attachment_name, + Chdir ) + + diff --git a/invokeai/backend/util/devices.py b/invokeai/backend/util/devices.py index c6c0819df8..615209d98d 100644 --- a/invokeai/backend/util/devices.py +++ b/invokeai/backend/util/devices.py @@ -4,15 +4,15 @@ from contextlib import nullcontext import torch from torch import autocast -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig CPU_DEVICE = torch.device("cpu") CUDA_DEVICE = torch.device("cuda") MPS_DEVICE = torch.device("mps") +config = InvokeAIAppConfig.get_config() def choose_torch_device() -> torch.device: """Convenience routine for guessing which GPU device to run model on""" - config = get_invokeai_config() if config.always_use_cpu: return CPU_DEVICE if torch.cuda.is_available(): @@ -32,7 +32,6 @@ def choose_precision(device: torch.device) -> str: def torch_dtype(device: torch.device) -> torch.dtype: - config = get_invokeai_config() if config.full_precision: return torch.float32 if choose_precision(device) == "float16": diff --git a/invokeai/backend/util/logging.py b/invokeai/backend/util/logging.py index 9d1262d5c6..09ae600633 100644 --- a/invokeai/backend/util/logging.py +++ b/invokeai/backend/util/logging.py @@ -1,6 +1,7 @@ # Copyright (c) 2023 Lincoln D. Stein and The InvokeAI Development Team -"""invokeai.util.logging +""" +invokeai.util.logging Logging class for InvokeAI that produces console messages @@ -11,6 +12,7 @@ from invokeai.backend.util.logging import InvokeAILogger logger = InvokeAILogger.getLogger(name='InvokeAI') // Initialization (or) logger = InvokeAILogger.getLogger(__name__) // To use the filename +logger.configure() logger.critical('this is critical') // Critical Message logger.error('this is an error') // Error Message @@ -28,10 +30,165 @@ Console messages: Alternate Method (in this case the logger name will be set to InvokeAI): import invokeai.backend.util.logging as IAILogger IAILogger.debug('this is a debugging message') + +## Configuration + +The default configuration will print to stderr on the console. To add +additional logging handlers, call getLogger with an initialized InvokeAIAppConfig +object: + + + config = InvokeAIAppConfig.get_config() + config.parse_args() + logger = InvokeAILogger.getLogger(config=config) + +### Three command-line options control logging: + +`--log_handlers ...` + +This option activates one or more log handlers. Options are "console", "file", "syslog" and "http". To specify more than one, separate them by spaces: + +``` +invokeai-web --log_handlers console syslog=/dev/log file=C:\\Users\\fred\\invokeai.log +``` + +The format of these options is described below. + +### `--log_format {plain|color|legacy|syslog}` + +This controls the format of log messages written to the console. Only the "console" log handler is currently affected by this setting. + +* "plain" provides formatted messages like this: + +```bash + +[2023-05-24 23:18:2[2023-05-24 23:18:50,352]::[InvokeAI]::DEBUG --> this is a debug message +[2023-05-24 23:18:50,352]::[InvokeAI]::INFO --> this is an informational messages +[2023-05-24 23:18:50,352]::[InvokeAI]::WARNING --> this is a warning +[2023-05-24 23:18:50,352]::[InvokeAI]::ERROR --> this is an error +[2023-05-24 23:18:50,352]::[InvokeAI]::CRITICAL --> this is a critical error +``` + +* "color" produces similar output, but the text will be color coded to indicate the severity of the message. + +* "legacy" produces output similar to InvokeAI versions 2.3 and earlier: + +``` +### this is a critical error +*** this is an error +** this is a warning +>> this is an informational messages + | this is a debug message +``` + +* "syslog" produces messages suitable for syslog entries: + +```bash +InvokeAI [2691178] this is a critical error +InvokeAI [2691178] this is an error +InvokeAI [2691178] this is a warning +InvokeAI [2691178] this is an informational messages +InvokeAI [2691178] this is a debug message +``` + +(note that the date, time and hostname will be added by the syslog system) + +### `--log_level {debug|info|warning|error|critical}` + +Providing this command-line option will cause only messages at the specified level or above to be emitted. + +## Console logging + +When "console" is provided to `--log_handlers`, messages will be written to the command line window in which InvokeAI was launched. By default, the color formatter will be used unless overridden by `--log_format`. + +## File logging + +When "file" is provided to `--log_handlers`, entries will be written to the file indicated in the path argument. By default, the "plain" format will be used: + +```bash +invokeai-web --log_handlers file=/var/log/invokeai.log +``` + +## Syslog logging + +When "syslog" is requested, entries will be sent to the syslog system. There are a variety of ways to control where the log message is sent: + +* Send to the local machine using the `/dev/log` socket: + +``` +invokeai-web --log_handlers syslog=/dev/log +``` + +* Send to the local machine using a UDP message: + +``` +invokeai-web --log_handlers syslog=localhost +``` + +* Send to the local machine using a UDP message on a nonstandard port: + +``` +invokeai-web --log_handlers syslog=localhost:512 +``` + +* Send to a remote machine named "loghost" on the local LAN using facility LOG_USER and UDP packets: + +``` +invokeai-web --log_handlers syslog=loghost,facility=LOG_USER,socktype=SOCK_DGRAM +``` + +This can be abbreviated `syslog=loghost`, as LOG_USER and SOCK_DGRAM are defaults. + +* Send to a remote machine named "loghost" using the facility LOCAL0 and using a TCP socket: + +``` +invokeai-web --log_handlers syslog=loghost,facility=LOG_LOCAL0,socktype=SOCK_STREAM +``` + +If no arguments are specified (just a bare "syslog"), then the logging system will look for a UNIX socket named `/dev/log`, and if not found try to send a UDP message to `localhost`. The Macintosh OS used to support logging to a socket named `/var/run/syslog`, but this feature has since been disabled. + +## Web logging + +If you have access to a web server that is configured to log messages when a particular URL is requested, you can log using the "http" method: + +``` +invokeai-web --log_handlers http=http://my.server/path/to/logger,method=POST +``` + +The optional [,method=] part can be used to specify whether the URL accepts GET (default) or POST messages. + +Currently password authentication and SSL are not supported. + +## Using the configuration file + +You can set and forget logging options by adding a "Logging" section to `invokeai.yaml`: + +``` +InvokeAI: + [... other settings...] + Logging: + log_handlers: + - console + - syslog=/dev/log + log_level: info + log_format: color +``` """ -import logging +import logging.handlers +import socket +import urllib.parse +from abc import abstractmethod +from pathlib import Path + +from invokeai.app.services.config import InvokeAIAppConfig, get_invokeai_config + +try: + import syslog + SYSLOG_AVAILABLE = True +except: + SYSLOG_AVAILABLE = False # module level functions def debug(msg, *args, **kwargs): @@ -62,11 +219,77 @@ def getLogger(name: str = None) -> logging.Logger: return InvokeAILogger.getLogger(name) -class InvokeAILogFormatter(logging.Formatter): +_FACILITY_MAP = dict( + LOG_KERN = syslog.LOG_KERN, + LOG_USER = syslog.LOG_USER, + LOG_MAIL = syslog.LOG_MAIL, + LOG_DAEMON = syslog.LOG_DAEMON, + LOG_AUTH = syslog.LOG_AUTH, + LOG_LPR = syslog.LOG_LPR, + LOG_NEWS = syslog.LOG_NEWS, + LOG_UUCP = syslog.LOG_UUCP, + LOG_CRON = syslog.LOG_CRON, + LOG_SYSLOG = syslog.LOG_SYSLOG, + LOG_LOCAL0 = syslog.LOG_LOCAL0, + LOG_LOCAL1 = syslog.LOG_LOCAL1, + LOG_LOCAL2 = syslog.LOG_LOCAL2, + LOG_LOCAL3 = syslog.LOG_LOCAL3, + LOG_LOCAL4 = syslog.LOG_LOCAL4, + LOG_LOCAL5 = syslog.LOG_LOCAL5, + LOG_LOCAL6 = syslog.LOG_LOCAL6, + LOG_LOCAL7 = syslog.LOG_LOCAL7, +) if SYSLOG_AVAILABLE else dict() + +_SOCK_MAP = dict( + SOCK_STREAM = socket.SOCK_STREAM, + SOCK_DGRAM = socket.SOCK_DGRAM, +) + +class InvokeAIFormatter(logging.Formatter): + ''' + Base class for logging formatter + + ''' + def format(self, record): + formatter = logging.Formatter(self.log_fmt(record.levelno)) + return formatter.format(record) + + @abstractmethod + def log_fmt(self, levelno: int)->str: + pass + +class InvokeAISyslogFormatter(InvokeAIFormatter): + ''' + Formatting for syslog + ''' + def log_fmt(self, levelno: int)->str: + return '%(name)s [%(process)d] <%(levelname)s> %(message)s' + +class InvokeAILegacyLogFormatter(InvokeAIFormatter): + ''' + Formatting for the InvokeAI Logger (legacy version) + ''' + FORMATS = { + logging.DEBUG: " | %(message)s", + logging.INFO: ">> %(message)s", + logging.WARNING: "** %(message)s", + logging.ERROR: "*** %(message)s", + logging.CRITICAL: "### %(message)s", + } + def log_fmt(self,levelno:int)->str: + return self.FORMATS.get(levelno) + +class InvokeAIPlainLogFormatter(InvokeAIFormatter): + ''' + Custom Formatting for the InvokeAI Logger (plain version) + ''' + def log_fmt(self, levelno: int)->str: + return "[%(asctime)s]::[%(name)s]::%(levelname)s --> %(message)s" + +class InvokeAIColorLogFormatter(InvokeAIFormatter): ''' Custom Formatting for the InvokeAI Logger ''' - # Color Codes grey = "\x1b[38;20m" yellow = "\x1b[33;20m" @@ -88,23 +311,115 @@ class InvokeAILogFormatter(logging.Formatter): logging.CRITICAL: bold_red + log_format + reset } - def format(self, record): - log_fmt = self.FORMATS.get(record.levelno) - formatter = logging.Formatter(log_fmt, datefmt="%d-%m-%Y %H:%M:%S") - return formatter.format(record) + def log_fmt(self, levelno: int)->str: + return self.FORMATS.get(levelno) +LOG_FORMATTERS = { + 'plain': InvokeAIPlainLogFormatter, + 'color': InvokeAIColorLogFormatter, + 'syslog': InvokeAISyslogFormatter, + 'legacy': InvokeAILegacyLogFormatter, +} class InvokeAILogger(object): loggers = dict() @classmethod - def getLogger(cls, name: str = 'InvokeAI') -> logging.Logger: - if name not in cls.loggers: + def getLogger(cls, + name: str = 'InvokeAI', + config: InvokeAIAppConfig=InvokeAIAppConfig.get_config())->logging.Logger: + if name in cls.loggers: + logger = cls.loggers[name] + logger.handlers.clear() + else: logger = logging.getLogger(name) - logger.setLevel(logging.DEBUG) - ch = logging.StreamHandler() - fmt = InvokeAILogFormatter() - ch.setFormatter(fmt) + logger.setLevel(config.log_level.upper()) # yes, strings work here + for ch in cls.getLoggers(config): logger.addHandler(ch) cls.loggers[name] = logger return cls.loggers[name] + + @classmethod + def getLoggers(cls, config: InvokeAIAppConfig) -> list[logging.Handler]: + handler_strs = config.log_handlers + handlers = list() + for handler in handler_strs: + handler_name,*args = handler.split('=',2) + args = args[0] if len(args) > 0 else None + + # console and file get the fancy formatter. + # syslog gets a simple one + # http gets no custom formatter + formatter = LOG_FORMATTERS[config.log_format] + if handler_name=='console': + ch = logging.StreamHandler() + ch.setFormatter(formatter()) + handlers.append(ch) + + elif handler_name=='syslog': + ch = cls._parse_syslog_args(args) + handlers.append(ch) + + elif handler_name=='file': + ch = cls._parse_file_args(args) + ch.setFormatter(formatter()) + handlers.append(ch) + + elif handler_name=='http': + ch = cls._parse_http_args(args) + handlers.append(ch) + return handlers + + @staticmethod + def _parse_syslog_args( + args: str=None + )-> logging.Handler: + if not SYSLOG_AVAILABLE: + raise ValueError("syslog is not available on this system") + if not args: + args='/dev/log' if Path('/dev/log').exists() else 'address:localhost:514' + syslog_args = dict() + try: + for a in args.split(','): + arg_name,*arg_value = a.split(':',2) + if arg_name=='address': + host,*port = arg_value + port = 514 if len(port)==0 else int(port[0]) + syslog_args['address'] = (host,port) + elif arg_name=='facility': + syslog_args['facility'] = _FACILITY_MAP[arg_value[0]] + elif arg_name=='socktype': + syslog_args['socktype'] = _SOCK_MAP[arg_value[0]] + else: + syslog_args['address'] = arg_name + except: + raise ValueError(f"{args} is not a value argument list for syslog logging") + return logging.handlers.SysLogHandler(**syslog_args) + + @staticmethod + def _parse_file_args(args: str=None)-> logging.Handler: + if not args: + raise ValueError("please provide filename for file logging using format 'file=/path/to/logfile.txt'") + return logging.FileHandler(args) + + @staticmethod + def _parse_http_args(args: str=None)-> logging.Handler: + if not args: + raise ValueError("please provide destination for http logging using format 'http=url'") + arg_list = args.split(',') + url = urllib.parse.urlparse(arg_list.pop(0)) + if url.scheme != 'http': + raise ValueError(f"the http logging module can only log to HTTP URLs, but {url.scheme} was specified") + host = url.hostname + path = url.path + port = url.port or 80 + + syslog_args = dict() + for a in arg_list: + arg_name, *arg_value = a.split(':',2) + if arg_name=='method': + arg_value = arg_value[0] if len(arg_value)>0 else 'GET' + syslog_args[arg_name] = arg_value + else: # TODO: Provide support for SSL context and credentials + pass + return logging.handlers.HTTPHandler(f'{host}:{port}',path,**syslog_args) diff --git a/invokeai/backend/util/util.py b/invokeai/backend/util/util.py index edc6a7b04b..1cc632e483 100644 --- a/invokeai/backend/util/util.py +++ b/invokeai/backend/util/util.py @@ -322,8 +322,8 @@ def download_with_resume(url: str, dest: Path, access_token: str = None) -> Path logger.warning("corrupt existing file found. re-downloading") os.remove(dest) exist_size = 0 - - if resp.status_code == 416 or exist_size == content_length: + + if resp.status_code == 416 or (content_length > 0 and exist_size == content_length): logger.warning(f"{dest}: complete file found. Skipping.") return dest elif resp.status_code == 206 or exist_size > 0: @@ -331,7 +331,7 @@ def download_with_resume(url: str, dest: Path, access_token: str = None) -> Path elif resp.status_code != 200: logger.error(f"An error occurred during downloading {dest}: {resp.reason}") else: - logger.error(f"{dest}: Downloading...") + logger.info(f"{dest}: Downloading...") try: if content_length < 2000: @@ -381,3 +381,18 @@ def image_to_dataURL(image: Image.Image, image_format: str = "PNG") -> str: buffered.getvalue() ).decode("UTF-8") return image_base64 + +class Chdir(object): + '''Context manager to chdir to desired directory and change back after context exits: + Args: + path (Path): The path to the cwd + ''' + def __init__(self, path: Path): + self.path = path + self.original = Path().absolute() + + def __enter__(self): + os.chdir(self.path) + + def __exit__(self,*args): + os.chdir(self.original) diff --git a/invokeai/backend/web/invoke_ai_web_server.py b/invokeai/backend/web/invoke_ai_web_server.py index 97687bd2bf..eec02cd9dc 100644 --- a/invokeai/backend/web/invoke_ai_web_server.py +++ b/invokeai/backend/web/invoke_ai_web_server.py @@ -1277,13 +1277,14 @@ class InvokeAIWebServer: eventlet.sleep(0) parsed_prompt, _ = get_prompt_structure(generation_parameters["prompt"]) - tokens = ( - None - if type(parsed_prompt) is Blend - else get_tokens_for_prompt_object( - self.generate.model.tokenizer, parsed_prompt + with self.generate.model_context as model: + tokens = ( + None + if type(parsed_prompt) is Blend + else get_tokens_for_prompt_object( + model.tokenizer, parsed_prompt + ) ) - ) attention_maps_image_base64_url = ( None if attention_maps_image is None diff --git a/invokeai/backend/web/modules/parameters.py b/invokeai/backend/web/modules/parameters.py index 9a4bc0aec3..440f21a947 100644 --- a/invokeai/backend/web/modules/parameters.py +++ b/invokeai/backend/web/modules/parameters.py @@ -7,6 +7,7 @@ SAMPLER_CHOICES = [ "ddpm", "deis", "lms", + "lms_k", "pndm", "heun", 'heun_k', @@ -16,8 +17,13 @@ SAMPLER_CHOICES = [ "kdpm_2", "kdpm_2_a", "dpmpp_2s", + "dpmpp_2s_k", "dpmpp_2m", "dpmpp_2m_k", + "dpmpp_2m_sde", + "dpmpp_2m_sde_k", + "dpmpp_sde", + "dpmpp_sde_k", "unipc", ] diff --git a/invokeai/configs/INITIAL_MODELS.yaml b/invokeai/configs/INITIAL_MODELS.yaml index 3ebd38a5f6..4ba67bc4bc 100644 --- a/invokeai/configs/INITIAL_MODELS.yaml +++ b/invokeai/configs/INITIAL_MODELS.yaml @@ -1,83 +1,92 @@ -stable-diffusion-1.5: +# This file predefines a few models that the user may want to install. +sd-1/main/stable-diffusion-v1-5: description: Stable Diffusion version 1.5 diffusers model (4.27 GB) repo_id: runwayml/stable-diffusion-v1-5 - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse recommended: True default: True -sd-inpainting-1.5: - description: RunwayML SD 1.5 model optimized for inpainting, diffusers version (4.27 GB) - repo_id: runwayml/stable-diffusion-inpainting - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse - recommended: True -stable-diffusion-2.1: +sd-1/main/stable-diffusion-inpainting: + description: RunwayML SD 1.5 model optimized for inpainting, diffusers version (4.27 GB) + repo_id: runwayml/stable-diffusion-inpainting + recommended: True +sd-2/main/stable-diffusion-2-1: description: Stable Diffusion version 2.1 diffusers model, trained on 768 pixel images (5.21 GB) repo_id: stabilityai/stable-diffusion-2-1 - format: diffusers recommended: True -sd-inpainting-2.0: +sd-2/main/stable-diffusion-2-inpainting: description: Stable Diffusion version 2.0 inpainting model (5.21 GB) repo_id: stabilityai/stable-diffusion-2-inpainting - format: diffusers recommended: False -analog-diffusion-1.0: +sd-1/main/Analog-Diffusion: description: An SD-1.5 model trained on diverse analog photographs (2.13 GB) repo_id: wavymulder/Analog-Diffusion - format: diffusers recommended: false -deliberate-1.0: +sd-1/main/Deliberate: description: Versatile model that produces detailed images up to 768px (4.27 GB) - format: diffusers repo_id: XpucT/Deliberate recommended: False -d&d-diffusion-1.0: +sd-1/main/Dungeons-and-Diffusion: description: Dungeons & Dragons characters (2.13 GB) - format: diffusers repo_id: 0xJustin/Dungeons-and-Diffusion recommended: False -dreamlike-photoreal-2.0: +sd-1/main/dreamlike-photoreal-2: description: A photorealistic model trained on 768 pixel images based on SD 1.5 (2.13 GB) - format: diffusers repo_id: dreamlike-art/dreamlike-photoreal-2.0 recommended: False -inkpunk-1.0: +sd-1/main/Inkpunk-Diffusion: description: Stylized illustrations inspired by Gorillaz, FLCL and Shinkawa; prompt with "nvinkpunk" (4.27 GB) - format: diffusers repo_id: Envvi/Inkpunk-Diffusion recommended: False -openjourney-4.0: +sd-1/main/openjourney: description: An SD 1.5 model fine tuned on Midjourney; prompt with "mdjrny-v4 style" (2.13 GB) - format: diffusers repo_id: prompthero/openjourney - vae: - repo_id: stabilityai/sd-vae-ft-mse recommended: False -portrait-plus-1.0: +sd-1/main/portraitplus: description: An SD-1.5 model trained on close range portraits of people; prompt with "portrait+" (2.13 GB) - format: diffusers repo_id: wavymulder/portraitplus recommended: False -seek-art-mega-1.0: - description: A general use SD-1.5 "anything" model that supports multiple styles (2.1 GB) +sd-1/main/seek.art_MEGA: repo_id: coreco/seek.art_MEGA - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse + description: A general use SD-1.5 "anything" model that supports multiple styles (2.1 GB) recommended: False -trinart-2.0: +sd-1/main/trinart_stable_diffusion_v2: description: An SD-1.5 model finetuned with ~40K assorted high resolution manga/anime-style images (2.13 GB) repo_id: naclbit/trinart_stable_diffusion_v2 - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse recommended: False -waifu-diffusion-1.4: +sd-1/main/waifu-diffusion: description: An SD-1.5 model trained on 680k anime/manga-style images (2.13 GB) repo_id: hakurei/waifu-diffusion - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse recommended: False +sd-1/controlnet/canny: + repo_id: lllyasviel/control_v11p_sd15_canny +sd-1/controlnet/inpaint: + repo_id: lllyasviel/control_v11p_sd15_inpaint +sd-1/controlnet/mlsd: + repo_id: lllyasviel/control_v11p_sd15_mlsd +sd-1/controlnet/depth: + repo_id: lllyasviel/control_v11f1p_sd15_depth +sd-1/controlnet/normal_bae: + repo_id: lllyasviel/control_v11p_sd15_normalbae +sd-1/controlnet/seg: + repo_id: lllyasviel/control_v11p_sd15_seg +sd-1/controlnet/lineart: + repo_id: lllyasviel/control_v11p_sd15_lineart +sd-1/controlnet/lineart_anime: + repo_id: lllyasviel/control_v11p_sd15s2_lineart_anime +sd-1/controlnet/scribble: + repo_id: lllyasviel/control_v11p_sd15_scribble +sd-1/controlnet/softedge: + repo_id: lllyasviel/control_v11p_sd15_softedge +sd-1/controlnet/shuffle: + repo_id: lllyasviel/control_v11e_sd15_shuffle +sd-1/controlnet/tile: + repo_id: lllyasviel/control_v11f1e_sd15_tile +sd-1/controlnet/ip2p: + repo_id: lllyasviel/control_v11e_sd15_ip2p +sd-1/embedding/EasyNegative: + path: https://huggingface.co/embed/EasyNegative/resolve/main/EasyNegative.safetensors +sd-1/embedding/ahx-beta-453407d: + repo_id: sd-concepts-library/ahx-beta-453407d +sd-1/lora/LowRA: + path: https://civitai.com/api/download/models/63006 +sd-1/lora/Ink scenery: + path: https://civitai.com/api/download/models/83390 diff --git a/invokeai/configs/stable-diffusion/v2-inpainting-inference-v.yaml b/invokeai/configs/stable-diffusion/v2-inpainting-inference-v.yaml new file mode 100644 index 0000000000..37cda460aa --- /dev/null +++ b/invokeai/configs/stable-diffusion/v2-inpainting-inference-v.yaml @@ -0,0 +1,159 @@ +model: + base_learning_rate: 5.0e-05 + target: ldm.models.diffusion.ddpm.LatentInpaintDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + parameterization: "v" + num_timesteps_cond: 1 + log_every_t: 200 + timesteps: 1000 + first_stage_key: "jpg" + cond_stage_key: "txt" + image_size: 64 + channels: 4 + cond_stage_trainable: false + conditioning_key: hybrid + scale_factor: 0.18215 + monitor: val/loss_simple_ema + finetune_keys: null + use_ema: False + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + use_checkpoint: True + image_size: 32 # unused + in_channels: 9 + out_channels: 4 + model_channels: 320 + attention_resolutions: [ 4, 2, 1 ] + num_res_blocks: 2 + channel_mult: [ 1, 2, 4, 4 ] + num_head_channels: 64 # need to fix for flash-attn + use_spatial_transformer: True + use_linear_in_transformer: True + transformer_depth: 1 + context_dim: 1024 + legacy: False + + first_stage_config: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + #attn_type: "vanilla-xformers" + double_z: true + z_channels: 4 + resolution: 256 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: + - 1 + - 2 + - 4 + - 4 + num_res_blocks: 2 + attn_resolutions: [ ] + dropout: 0.0 + lossconfig: + target: torch.nn.Identity + + cond_stage_config: + target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder + params: + freeze: True + layer: "penultimate" + + +data: + target: ldm.data.laion.WebDataModuleFromConfig + params: + tar_base: null # for concat as in LAION-A + p_unsafe_threshold: 0.1 + filter_word_list: "data/filters.yaml" + max_pwatermark: 0.45 + batch_size: 8 + num_workers: 6 + multinode: True + min_size: 512 + train: + shards: + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-0/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-1/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-2/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-3/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-4/{00000..18699}.tar -" #{00000-94333}.tar" + shuffle: 10000 + image_key: jpg + image_transforms: + - target: torchvision.transforms.Resize + params: + size: 512 + interpolation: 3 + - target: torchvision.transforms.RandomCrop + params: + size: 512 + postprocess: + target: ldm.data.laion.AddMask + params: + mode: "512train-large" + p_drop: 0.25 + # NOTE use enough shards to avoid empty validation loops in workers + validation: + shards: + - "pipe:aws s3 cp s3://deep-floyd-s3/datasets/laion_cleaned-part5/{93001..94333}.tar - " + shuffle: 0 + image_key: jpg + image_transforms: + - target: torchvision.transforms.Resize + params: + size: 512 + interpolation: 3 + - target: torchvision.transforms.CenterCrop + params: + size: 512 + postprocess: + target: ldm.data.laion.AddMask + params: + mode: "512train-large" + p_drop: 0.25 + +lightning: + find_unused_parameters: True + modelcheckpoint: + params: + every_n_train_steps: 5000 + + callbacks: + metrics_over_trainsteps_checkpoint: + params: + every_n_train_steps: 10000 + + image_logger: + target: main.ImageLogger + params: + enable_autocast: False + disabled: False + batch_frequency: 1000 + max_images: 4 + increase_log_steps: False + log_first_step: False + log_images_kwargs: + use_ema_scope: False + inpaint: False + plot_progressive_rows: False + plot_diffusion_rows: False + N: 4 + unconditional_guidance_scale: 5.0 + unconditional_guidance_label: [""] + ddim_steps: 50 # todo check these out for depth2img, + ddim_eta: 0.0 # todo check these out for depth2img, + + trainer: + benchmark: True + val_check_interval: 5000000 + num_sanity_val_steps: 0 + accumulate_grad_batches: 1 \ No newline at end of file diff --git a/invokeai/configs/stable-diffusion/v2-inpainting-inference.yaml b/invokeai/configs/stable-diffusion/v2-inpainting-inference.yaml new file mode 100644 index 0000000000..5aaf13162d --- /dev/null +++ b/invokeai/configs/stable-diffusion/v2-inpainting-inference.yaml @@ -0,0 +1,158 @@ +model: + base_learning_rate: 5.0e-05 + target: ldm.models.diffusion.ddpm.LatentInpaintDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + num_timesteps_cond: 1 + log_every_t: 200 + timesteps: 1000 + first_stage_key: "jpg" + cond_stage_key: "txt" + image_size: 64 + channels: 4 + cond_stage_trainable: false + conditioning_key: hybrid + scale_factor: 0.18215 + monitor: val/loss_simple_ema + finetune_keys: null + use_ema: False + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + use_checkpoint: True + image_size: 32 # unused + in_channels: 9 + out_channels: 4 + model_channels: 320 + attention_resolutions: [ 4, 2, 1 ] + num_res_blocks: 2 + channel_mult: [ 1, 2, 4, 4 ] + num_head_channels: 64 # need to fix for flash-attn + use_spatial_transformer: True + use_linear_in_transformer: True + transformer_depth: 1 + context_dim: 1024 + legacy: False + + first_stage_config: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + #attn_type: "vanilla-xformers" + double_z: true + z_channels: 4 + resolution: 256 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: + - 1 + - 2 + - 4 + - 4 + num_res_blocks: 2 + attn_resolutions: [ ] + dropout: 0.0 + lossconfig: + target: torch.nn.Identity + + cond_stage_config: + target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder + params: + freeze: True + layer: "penultimate" + + +data: + target: ldm.data.laion.WebDataModuleFromConfig + params: + tar_base: null # for concat as in LAION-A + p_unsafe_threshold: 0.1 + filter_word_list: "data/filters.yaml" + max_pwatermark: 0.45 + batch_size: 8 + num_workers: 6 + multinode: True + min_size: 512 + train: + shards: + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-0/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-1/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-2/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-3/{00000..18699}.tar -" + - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-4/{00000..18699}.tar -" #{00000-94333}.tar" + shuffle: 10000 + image_key: jpg + image_transforms: + - target: torchvision.transforms.Resize + params: + size: 512 + interpolation: 3 + - target: torchvision.transforms.RandomCrop + params: + size: 512 + postprocess: + target: ldm.data.laion.AddMask + params: + mode: "512train-large" + p_drop: 0.25 + # NOTE use enough shards to avoid empty validation loops in workers + validation: + shards: + - "pipe:aws s3 cp s3://deep-floyd-s3/datasets/laion_cleaned-part5/{93001..94333}.tar - " + shuffle: 0 + image_key: jpg + image_transforms: + - target: torchvision.transforms.Resize + params: + size: 512 + interpolation: 3 + - target: torchvision.transforms.CenterCrop + params: + size: 512 + postprocess: + target: ldm.data.laion.AddMask + params: + mode: "512train-large" + p_drop: 0.25 + +lightning: + find_unused_parameters: True + modelcheckpoint: + params: + every_n_train_steps: 5000 + + callbacks: + metrics_over_trainsteps_checkpoint: + params: + every_n_train_steps: 10000 + + image_logger: + target: main.ImageLogger + params: + enable_autocast: False + disabled: False + batch_frequency: 1000 + max_images: 4 + increase_log_steps: False + log_first_step: False + log_images_kwargs: + use_ema_scope: False + inpaint: False + plot_progressive_rows: False + plot_diffusion_rows: False + N: 4 + unconditional_guidance_scale: 5.0 + unconditional_guidance_label: [""] + ddim_steps: 50 # todo check these out for depth2img, + ddim_eta: 0.0 # todo check these out for depth2img, + + trainer: + benchmark: True + val_check_interval: 5000000 + num_sanity_val_steps: 0 + accumulate_grad_batches: 1 \ No newline at end of file diff --git a/invokeai/frontend/install/invokeai_configure.py b/invokeai/frontend/install/invokeai_configure.py index 0df5fdb16f..e087e4f40f 100644 --- a/invokeai/frontend/install/invokeai_configure.py +++ b/invokeai/frontend/install/invokeai_configure.py @@ -1,4 +1,4 @@ """ Wrapper for invokeai.backend.configure.invokeai_configure """ -from ...backend.config.invokeai_configure import main +from ...backend.install.invokeai_configure import main diff --git a/invokeai/frontend/install/invokeai_update.py b/invokeai/frontend/install/invokeai_update.py index 781c66cddd..18ad71957e 100644 --- a/invokeai/frontend/install/invokeai_update.py +++ b/invokeai/frontend/install/invokeai_update.py @@ -4,14 +4,14 @@ pip install . ''' import os import platform +import pkg_resources +import psutil import requests from rich import box, print -from rich.console import Console, Group, group +from rich.console import Console, group from rich.panel import Panel from rich.prompt import Prompt from rich.style import Style -from rich.syntax import Syntax -from rich.text import Text from invokeai.version import __version__ @@ -32,6 +32,18 @@ else: def get_versions()->dict: return requests.get(url=INVOKE_AI_REL).json() +def invokeai_is_running()->bool: + for p in psutil.process_iter(): + try: + cmdline = p.cmdline() + matches = [x for x in cmdline if x.endswith(('invokeai','invokeai.exe'))] + if matches: + print(f':exclamation: [bold red]An InvokeAI instance appears to be running as process {p.pid}[/red bold]') + return True + except (psutil.AccessDenied,psutil.NoSuchProcess): + continue + return False + def welcome(versions: dict): @group() @@ -60,8 +72,22 @@ def welcome(versions: dict): ) console.line() +def get_extras(): + extras = '' + try: + dist = pkg_resources.get_distribution('xformers') + extras = '[xformers]' + except pkg_resources.DistributionNotFound: + pass + return extras + def main(): versions = get_versions() + if invokeai_is_running(): + print(f':exclamation: [bold red]Please terminate all running instances of InvokeAI before updating.[/red bold]') + input('Press any key to continue...') + return + welcome(versions) tag = None @@ -78,13 +104,15 @@ def main(): elif choice=='4': branch = Prompt.ask('Enter an InvokeAI branch name') + extras = get_extras() + print(f':crossed_fingers: Upgrading to [yellow]{tag if tag else release}[/yellow]') if release: - cmd = f'pip install {INVOKE_AI_SRC}/{release}.zip --use-pep517 --upgrade' + cmd = f"pip install 'invokeai{extras} @ {INVOKE_AI_SRC}/{release}.zip' --use-pep517 --upgrade" elif tag: - cmd = f'pip install {INVOKE_AI_TAG}/{tag}.zip --use-pep517 --upgrade' + cmd = f"pip install 'invokeai{extras} @ {INVOKE_AI_TAG}/{tag}.zip' --use-pep517 --upgrade" else: - cmd = f'pip install {INVOKE_AI_BRANCH}/{branch}.zip --use-pep517 --upgrade' + cmd = f"pip install 'invokeai{extras} @ {INVOKE_AI_BRANCH}/{branch}.zip' --use-pep517 --upgrade" print('') print('') if os.system(cmd)==0: diff --git a/invokeai/frontend/install/model_install.py b/invokeai/frontend/install/model_install.py index a283b4952d..33ef114912 100644 --- a/invokeai/frontend/install/model_install.py +++ b/invokeai/frontend/install/model_install.py @@ -10,72 +10,86 @@ The work is actually done in backend code in model_install_backend.py. """ import argparse -import os +import curses import sys +import textwrap +import traceback from argparse import Namespace +from multiprocessing import Process +from multiprocessing.connection import Connection, Pipe from pathlib import Path from shutil import get_terminal_size -from typing import List +import logging import npyscreen import torch from npyscreen import widget -from omegaconf import OmegaConf -import invokeai.backend.util.logging as logger +from invokeai.backend.util.logging import InvokeAILogger -from ...backend.config.model_install_backend import ( - Dataset_path, - default_config_file, - default_dataset, - get_root, - install_requested_models, - recommended_datasets, +from invokeai.backend.install.model_install_backend import ( + ModelInstallList, + InstallSelections, + ModelInstall, + SchedulerPredictionType, ) -from ...backend.util import choose_precision, choose_torch_device -from .widgets import ( +from invokeai.backend.model_management import ModelManager, ModelType +from invokeai.backend.util import choose_precision, choose_torch_device +from invokeai.frontend.install.widgets import ( CenteredTitleText, MultiSelectColumns, - OffsetButtonPress, + SingleSelectColumns, TextBox, + BufferBox, + FileBox, set_min_terminal_size, + select_stable_diffusion_config_file, + CyclingForm, + MIN_COLS, + MIN_LINES, ) -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig -# minimum size for the UI -MIN_COLS = 120 -MIN_LINES = 45 +config = InvokeAIAppConfig.get_config() +logger = InvokeAILogger.getLogger() -config = get_invokeai_config() +# build a table mapping all non-printable characters to None +# for stripping control characters +# from https://stackoverflow.com/questions/92438/stripping-non-printable-characters-from-a-string-in-python +NOPRINT_TRANS_TABLE = { + i: None for i in range(0, sys.maxunicode + 1) if not chr(i).isprintable() +} -class addModelsForm(npyscreen.FormMultiPage): - # for responsive resizing - disabled - # FIX_MINIMUM_SIZE_WHEN_CREATED = False +def make_printable(s:str)->str: + '''Replace non-printable characters in a string''' + return s.translate(NOPRINT_TRANS_TABLE) + +class addModelsForm(CyclingForm, npyscreen.FormMultiPage): + # for responsive resizing set to False, but this seems to cause a crash! + FIX_MINIMUM_SIZE_WHEN_CREATED = True + + # for persistence + current_tab = 0 def __init__(self, parentApp, name, multipage=False, *args, **keywords): self.multipage = multipage - self.initial_models = OmegaConf.load(Dataset_path) - try: - self.existing_models = OmegaConf.load(default_config_file()) - except: - self.existing_models = dict() - self.starter_model_list = [ - x for x in list(self.initial_models.keys()) if x not in self.existing_models - ] - self.installed_models = dict() + self.subprocess = None super().__init__(parentApp=parentApp, name=name, *args, **keywords) def create(self): + self.keypress_timeout = 10 + self.counter = 0 + self.subprocess_connection = None + + if not config.model_conf_path.exists(): + with open(config.model_conf_path,'w') as file: + print('# InvokeAI model configuration file',file=file) + self.installer = ModelInstall(config) + self.all_models = self.installer.all_models() + self.starter_models = self.installer.starter_models() + self.model_labels = self._get_model_labels() window_width, window_height = get_terminal_size() - starter_model_labels = self._get_starter_model_labels() - recommended_models = [ - x - for x in self.starter_model_list - if self.initial_models[x].get("recommended", False) - ] - self.installed_models = sorted( - [x for x in list(self.initial_models.keys()) if x in self.existing_models] - ) + self.nextrely -= 1 self.add_widget_intelligent( npyscreen.FixedText, @@ -90,180 +104,271 @@ class addModelsForm(npyscreen.FormMultiPage): color="CAUTION", ) self.nextrely += 1 - if len(self.installed_models) > 0: - self.add_widget_intelligent( - CenteredTitleText, - name="== INSTALLED STARTER MODELS ==", - editable=False, - color="CONTROL", - ) - self.nextrely -= 1 - self.add_widget_intelligent( - CenteredTitleText, - name="Currently installed starter models. Uncheck to delete:", - editable=False, - labelColor="CAUTION", - ) - self.nextrely -= 1 - columns = self._get_columns() - self.previously_installed_models = self.add_widget_intelligent( - MultiSelectColumns, - columns=columns, - values=self.installed_models, - value=[x for x in range(0, len(self.installed_models))], - max_height=1 + len(self.installed_models) // columns, - relx=4, - slow_scroll=True, - scroll_exit=True, - ) - self.purge_deleted = self.add_widget_intelligent( - npyscreen.Checkbox, - name="Purge deleted models from disk", - value=False, - scroll_exit=True, - relx=4, - ) + self.tabs = self.add_widget_intelligent( + SingleSelectColumns, + values=[ + 'STARTER MODELS', + 'MORE MODELS', + 'CONTROLNETS', + 'LORA/LYCORIS', + 'TEXTUAL INVERSION', + ], + value=[self.current_tab], + columns = 5, + max_height = 2, + relx=8, + scroll_exit = True, + ) + self.tabs.on_changed = self._toggle_tables + + top_of_table = self.nextrely + self.starter_pipelines = self.add_starter_pipelines() + bottom_of_table = self.nextrely + + self.nextrely = top_of_table + self.pipeline_models = self.add_pipeline_widgets( + model_type=ModelType.Main, + window_width=window_width, + exclude = self.starter_models + ) + # self.pipeline_models['autoload_pending'] = True + bottom_of_table = max(bottom_of_table,self.nextrely) + + self.nextrely = top_of_table + self.controlnet_models = self.add_model_widgets( + model_type=ModelType.ControlNet, + window_width=window_width, + ) + bottom_of_table = max(bottom_of_table,self.nextrely) + + self.nextrely = top_of_table + self.lora_models = self.add_model_widgets( + model_type=ModelType.Lora, + window_width=window_width, + ) + bottom_of_table = max(bottom_of_table,self.nextrely) + + self.nextrely = top_of_table + self.ti_models = self.add_model_widgets( + model_type=ModelType.TextualInversion, + window_width=window_width, + ) + bottom_of_table = max(bottom_of_table,self.nextrely) + + self.nextrely = bottom_of_table+1 + + self.monitor = self.add_widget_intelligent( + BufferBox, + name='Log Messages', + editable=False, + max_height = 10, + ) + self.nextrely += 1 - if len(self.starter_model_list) > 0: - self.add_widget_intelligent( - CenteredTitleText, - name="== STARTER MODELS (recommended ones selected) ==", - editable=False, - color="CONTROL", + done_label = "APPLY CHANGES" + back_label = "BACK" + if self.multipage: + self.back_button = self.add_widget_intelligent( + npyscreen.ButtonPress, + name=back_label, + rely=-3, + when_pressed_function=self.on_back, ) - self.nextrely -= 1 - self.add_widget_intelligent( + else: + self.ok_button = self.add_widget_intelligent( + npyscreen.ButtonPress, + name=done_label, + relx=(window_width - len(done_label)) // 2, + rely=-3, + when_pressed_function=self.on_execute + ) + + label = "APPLY CHANGES & EXIT" + self.done = self.add_widget_intelligent( + npyscreen.ButtonPress, + name=label, + rely=-3, + relx=window_width-len(label)-15, + when_pressed_function=self.on_done, + ) + + # This restores the selected page on return from an installation + for i in range(1,self.current_tab+1): + self.tabs.h_cursor_line_down(1) + self._toggle_tables([self.current_tab]) + + ############# diffusers tab ########## + def add_starter_pipelines(self)->dict[str, npyscreen.widget]: + '''Add widgets responsible for selecting diffusers models''' + widgets = dict() + models = self.all_models + starters = self.starter_models + starter_model_labels = self.model_labels + + self.installed_models = sorted( + [x for x in starters if models[x].installed] + ) + + widgets.update( + label1 = self.add_widget_intelligent( CenteredTitleText, name="Select from a starter set of Stable Diffusion models from HuggingFace.", editable=False, labelColor="CAUTION", ) - self.nextrely -= 1 - # if user has already installed some initial models, then don't patronize them - # by showing more recommendations - show_recommended = not self.existing_models - self.models_selected = self.add_widget_intelligent( - npyscreen.MultiSelect, + ) + + self.nextrely -= 1 + # if user has already installed some initial models, then don't patronize them + # by showing more recommendations + show_recommended = len(self.installed_models)==0 + keys = [x for x in models.keys() if x in starters] + widgets.update( + models_selected = self.add_widget_intelligent( + MultiSelectColumns, + columns=1, name="Install Starter Models", - values=starter_model_labels, + values=[starter_model_labels[x] for x in keys], value=[ - self.starter_model_list.index(x) - for x in self.starter_model_list - if show_recommended and x in recommended_models + keys.index(x) + for x in keys + if (show_recommended and models[x].recommended) \ + or (x in self.installed_models) ], - max_height=len(starter_model_labels) + 1, + max_height=len(starters) + 1, relx=4, scroll_exit=True, - ) - self.add_widget_intelligent( - CenteredTitleText, - name="== IMPORT LOCAL AND REMOTE MODELS ==", - editable=False, - color="CONTROL", + ), + models = keys, ) - self.nextrely -= 1 - for line in [ - "In the box below, enter URLs, file paths, or HuggingFace repository IDs.", - "Separate model names by lines or whitespace (Use shift-control-V to paste):", - ]: - self.add_widget_intelligent( - CenteredTitleText, - name=line, - editable=False, - labelColor="CONTROL", - relx=4, - ) - self.nextrely -= 1 - self.import_model_paths = self.add_widget_intelligent( - TextBox, max_height=7, scroll_exit=True, editable=True, relx=4 - ) self.nextrely += 1 - self.show_directory_fields = self.add_widget_intelligent( - npyscreen.FormControlCheckbox, - name="Select a directory for models to import", - value=False, - ) - self.autoload_directory = self.add_widget_intelligent( - npyscreen.TitleFilename, - name="Directory ( autocompletes):", - select_dir=True, - must_exist=True, - use_two_lines=False, - labelColor="DANGER", - begin_entry_at=34, - scroll_exit=True, - ) - self.autoscan_on_startup = self.add_widget_intelligent( - npyscreen.Checkbox, - name="Scan this directory each time InvokeAI starts for new models to import", - value=False, - relx=4, - scroll_exit=True, - ) - self.cancel = self.add_widget_intelligent( - npyscreen.ButtonPress, - name="CANCEL", - rely=-3, - when_pressed_function=self.on_cancel, - ) - done_label = "DONE" - back_label = "BACK" - button_length = len(done_label) - button_offset = 0 - if self.multipage: - button_length += len(back_label) + 1 - button_offset += len(back_label) + 1 - self.back_button = self.add_widget_intelligent( - OffsetButtonPress, - name=back_label, - relx=(window_width - button_length) // 2, - offset=-3, - rely=-3, - when_pressed_function=self.on_back, + return widgets + + ############# Add a set of model install widgets ######## + def add_model_widgets(self, + model_type: ModelType, + window_width: int=120, + install_prompt: str=None, + exclude: set=set(), + )->dict[str,npyscreen.widget]: + '''Generic code to create model selection widgets''' + widgets = dict() + model_list = [x for x in self.all_models if self.all_models[x].model_type==model_type and not x in exclude] + model_labels = [self.model_labels[x] for x in model_list] + if len(model_list) > 0: + max_width = max([len(x) for x in model_labels]) + columns = window_width // (max_width+8) # 8 characters for "[x] " and padding + columns = min(len(model_list),columns) or 1 + prompt = install_prompt or f"Select the desired {model_type.value.title()} models to install. Unchecked models will be purged from disk." + + widgets.update( + label1 = self.add_widget_intelligent( + CenteredTitleText, + name=prompt, + editable=False, + labelColor="CAUTION", + ) ) - self.ok_button = self.add_widget_intelligent( - OffsetButtonPress, - name=done_label, - offset=+3, - relx=button_offset + 1 + (window_width - button_length) // 2, - rely=-3, - when_pressed_function=self.on_ok, + + widgets.update( + models_selected = self.add_widget_intelligent( + MultiSelectColumns, + columns=columns, + name=f"Install {model_type} Models", + values=model_labels, + value=[ + model_list.index(x) + for x in model_list + if self.all_models[x].installed + ], + max_height=len(model_list)//columns + 1, + relx=4, + scroll_exit=True, + ), + models = model_list, + ) + + self.nextrely += 1 + widgets.update( + download_ids = self.add_widget_intelligent( + TextBox, + name = "Additional URLs, or HuggingFace repo_ids to install (Space separated. Use shift-control-V to paste):", + max_height=4, + scroll_exit=True, + editable=True, + ) + ) + return widgets + + ### Tab for arbitrary diffusers widgets ### + def add_pipeline_widgets(self, + model_type: ModelType=ModelType.Main, + window_width: int=120, + **kwargs, + )->dict[str,npyscreen.widget]: + '''Similar to add_model_widgets() but adds some additional widgets at the bottom + to support the autoload directory''' + widgets = self.add_model_widgets( + model_type = model_type, + window_width = window_width, + install_prompt=f"Additional {model_type.value.title()} models already installed.", + **kwargs, ) - for i in [self.autoload_directory, self.autoscan_on_startup]: - self.show_directory_fields.addVisibleWhenSelected(i) - - self.show_directory_fields.when_value_edited = self._clear_scan_directory + return widgets def resize(self): super().resize() - if hasattr(self, "models_selected"): - self.models_selected.values = self._get_starter_model_labels() + if (s := self.starter_pipelines.get("models_selected")): + keys = [x for x in self.all_models.keys() if x in self.starter_models] + s.values = [self.model_labels[x] for x in keys] - def _clear_scan_directory(self): - if not self.show_directory_fields.value: - self.autoload_directory.value = "" + def _toggle_tables(self, value=None): + selected_tab = value[0] + widgets = [ + self.starter_pipelines, + self.pipeline_models, + self.controlnet_models, + self.lora_models, + self.ti_models, + ] - def _get_starter_model_labels(self) -> List[str]: + for group in widgets: + for k,v in group.items(): + try: + v.hidden = True + v.editable = False + except: + pass + for k,v in widgets[selected_tab].items(): + try: + v.hidden = False + if not isinstance(v,(npyscreen.FixedText, npyscreen.TitleFixedText, CenteredTitleText)): + v.editable = True + except: + pass + self.__class__.current_tab = selected_tab # for persistence + self.display() + + def _get_model_labels(self) -> dict[str,str]: window_width, window_height = get_terminal_size() - label_width = 25 checkbox_width = 4 spacing_width = 2 + + models = self.all_models + label_width = max([len(models[x].name) for x in models]) description_width = window_width - label_width - checkbox_width - spacing_width - im = self.initial_models - names = self.starter_model_list - descriptions = [ - im[x].description[0 : description_width - 3] + "..." - if len(im[x].description) > description_width - else im[x].description - for x in names - ] - return [ - f"%-{label_width}s %s" % (names[x], descriptions[x]) - for x in range(0, len(names)) - ] + result = dict() + for x in models.keys(): + description = models[x].description + description = description[0 : description_width - 3] + "..." \ + if description and len(description) > description_width \ + else description if description else '' + result[x] = f"%-{label_width}s %s" % (models[x].name, description) + return result + def _get_columns(self) -> int: window_width, window_height = get_terminal_size() cols = ( @@ -277,24 +382,113 @@ class addModelsForm(npyscreen.FormMultiPage): ) return min(cols, len(self.installed_models)) - def on_ok(self): - self.parentApp.setNextForm(None) - self.editing = False - self.parentApp.user_cancelled = False + def on_execute(self): + self.monitor.entry_widget.buffer(['Processing...'],scroll_end=True) self.marshall_arguments() + app = self.parentApp + self.ok_button.hidden = True + self.display() + + # for communication with the subprocess + parent_conn, child_conn = Pipe() + p = Process( + target = process_and_execute, + kwargs=dict( + opt = app.program_opts, + selections = app.install_selections, + conn_out = child_conn, + ) + ) + p.start() + child_conn.close() + self.subprocess_connection = parent_conn + self.subprocess = p + app.install_selections = InstallSelections() + # process_and_execute(app.opt, app.install_selections) def on_back(self): self.parentApp.switchFormPrevious() self.editing = False def on_cancel(self): - if npyscreen.notify_yes_no( - "Are you sure you want to cancel?\nYou may re-run this script later using the invoke.sh or invoke.bat command.\n" - ): - self.parentApp.setNextForm(None) - self.parentApp.user_cancelled = True - self.editing = False + self.parentApp.setNextForm(None) + self.parentApp.user_cancelled = True + self.editing = False + + def on_done(self): + self.marshall_arguments() + self.parentApp.setNextForm(None) + self.parentApp.user_cancelled = False + self.editing = False + + ########## This routine monitors the child process that is performing model installation and removal ##### + def while_waiting(self): + '''Called during idle periods. Main task is to update the Log Messages box with messages + from the child process that does the actual installation/removal''' + c = self.subprocess_connection + if not c: + return + + monitor_widget = self.monitor.entry_widget + while c.poll(): + try: + data = c.recv_bytes().decode('utf-8') + data.strip('\n') + # processing child is requesting user input to select the + # right configuration file + if data.startswith('*need v2 config'): + _,model_path,*_ = data.split(":",2) + self._return_v2_config(model_path) + + # processing child is done + elif data=='*done*': + self._close_subprocess_and_regenerate_form() + break + + # update the log message box + else: + data=make_printable(data) + data=data.replace('[A','') + monitor_widget.buffer( + textwrap.wrap(data, + width=monitor_widget.width, + subsequent_indent=' ', + ), + scroll_end=True + ) + self.display() + except (EOFError,OSError): + self.subprocess_connection = None + + def _return_v2_config(self,model_path: str): + c = self.subprocess_connection + model_name = Path(model_path).name + message = select_stable_diffusion_config_file(model_name=model_name) + c.send_bytes(message.encode('utf-8')) + + def _close_subprocess_and_regenerate_form(self): + app = self.parentApp + self.subprocess_connection.close() + self.subprocess_connection = None + self.monitor.entry_widget.buffer(['** Action Complete **']) + self.display() + + # rebuild the form, saving and restoring some of the fields that need to be preserved. + saved_messages = self.monitor.entry_widget.values + # autoload_dir = str(config.root_path / self.pipeline_models['autoload_directory'].value) + # autoscan = self.pipeline_models['autoscan_on_startup'].value + + app.main_form = app.addForm( + "MAIN", addModelsForm, name="Install Stable Diffusion Models", multipage=self.multipage, + ) + app.switchForm("MAIN") + + app.main_form.monitor.entry_widget.values = saved_messages + app.main_form.monitor.entry_widget.buffer([''],scroll_end=True) + # app.main_form.pipeline_models['autoload_directory'].value = autoload_dir + # app.main_form.pipeline_models['autoscan_on_startup'].value = autoscan + def marshall_arguments(self): """ Assemble arguments and store as attributes of the application: @@ -305,87 +499,152 @@ class addModelsForm(npyscreen.FormMultiPage): .autoscan_on_startup: True if invokeai should scan and import at startup time .import_model_paths: list of URLs, repo_ids and file paths to import """ - # we're using a global here rather than storing the result in the parentapp - # due to some bug in npyscreen that is causing attributes to be lost - selections = self.parentApp.user_selections + selections = self.parentApp.install_selections + all_models = self.all_models - # starter models to install/remove - if hasattr(self, "models_selected"): - starter_models = dict( - map( - lambda x: (self.starter_model_list[x], True), - self.models_selected.value, - ) - ) - else: - starter_models = dict() - selections.purge_deleted_models = False - if hasattr(self, "previously_installed_models"): - unchecked = [ - self.previously_installed_models.values[x] - for x in range(0, len(self.previously_installed_models.values)) - if x not in self.previously_installed_models.value - ] - starter_models.update(map(lambda x: (x, False), unchecked)) - selections.purge_deleted_models = self.purge_deleted.value - selections.starter_models = starter_models + # Defined models (in INITIAL_CONFIG.yaml or models.yaml) to add/remove + ui_sections = [self.starter_pipelines, self.pipeline_models, + self.controlnet_models, self.lora_models, self.ti_models] + for section in ui_sections: + if not 'models_selected' in section: + continue + selected = set([section['models'][x] for x in section['models_selected'].value]) + models_to_install = [x for x in selected if not self.all_models[x].installed] + models_to_remove = [x for x in section['models'] if x not in selected and self.all_models[x].installed] + selections.remove_models.extend(models_to_remove) + selections.install_models.extend(all_models[x].path or all_models[x].repo_id \ + for x in models_to_install if all_models[x].path or all_models[x].repo_id) + + # models located in the 'download_ids" section + for section in ui_sections: + if downloads := section.get('download_ids'): + selections.install_models.extend(downloads.value.split()) # load directory and whether to scan on startup - if self.show_directory_fields.value: - selections.scan_directory = self.autoload_directory.value - selections.autoscan_on_startup = self.autoscan_on_startup.value - else: - selections.scan_directory = None - selections.autoscan_on_startup = False - - # URLs and the like - selections.import_model_paths = self.import_model_paths.value.split() - + # if self.parentApp.autoload_pending: + # selections.scan_directory = str(config.root_path / self.pipeline_models['autoload_directory'].value) + # self.parentApp.autoload_pending = False + # selections.autoscan_on_startup = self.pipeline_models['autoscan_on_startup'].value class AddModelApplication(npyscreen.NPSAppManaged): - def __init__(self): + def __init__(self,opt): super().__init__() + self.program_opts = opt self.user_cancelled = False - self.user_selections = Namespace( - starter_models=None, - purge_deleted_models=False, - scan_directory=None, - autoscan_on_startup=None, - import_model_paths=None, - ) + # self.autoload_pending = True + self.install_selections = InstallSelections() def onStart(self): npyscreen.setTheme(npyscreen.Themes.DefaultTheme) self.main_form = self.addForm( - "MAIN", addModelsForm, name="Install Stable Diffusion Models" + "MAIN", addModelsForm, name="Install Stable Diffusion Models", cycle_widgets=True, ) +class StderrToMessage(): + def __init__(self, connection: Connection): + self.connection = connection + + def write(self, data:str): + self.connection.send_bytes(data.encode('utf-8')) + + def flush(self): + pass # -------------------------------------------------------- -def process_and_execute(opt: Namespace, selections: Namespace): - models_to_remove = [ - x for x in selections.starter_models if not selections.starter_models[x] - ] - models_to_install = [ - x for x in selections.starter_models if selections.starter_models[x] - ] - directory_to_scan = selections.scan_directory - scan_at_startup = selections.autoscan_on_startup - potential_models_to_install = selections.import_model_paths +def ask_user_for_prediction_type(model_path: Path, + tui_conn: Connection=None + )->SchedulerPredictionType: + if tui_conn: + logger.debug('Waiting for user response...') + return _ask_user_for_pt_tui(model_path, tui_conn) + else: + return _ask_user_for_pt_cmdline(model_path) - install_requested_models( - install_initial_models=models_to_install, - remove_models=models_to_remove, - scan_directory=Path(directory_to_scan) if directory_to_scan else None, - external_models=potential_models_to_install, - scan_at_startup=scan_at_startup, - precision="float32" - if opt.full_precision - else choose_precision(torch.device(choose_torch_device())), - purge_deleted=selections.purge_deleted_models, - config_file_path=Path(opt.config_file) if opt.config_file else None, - ) +def _ask_user_for_pt_cmdline(model_path: Path)->SchedulerPredictionType: + choices = [SchedulerPredictionType.Epsilon, SchedulerPredictionType.VPrediction, None] + print( +f""" +Please select the type of the V2 checkpoint named {model_path.name}: +[1] A model based on Stable Diffusion v2 trained on 512 pixel images (SD-2-base) +[2] A model based on Stable Diffusion v2 trained on 768 pixel images (SD-2-768) +[3] Skip this model and come back later. +""" + ) + choice = None + ok = False + while not ok: + try: + choice = input('select> ').strip() + choice = choices[int(choice)-1] + ok = True + except (ValueError, IndexError): + print(f'{choice} is not a valid choice') + except EOFError: + return + return choice + +def _ask_user_for_pt_tui(model_path: Path, tui_conn: Connection)->SchedulerPredictionType: + try: + tui_conn.send_bytes(f'*need v2 config for:{model_path}'.encode('utf-8')) + # note that we don't do any status checking here + response = tui_conn.recv_bytes().decode('utf-8') + if response is None: + return None + elif response == 'epsilon': + return SchedulerPredictionType.epsilon + elif response == 'v': + return SchedulerPredictionType.VPrediction + elif response == 'abort': + logger.info('Conversion aborted') + return None + else: + return response + except: + return None + +# -------------------------------------------------------- +def process_and_execute(opt: Namespace, + selections: InstallSelections, + conn_out: Connection=None, + ): + # set up so that stderr is sent to conn_out + if conn_out: + translator = StderrToMessage(conn_out) + sys.stderr = translator + sys.stdout = translator + logger = InvokeAILogger.getLogger() + logger.handlers.clear() + logger.addHandler(logging.StreamHandler(translator)) + installer = ModelInstall(config, prediction_type_helper=lambda x: ask_user_for_prediction_type(x,conn_out)) + installer.install(selections) + + if conn_out: + conn_out.send_bytes('*done*'.encode('utf-8')) + conn_out.close() + +def do_listings(opt)->bool: + """List installed models of various sorts, and return + True if any were requested.""" + model_manager = ModelManager(config.model_conf_path) + if opt.list_models == 'diffusers': + print("Diffuser models:") + model_manager.print_models() + elif opt.list_models == 'controlnets': + print("Installed Controlnet Models:") + cnm = model_manager.list_controlnet_models() + print(textwrap.indent("\n".join([x for x in cnm if cnm[x]]),prefix=' ')) + elif opt.list_models == 'loras': + print("Installed LoRA/LyCORIS Models:") + cnm = model_manager.list_lora_models() + print(textwrap.indent("\n".join([x for x in cnm if cnm[x]]),prefix=' ')) + elif opt.list_models == 'tis': + print("Installed Textual Inversion Embeddings:") + cnm = model_manager.list_ti_models() + print(textwrap.indent("\n".join([x for x in cnm if cnm[x]]),prefix=' ')) + else: + return False + return True # -------------------------------------------------------- def select_and_download_models(opt: Namespace): @@ -394,28 +653,64 @@ def select_and_download_models(opt: Namespace): if opt.full_precision else choose_precision(torch.device(choose_torch_device())) ) - if opt.default_only: - install_requested_models( - install_initial_models=default_dataset(), - precision=precision, + config.precision = precision + helper = lambda x: ask_user_for_prediction_type(x) + # if do_listings(opt): + # pass + + installer = ModelInstall(config, prediction_type_helper=helper) + if opt.add or opt.delete: + selections = InstallSelections( + install_models = opt.add or [], + remove_models = opt.delete or [] ) + installer.install(selections) + elif opt.default_only: + selections = InstallSelections( + install_models = installer.default_model() + ) + installer.install(selections) elif opt.yes_to_all: - install_requested_models( - install_initial_models=recommended_datasets(), - precision=precision, + selections = InstallSelections( + install_models = installer.recommended_models() ) + installer.install(selections) + + # this is where the TUI is called else: - set_min_terminal_size(MIN_COLS, MIN_LINES) - installApp = AddModelApplication() - installApp.run() - - if not installApp.user_cancelled: - process_and_execute(opt, installApp.user_selections) + # needed because the torch library is loaded, even though we don't use it + # currently commented out because it has started generating errors (?) + # torch.multiprocessing.set_start_method("spawn") + # the third argument is needed in the Windows 11 environment in + # order to launch and resize a console window running this program + set_min_terminal_size(MIN_COLS, MIN_LINES,'invokeai-model-install') + installApp = AddModelApplication(opt) + try: + installApp.run() + except KeyboardInterrupt as e: + if hasattr(installApp,'main_form'): + if installApp.main_form.subprocess \ + and installApp.main_form.subprocess.is_alive(): + logger.info('Terminating subprocesses') + installApp.main_form.subprocess.terminate() + installApp.main_form.subprocess = None + raise e + process_and_execute(opt, installApp.install_selections) # ------------------------------------- def main(): parser = argparse.ArgumentParser(description="InvokeAI model downloader") + parser.add_argument( + "--add", + nargs="*", + help="List of URLs, local paths or repo_ids of models to install", + ) + parser.add_argument( + "--delete", + nargs="*", + help="List of names of models to idelete", + ) parser.add_argument( "--full-precision", dest="full_precision", @@ -434,7 +729,12 @@ def main(): parser.add_argument( "--default_only", action="store_true", - help="only install the default model", + help="Only install the default model", + ) + parser.add_argument( + "--list-models", + choices=["diffusers","loras","controlnets","tis"], + help="list installed models", ) parser.add_argument( "--config_file", @@ -452,11 +752,16 @@ def main(): help="path to root of install directory", ) opt = parser.parse_args() + + invoke_args = [] + if opt.root: + invoke_args.extend(['--root',opt.root]) + if opt.full_precision: + invoke_args.extend(['--precision','float32']) + config.parse_args(invoke_args) + logger = InvokeAILogger().getLogger(config=config) - # setting a global here - config.root = os.path.expanduser(get_root(opt.root) or "") - - if not (config.conf_path / '..' ).exists(): + if not (config.root_dir / config.conf_path.parent).exists(): logger.info( "Your InvokeAI root directory is not set up. Calling invokeai-configure." ) @@ -471,17 +776,26 @@ def main(): logger.error(e) sys.exit(-1) except KeyboardInterrupt: + curses.nocbreak() + curses.echo() + curses.endwin() logger.info("Goodbye! Come back soon.") except widget.NotEnoughSpaceForWidget as e: if str(e).startswith("Height of 1 allocated"): logger.error( "Insufficient vertical space for the interface. Please make your window taller and try again" ) - elif str(e).startswith("addwstr"): + input('Press any key to continue...') + except Exception as e: + if str(e).startswith("addwstr"): logger.error( "Insufficient horizontal space for the interface. Please make your window wider and try again." ) - + else: + print(f'An exception has occurred: {str(e)} Details:') + print(traceback.format_exc(), file=sys.stderr) + input('Press any key to continue...') + # ------------------------------------- if __name__ == "__main__": diff --git a/invokeai/frontend/install/widgets.py b/invokeai/frontend/install/widgets.py index 6c57b7cbd2..fcfc29de67 100644 --- a/invokeai/frontend/install/widgets.py +++ b/invokeai/frontend/install/widgets.py @@ -5,35 +5,99 @@ import curses import math import os import platform +import pyperclip import struct +import subprocess import sys -from shutil import get_terminal_size - import npyscreen +import textwrap +import npyscreen.wgmultiline as wgmultiline +from npyscreen import fmPopup +from shutil import get_terminal_size +from curses import BUTTON2_CLICKED,BUTTON3_CLICKED +# minimum size for UIs +MIN_COLS = 130 +MIN_LINES = 40 # ------------------------------------- -def set_terminal_size(columns: int, lines: int): +def set_terminal_size(columns: int, lines: int, launch_command: str=None): + ts = get_terminal_size() + width = max(columns,ts.columns) + height = max(lines,ts.lines) + OS = platform.uname().system if OS == "Windows": - os.system(f"mode con: cols={columns} lines={lines}") + # The new Windows Terminal doesn't resize, so we relaunch in a CMD window. + # Would prefer to use execvpe() here, but somehow it is not working properly + # in the Windows 10 environment. + if 'IA_RELAUNCHED' not in os.environ: + args=['conhost'] + args.extend([launch_command] if launch_command else [sys.argv[0]]) + args.extend(sys.argv[1:]) + os.environ['IA_RELAUNCHED'] = 'True' + os.execvp('conhost',args) + else: + _set_terminal_size_powershell(width,height) elif OS in ["Darwin", "Linux"]: - import fcntl - import termios + _set_terminal_size_unix(width,height) - winsize = struct.pack("HHHH", lines, columns, 0, 0) - fcntl.ioctl(sys.stdout.fileno(), termios.TIOCSWINSZ, winsize) - sys.stdout.write("\x1b[8;{rows};{cols}t".format(rows=lines, cols=columns)) - sys.stdout.flush() + # check whether it worked.... + ts = get_terminal_size() + pause = False + if ts.columns < columns: + print('\033[1mThis window is too narrow for the user interface. Please make it wider.\033[0m') + pause = True + if ts.lines < lines: + print('\033[1mThis window is too short for the user interface. Please make it taller.\033[0m') + pause = True + if pause: + input('Press any key to continue..') +def _set_terminal_size_powershell(width: int, height: int): + script=f''' +$pshost = get-host +$pswindow = $pshost.ui.rawui +$newsize = $pswindow.buffersize +$newsize.height = 3000 +$newsize.width = {width} +$pswindow.buffersize = $newsize +$newsize = $pswindow.windowsize +$newsize.height = {height} +$newsize.width = {width} +$pswindow.windowsize = $newsize +''' + subprocess.run(["powershell","-Command","-"],input=script,text=True) -def set_min_terminal_size(min_cols: int, min_lines: int): +def _set_terminal_size_unix(width: int, height: int): + import fcntl + import termios + + # These terminals accept the size command and report that the + # size changed, but they lie!!! + for bad_terminal in ['TERMINATOR_UUID', 'ALACRITTY_WINDOW_ID']: + if os.environ.get(bad_terminal): + return + + winsize = struct.pack("HHHH", height, width, 0, 0) + fcntl.ioctl(sys.stdout.fileno(), termios.TIOCSWINSZ, winsize) + sys.stdout.write("\x1b[8;{height};{width}t".format(height=height, width=width)) + sys.stdout.flush() + +def set_min_terminal_size(min_cols: int, min_lines: int, launch_command: str=None): # make sure there's enough room for the ui term_cols, term_lines = get_terminal_size() + if term_cols >= min_cols and term_lines >= min_lines: + return cols = max(term_cols, min_cols) lines = max(term_lines, min_lines) - set_terminal_size(cols, lines) + set_terminal_size(cols, lines, launch_command) + # did it work? + term_cols, term_lines = get_terminal_size() + if term_cols < cols or term_lines < lines: + print(f'This window is too small for optimal display. For best results please enlarge it.') + input('After resizing, press any key to continue...') class IntSlider(npyscreen.Slider): def translate_value(self): @@ -42,7 +106,23 @@ class IntSlider(npyscreen.Slider): stri = stri.rjust(l) return stri - +# ------------------------------------- +# fix npyscreen form so that cursor wraps both forward and backward +class CyclingForm(object): + def find_previous_editable(self, *args): + done = False + n = self.editw-1 + while not done: + if self._widgets__[n].editable and not self._widgets__[n].hidden: + self.editw = n + done = True + n -= 1 + if n<0: + if self.cycle_widgets: + n = len(self._widgets__)-1 + else: + done = True + # ------------------------------------- class CenteredTitleText(npyscreen.TitleText): def __init__(self, *args, **keywords): @@ -93,14 +173,7 @@ class FloatSlider(npyscreen.Slider): class FloatTitleSlider(npyscreen.TitleText): _entry_type = FloatSlider - -class MultiSelectColumns(npyscreen.MultiSelect): - def __init__(self, screen, columns: int = 1, values: list = [], **keywords): - self.columns = columns - self.value_cnt = len(values) - self.rows = math.ceil(self.value_cnt / self.columns) - super().__init__(screen, values=values, **keywords) - +class SelectColumnBase(): def make_contained_widgets(self): self._my_widgets = [] column_width = self.width // self.columns @@ -150,53 +223,223 @@ class MultiSelectColumns(npyscreen.MultiSelect): def h_cursor_line_right(self, ch): super().h_cursor_line_down(ch) + def handle_mouse_event(self, mouse_event): + mouse_id, rel_x, rel_y, z, bstate = self.interpret_mouse_event(mouse_event) + column_width = self.width // self.columns + column_height = math.ceil(self.value_cnt / self.columns) + column_no = rel_x // column_width + row_no = rel_y // self._contained_widget_height + self.cursor_line = column_no * column_height + row_no + if bstate & curses.BUTTON1_DOUBLE_CLICKED: + if hasattr(self,'on_mouse_double_click'): + self.on_mouse_double_click(self.cursor_line) + self.display() -class TextBox(npyscreen.MultiLineEdit): - def update(self, clear=True): - if clear: - self.clear() +class MultiSelectColumns( SelectColumnBase, npyscreen.MultiSelect): + def __init__(self, screen, columns: int = 1, values: list = [], **keywords): + self.columns = columns + self.value_cnt = len(values) + self.rows = math.ceil(self.value_cnt / self.columns) + super().__init__(screen, values=values, **keywords) - HEIGHT = self.height - WIDTH = self.width - # draw box. - self.parent.curses_pad.hline(self.rely, self.relx, curses.ACS_HLINE, WIDTH) - self.parent.curses_pad.hline( - self.rely + HEIGHT, self.relx, curses.ACS_HLINE, WIDTH - ) - self.parent.curses_pad.vline( - self.rely, self.relx, curses.ACS_VLINE, self.height - ) - self.parent.curses_pad.vline( - self.rely, self.relx + WIDTH, curses.ACS_VLINE, HEIGHT - ) + def on_mouse_double_click(self, cursor_line): + self.h_select_toggle(cursor_line) - # draw corners - self.parent.curses_pad.addch( - self.rely, - self.relx, - curses.ACS_ULCORNER, - ) - self.parent.curses_pad.addch( - self.rely, - self.relx + WIDTH, - curses.ACS_URCORNER, - ) - self.parent.curses_pad.addch( - self.rely + HEIGHT, - self.relx, - curses.ACS_LLCORNER, - ) - self.parent.curses_pad.addch( - self.rely + HEIGHT, - self.relx + WIDTH, - curses.ACS_LRCORNER, - ) +class SingleSelectWithChanged(npyscreen.SelectOne): + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) - # fool our superclass into thinking drawing area is smaller - this is really hacky but it seems to work - (relx, rely, height, width) = (self.relx, self.rely, self.height, self.width) - self.relx += 1 - self.rely += 1 - self.height -= 1 - self.width -= 1 - super().update(clear=False) - (self.relx, self.rely, self.height, self.width) = (relx, rely, height, width) + def h_select(self,ch): + super().h_select(ch) + if self.on_changed: + self.on_changed(self.value) + +class SingleSelectColumns(SelectColumnBase, SingleSelectWithChanged): + def __init__(self, screen, columns: int = 1, values: list = [], **keywords): + self.columns = columns + self.value_cnt = len(values) + self.rows = math.ceil(self.value_cnt / self.columns) + self.on_changed = None + super().__init__(screen, values=values, **keywords) + + def when_value_edited(self): + self.h_select(self.cursor_line) + + def when_cursor_moved(self): + self.h_select(self.cursor_line) + + def h_cursor_line_right(self,ch): + self.h_exit_down('bye bye') + +class TextBoxInner(npyscreen.MultiLineEdit): + + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + self.yank = None + self.handlers.update({ + "^A": self.h_cursor_to_start, + "^E": self.h_cursor_to_end, + "^K": self.h_kill, + "^F": self.h_cursor_right, + "^B": self.h_cursor_left, + "^Y": self.h_yank, + "^V": self.h_paste, + }) + + def h_cursor_to_start(self, input): + self.cursor_position = 0 + + def h_cursor_to_end(self, input): + self.cursor_position = len(self.value) + + def h_kill(self, input): + self.yank = self.value[self.cursor_position:] + self.value = self.value[:self.cursor_position] + + def h_yank(self, input): + if self.yank: + self.paste(self.yank) + + def paste(self, text: str): + self.value = self.value[:self.cursor_position] + text + self.value[self.cursor_position:] + self.cursor_position += len(text) + + def h_paste(self, input: int=0): + try: + text = pyperclip.paste() + except ModuleNotFoundError: + text = "To paste with the mouse on Linux, please install the 'xclip' program." + self.paste(text) + + def handle_mouse_event(self, mouse_event): + mouse_id, rel_x, rel_y, z, bstate = self.interpret_mouse_event(mouse_event) + if bstate & (BUTTON2_CLICKED|BUTTON3_CLICKED): + self.h_paste() + + # def update(self, clear=True): + # if clear: + # self.clear() + + # HEIGHT = self.height + # WIDTH = self.width + # # draw box. + # self.parent.curses_pad.hline(self.rely, self.relx, curses.ACS_HLINE, WIDTH) + # self.parent.curses_pad.hline( + # self.rely + HEIGHT, self.relx, curses.ACS_HLINE, WIDTH + # ) + # self.parent.curses_pad.vline( + # self.rely, self.relx, curses.ACS_VLINE, self.height + # ) + # self.parent.curses_pad.vline( + # self.rely, self.relx + WIDTH, curses.ACS_VLINE, HEIGHT + # ) + + # # draw corners + # self.parent.curses_pad.addch( + # self.rely, + # self.relx, + # curses.ACS_ULCORNER, + # ) + # self.parent.curses_pad.addch( + # self.rely, + # self.relx + WIDTH, + # curses.ACS_URCORNER, + # ) + # self.parent.curses_pad.addch( + # self.rely + HEIGHT, + # self.relx, + # curses.ACS_LLCORNER, + # ) + # self.parent.curses_pad.addch( + # self.rely + HEIGHT, + # self.relx + WIDTH, + # curses.ACS_LRCORNER, + # ) + + # # fool our superclass into thinking drawing area is smaller - this is really hacky but it seems to work + # (relx, rely, height, width) = (self.relx, self.rely, self.height, self.width) + # self.relx += 1 + # self.rely += 1 + # self.height -= 1 + # self.width -= 1 + # super().update(clear=False) + # (self.relx, self.rely, self.height, self.width) = (relx, rely, height, width) + +class TextBox(npyscreen.BoxTitle): + _contained_widget = TextBoxInner + +class BufferBox(npyscreen.BoxTitle): + _contained_widget = npyscreen.BufferPager + +class ConfirmCancelPopup(fmPopup.ActionPopup): + DEFAULT_COLUMNS = 100 + def on_ok(self): + self.value = True + def on_cancel(self): + self.value = False + +class FileBox(npyscreen.BoxTitle): + _contained_widget = npyscreen.Filename + +class PrettyTextBox(npyscreen.BoxTitle): + _contained_widget = TextBox + +def _wrap_message_lines(message, line_length): + lines = [] + for line in message.split('\n'): + lines.extend(textwrap.wrap(line.rstrip(), line_length)) + return lines + +def _prepare_message(message): + if isinstance(message, list) or isinstance(message, tuple): + return "\n".join([ s.rstrip() for s in message]) + #return "\n".join(message) + else: + return message + +def select_stable_diffusion_config_file( + form_color: str='DANGER', + wrap:bool =True, + model_name:str='Unknown', +): + message = f"Please select the correct base model for the V2 checkpoint named '{model_name}'. Press to skip installation." + title = "CONFIG FILE SELECTION" + options=[ + "An SD v2.x base model (512 pixels; no 'parameterization:' line in its yaml file)", + "An SD v2.x v-predictive model (768 pixels; 'parameterization: \"v\"' line in its yaml file)", + "Skip installation for now and come back later", + ] + + F = ConfirmCancelPopup( + name=title, + color=form_color, + cycle_widgets=True, + lines=16, + ) + F.preserve_selected_widget = True + + mlw = F.add( + wgmultiline.Pager, + max_height=4, + editable=False, + ) + mlw_width = mlw.width-1 + if wrap: + message = _wrap_message_lines(message, mlw_width) + mlw.values = message + + choice = F.add( + npyscreen.SelectOne, + values = options, + value = [0], + max_height = len(options)+1, + scroll_exit=True, + ) + + F.editw = 1 + F.edit() + if not F.value: + return None + assert choice.value[0] in range(0,3),'invalid choice' + choices = ['epsilon','v','abort'] + return choices[choice.value[0]] diff --git a/invokeai/frontend/merge/merge_diffusers.py b/invokeai/frontend/merge/merge_diffusers.py index 882a4587b6..9da04b97f8 100644 --- a/invokeai/frontend/merge/merge_diffusers.py +++ b/invokeai/frontend/merge/merge_diffusers.py @@ -20,12 +20,12 @@ from npyscreen import widget from omegaconf import OmegaConf import invokeai.backend.util.logging as logger -from invokeai.services.config import get_invokeai_config +from invokeai.services.config import InvokeAIAppConfig from ...backend.model_management import ModelManager from ...frontend.install.widgets import FloatTitleSlider DEST_MERGED_MODEL_DIR = "merged_models" -config = get_invokeai_config() +config = InvokeAIAppConfig.get_config() def merge_diffusion_models( model_ids_or_paths: List[Union[str, Path]], diff --git a/invokeai/frontend/training/textual_inversion.py b/invokeai/frontend/training/textual_inversion.py index 90e402f48b..e1c7b3749f 100755 --- a/invokeai/frontend/training/textual_inversion.py +++ b/invokeai/frontend/training/textual_inversion.py @@ -22,7 +22,7 @@ from omegaconf import OmegaConf import invokeai.backend.util.logging as logger -from invokeai.app.services.config import get_invokeai_config +from invokeai.app.services.config import InvokeAIAppConfig from ...backend.training import ( do_textual_inversion_training, parse_args @@ -423,7 +423,7 @@ def do_front_end(args: Namespace): save_args(args) try: - do_textual_inversion_training(get_invokeai_config(),**args) + do_textual_inversion_training(InvokeAIAppConfig.get_config(),**args) copy_to_embeddings_folder(args) except Exception as e: logger.error("An exception occurred during training. The exception was:") @@ -436,7 +436,7 @@ def main(): global config args = parse_args() - config = get_invokeai_config(argv=[]) + config = InvokeAIAppConfig.get_config() # change root if needed if args.root_dir: diff --git a/invokeai/frontend/web/.eslintignore b/invokeai/frontend/web/.eslintignore index b351fc6a96..699cb06987 100644 --- a/invokeai/frontend/web/.eslintignore +++ b/invokeai/frontend/web/.eslintignore @@ -1,4 +1,5 @@ dist/ +static/ .husky/ node_modules/ patches/ diff --git a/invokeai/frontend/web/config/common.ts b/invokeai/frontend/web/config/common.ts new file mode 100644 index 0000000000..4470224225 --- /dev/null +++ b/invokeai/frontend/web/config/common.ts @@ -0,0 +1,12 @@ +import react from '@vitejs/plugin-react-swc'; +import { visualizer } from 'rollup-plugin-visualizer'; +import { PluginOption, UserConfig } from 'vite'; +import eslint from 'vite-plugin-eslint'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export const commonPlugins: UserConfig['plugins'] = [ + react(), + eslint(), + tsconfigPaths(), + visualizer() as unknown as PluginOption, +]; diff --git a/invokeai/frontend/web/config/vite.app.config.ts b/invokeai/frontend/web/config/vite.app.config.ts index e6c4df79fd..958313402a 100644 --- a/invokeai/frontend/web/config/vite.app.config.ts +++ b/invokeai/frontend/web/config/vite.app.config.ts @@ -1,17 +1,9 @@ -import react from '@vitejs/plugin-react-swc'; -import { visualizer } from 'rollup-plugin-visualizer'; -import { PluginOption, UserConfig } from 'vite'; -import eslint from 'vite-plugin-eslint'; -import tsconfigPaths from 'vite-tsconfig-paths'; +import { UserConfig } from 'vite'; +import { commonPlugins } from './common'; export const appConfig: UserConfig = { base: './', - plugins: [ - react(), - eslint(), - tsconfigPaths(), - visualizer() as unknown as PluginOption, - ], + plugins: [...commonPlugins], build: { chunkSizeWarningLimit: 1500, }, diff --git a/invokeai/frontend/web/config/vite.package.config.ts b/invokeai/frontend/web/config/vite.package.config.ts index f87cce0bc9..0dcccab086 100644 --- a/invokeai/frontend/web/config/vite.package.config.ts +++ b/invokeai/frontend/web/config/vite.package.config.ts @@ -1,19 +1,13 @@ -import react from '@vitejs/plugin-react-swc'; import path from 'path'; -import { visualizer } from 'rollup-plugin-visualizer'; -import { PluginOption, UserConfig } from 'vite'; +import { UserConfig } from 'vite'; import dts from 'vite-plugin-dts'; -import eslint from 'vite-plugin-eslint'; -import tsconfigPaths from 'vite-tsconfig-paths'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; +import { commonPlugins } from './common'; export const packageConfig: UserConfig = { base: './', plugins: [ - react(), - eslint(), - tsconfigPaths(), - visualizer() as unknown as PluginOption, + ...commonPlugins, dts({ insertTypesEntry: true, }), diff --git a/invokeai/frontend/web/dist/index.html b/invokeai/frontend/web/dist/index.html index a41ad12aa3..6c4c1c21ae 100644 --- a/invokeai/frontend/web/dist/index.html +++ b/invokeai/frontend/web/dist/index.html @@ -12,7 +12,7 @@ margin: 0; } - + diff --git a/invokeai/frontend/web/dist/locales/en.json b/invokeai/frontend/web/dist/locales/en.json index 94dff3934a..7a73bae411 100644 --- a/invokeai/frontend/web/dist/locales/en.json +++ b/invokeai/frontend/web/dist/locales/en.json @@ -122,7 +122,9 @@ "noImagesInGallery": "No Images In Gallery", "deleteImage": "Delete Image", "deleteImageBin": "Deleted images will be sent to your operating system's Bin.", - "deleteImagePermanent": "Deleted images cannot be restored." + "deleteImagePermanent": "Deleted images cannot be restored.", + "images": "Images", + "assets": "Assets" }, "hotkeys": { "keyboardShortcuts": "Keyboard Shortcuts", @@ -452,6 +454,8 @@ "height": "Height", "scheduler": "Scheduler", "seed": "Seed", + "boundingBoxWidth": "Bounding Box Width", + "boundingBoxHeight": "Bounding Box Height", "imageToImage": "Image to Image", "randomizeSeed": "Randomize Seed", "shuffle": "Shuffle Seed", @@ -502,8 +506,8 @@ "isScheduled": "Canceling", "setType": "Set cancel type" }, - "promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)", - "negativePrompts": "Negative Prompts", + "positivePromptPlaceholder": "Positive Prompt", + "negativePromptPlaceholder": "Negative Prompt", "sendTo": "Send to", "sendToImg2Img": "Send to Image to Image", "sendToUnifiedCanvas": "Send To Unified Canvas", @@ -524,7 +528,7 @@ }, "settings": { "models": "Models", - "displayInProgress": "Display In-Progress Images", + "displayInProgress": "Display Progress Images", "saveSteps": "Save images every n steps", "confirmOnDelete": "Confirm On Delete", "displayHelpIcons": "Display Help Icons", @@ -564,6 +568,8 @@ "canvasMerged": "Canvas Merged", "sentToImageToImage": "Sent To Image To Image", "sentToUnifiedCanvas": "Sent to Unified Canvas", + "parameterSet": "Parameter set", + "parameterNotSet": "Parameter not set", "parametersSet": "Parameters Set", "parametersNotSet": "Parameters Not Set", "parametersNotSetDesc": "No metadata found for this image.", diff --git a/invokeai/frontend/web/docs/API_CLIENT.md b/invokeai/frontend/web/docs/API_CLIENT.md index 51f3a6510c..5072aa2c42 100644 --- a/invokeai/frontend/web/docs/API_CLIENT.md +++ b/invokeai/frontend/web/docs/API_CLIENT.md @@ -26,10 +26,10 @@ We need to start the nodes web server, which serves the OpenAPI schema to the ge ```bash # from the repo root -python scripts/invoke-new.py --web +python scripts/invokeai-web.py ``` -2. Generate the API client. +2. Generate the API client. ```bash # from invokeai/frontend/web/ diff --git a/invokeai/frontend/web/docs/README.md b/invokeai/frontend/web/docs/README.md index 323dcc5bc7..e8b150e71e 100644 --- a/invokeai/frontend/web/docs/README.md +++ b/invokeai/frontend/web/docs/README.md @@ -12,7 +12,14 @@ Code in `invokeai/frontend/web/` if you want to have a look. ## Stack -State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a custom redux middleware to help). +State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). We lean heavily on RTK: +- `createAsyncThunk` for HTTP requests +- `createEntityAdapter` for fetching images and models +- `createListenerMiddleware` for workflows + +The API client and associated types are generated from the OpenAPI schema. See API_CLIENT.md. + +Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a simple socket.io redux middleware to help). [Chakra-UI](https://github.com/chakra-ui/chakra-ui) for components and styling. @@ -37,9 +44,15 @@ From `invokeai/frontend/web/` run `yarn install` to get everything set up. Start everything in dev mode: 1. Start the dev server: `yarn dev` -2. Start the InvokeAI Nodes backend: `python scripts/invokeai-new.py --web # run from the repo root` +2. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root` 3. Point your browser to the dev server address e.g. +#### VSCode Remote Dev + +We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out. Suggest disabling the IDE's port forwarding feature and doing it manually via SSH: + +`ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host` + ### Production builds For a number of technical and logistical reasons, we need to commit UI build artefacts to the repo. diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 13b8d78bf7..786a721d5c 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -23,8 +23,7 @@ "dev": "concurrently \"vite dev\" \"yarn run theme:watch\"", "dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"", "build": "yarn run lint && vite build", - "api:web": "openapi -i http://localhost:9090/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --indent 2 --request src/services/fixtures/request.ts", - "api:file": "openapi -i src/services/fixtures/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --indent 2 --request src/services/fixtures/request.ts", + "typegen": "npx openapi-typescript http://localhost:9090/openapi.json --output src/services/api/schema.d.ts -t", "preview": "vite preview", "lint:madge": "madge --circular src/main.tsx", "lint:eslint": "eslint --max-warnings=0 .", @@ -56,52 +55,61 @@ "dependencies": { "@chakra-ui/anatomy": "^2.1.1", "@chakra-ui/icons": "^2.0.19", - "@chakra-ui/react": "^2.6.0", - "@chakra-ui/styled-system": "^2.9.0", - "@chakra-ui/theme-tools": "^2.0.16", - "@dagrejs/graphlib": "^2.1.12", - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@floating-ui/react-dom": "^2.0.0", - "@fontsource/inter": "^4.5.15", + "@chakra-ui/react": "^2.7.1", + "@chakra-ui/styled-system": "^2.9.1", + "@chakra-ui/theme-tools": "^2.0.18", + "@dagrejs/graphlib": "^2.1.13", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/modifiers": "^6.0.1", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@floating-ui/react-dom": "^2.0.1", + "@fontsource-variable/inter": "^5.0.3", + "@fontsource/inter": "^5.0.3", + "@mantine/core": "^6.0.14", + "@mantine/hooks": "^6.0.14", "@reduxjs/toolkit": "^1.9.5", "@roarr/browser-log-writer": "^1.1.5", "chakra-ui-contextmenu": "^1.0.5", "dateformat": "^5.0.3", "downshift": "^7.6.0", - "formik": "^2.2.9", - "framer-motion": "^10.12.4", + "formik": "^2.4.2", + "framer-motion": "^10.12.17", "fuse.js": "^6.6.2", - "i18next": "^22.4.15", - "i18next-browser-languagedetector": "^7.0.1", - "i18next-http-backend": "^2.2.0", - "konva": "^9.0.1", + "i18next": "^23.2.3", + "i18next-browser-languagedetector": "^7.0.2", + "i18next-http-backend": "^2.2.1", + "konva": "^9.2.0", "lodash-es": "^4.17.21", - "overlayscrollbars": "^2.1.1", + "nanostores": "^0.9.2", + "openapi-fetch": "^0.4.0", + "overlayscrollbars": "^2.2.0", "overlayscrollbars-react": "^0.5.0", "patch-package": "^7.0.0", + "query-string": "^8.1.0", "re-resizable": "^6.9.9", "react": "^18.2.0", "react-colorful": "^5.6.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hotkeys-hook": "4.4.0", - "react-i18next": "^12.2.2", - "react-icons": "^4.7.1", - "react-konva": "^18.2.7", - "react-redux": "^8.0.5", - "react-resizable-panels": "^0.0.42", + "react-i18next": "^13.0.1", + "react-icons": "^4.10.1", + "react-konva": "^18.2.10", + "react-redux": "^8.1.1", + "react-resizable-panels": "^0.0.52", "react-use": "^17.4.0", - "react-virtuoso": "^4.3.5", - "react-zoom-pan-pinch": "^3.0.7", - "reactflow": "^11.7.0", + "react-virtuoso": "^4.3.11", + "react-zoom-pan-pinch": "^3.0.8", + "reactflow": "^11.7.4", "redux-dynamic-middlewares": "^2.2.0", "redux-remember": "^3.3.1", "roarr": "^7.15.0", "serialize-error": "^11.0.0", - "socket.io-client": "^4.6.0", - "use-image": "^1.1.0", - "uuid": "^9.0.0" + "socket.io-client": "^4.7.0", + "use-image": "^1.1.1", + "uuid": "^9.0.0", + "zod": "^3.21.4" }, "peerDependencies": { "@chakra-ui/cli": "^2.4.0", @@ -110,22 +118,22 @@ "ts-toolbelt": "^9.6.0" }, "devDependencies": { - "@chakra-ui/cli": "^2.4.0", + "@chakra-ui/cli": "^2.4.1", "@types/dateformat": "^5.0.0", "@types/lodash-es": "^4.14.194", - "@types/node": "^18.16.2", - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.1", + "@types/node": "^20.3.1", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", "@types/react-redux": "^7.1.25", - "@types/react-transition-group": "^4.4.5", - "@types/uuid": "^9.0.0", - "@typescript-eslint/eslint-plugin": "^5.59.1", - "@typescript-eslint/parser": "^5.59.1", - "@vitejs/plugin-react-swc": "^3.3.0", + "@types/react-transition-group": "^4.4.6", + "@types/uuid": "^9.0.2", + "@typescript-eslint/eslint-plugin": "^5.60.0", + "@typescript-eslint/parser": "^5.60.0", + "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.4.0", "babel-plugin-transform-imports": "^2.0.0", - "concurrently": "^8.0.1", - "eslint": "^8.39.0", + "concurrently": "^8.2.0", + "eslint": "^8.43.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", @@ -133,15 +141,16 @@ "form-data": "^4.0.0", "husky": "^8.0.3", "lint-staged": "^13.2.2", - "madge": "^6.0.0", - "openapi-types": "^12.1.0", + "madge": "^6.1.0", + "openapi-types": "^12.1.3", + "openapi-typescript": "^6.2.8", "openapi-typescript-codegen": "^0.24.0", "postinstall-postinstall": "^2.1.0", "prettier": "^2.8.8", - "rollup-plugin-visualizer": "^5.9.0", - "terser": "^5.17.1", + "rollup-plugin-visualizer": "^5.9.2", + "terser": "^5.18.1", "ts-toolbelt": "^9.6.0", - "vite": "^4.3.3", + "vite": "^4.3.9", "vite-plugin-css-injected-by-js": "^3.1.1", "vite-plugin-dts": "^2.3.0", "vite-plugin-eslint": "^1.8.1", diff --git a/invokeai/frontend/web/patches/@chakra-ui+cli+2.4.0.patch b/invokeai/frontend/web/patches/@chakra-ui+cli+2.4.0.patch deleted file mode 100644 index 03db6e8238..0000000000 --- a/invokeai/frontend/web/patches/@chakra-ui+cli+2.4.0.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/node_modules/@chakra-ui/cli/dist/scripts/read-theme-file.worker.js b/node_modules/@chakra-ui/cli/dist/scripts/read-theme-file.worker.js -index 937cf0d..7dcc0c0 100644 ---- a/node_modules/@chakra-ui/cli/dist/scripts/read-theme-file.worker.js -+++ b/node_modules/@chakra-ui/cli/dist/scripts/read-theme-file.worker.js -@@ -50,7 +50,8 @@ async function readTheme(themeFilePath) { - project: tsConfig.configFileAbsolutePath, - compilerOptions: { - module: "CommonJS", -- esModuleInterop: true -+ esModuleInterop: true, -+ jsx: 'react' - }, - transpileOnly: true, - swc: true diff --git a/invokeai/frontend/web/patches/openapi-fetch+0.4.0.patch b/invokeai/frontend/web/patches/openapi-fetch+0.4.0.patch new file mode 100644 index 0000000000..d82843f71c --- /dev/null +++ b/invokeai/frontend/web/patches/openapi-fetch+0.4.0.patch @@ -0,0 +1,55 @@ +diff --git a/node_modules/openapi-fetch/dist/index.js b/node_modules/openapi-fetch/dist/index.js +index cd4528a..8976b51 100644 +--- a/node_modules/openapi-fetch/dist/index.js ++++ b/node_modules/openapi-fetch/dist/index.js +@@ -1,5 +1,5 @@ + // settings & const +-const DEFAULT_HEADERS = { ++const CONTENT_TYPE_APPLICATION_JSON = { + "Content-Type": "application/json", + }; + const TRAILING_SLASH_RE = /\/*$/; +@@ -29,18 +29,29 @@ export function createFinalURL(url, options) { + } + return finalURL; + } ++function stringifyBody(body) { ++ if (body instanceof ArrayBuffer || body instanceof File || body instanceof DataView || body instanceof Blob || ArrayBuffer.isView(body) || body instanceof URLSearchParams || body instanceof FormData) { ++ return; ++ } ++ ++ if (typeof body === "string") { ++ return body; ++ } ++ ++ return JSON.stringify(body); ++ } ++ + export default function createClient(clientOptions = {}) { + const { fetch = globalThis.fetch, ...options } = clientOptions; +- const defaultHeaders = new Headers({ +- ...DEFAULT_HEADERS, +- ...(options.headers ?? {}), +- }); ++ const defaultHeaders = new Headers(options.headers ?? {}); + async function coreFetch(url, fetchOptions) { + const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = defaultSerializer, ...init } = fetchOptions || {}; + // URL + const finalURL = createFinalURL(url, { baseUrl: options.baseUrl, params, querySerializer }); ++ // Stringify body if needed ++ const stringifiedBody = stringifyBody(requestBody); + // headers +- const baseHeaders = new Headers(defaultHeaders); // clone defaults (don’t overwrite!) ++ const baseHeaders = new Headers(stringifiedBody ? { ...CONTENT_TYPE_APPLICATION_JSON, ...defaultHeaders } : defaultHeaders); // clone defaults (don’t overwrite!) + const headerOverrides = new Headers(headers); + for (const [k, v] of headerOverrides.entries()) { + if (v === undefined || v === null) +@@ -54,7 +65,7 @@ export default function createClient(clientOptions = {}) { + ...options, + ...init, + headers: baseHeaders, +- body: typeof requestBody === "string" ? requestBody : JSON.stringify(requestBody), ++ body: stringifiedBody ?? requestBody, + }); + // handle empty content + // note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 94dff3934a..1b3b790222 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -24,16 +24,13 @@ }, "common": { "hotkeysLabel": "Hotkeys", - "themeLabel": "Theme", + "darkMode": "Dark Mode", + "lightMode": "Light Mode", "languagePickerLabel": "Language", "reportBugLabel": "Report Bug", "githubLabel": "Github", "discordLabel": "Discord", "settingsLabel": "Settings", - "darkTheme": "Dark", - "lightTheme": "Light", - "greenTheme": "Green", - "oceanTheme": "Ocean", "langArabic": "العربية", "langEnglish": "English", "langDutch": "Nederlands", @@ -122,7 +119,9 @@ "noImagesInGallery": "No Images In Gallery", "deleteImage": "Delete Image", "deleteImageBin": "Deleted images will be sent to your operating system's Bin.", - "deleteImagePermanent": "Deleted images cannot be restored." + "deleteImagePermanent": "Deleted images cannot be restored.", + "images": "Images", + "assets": "Assets" }, "hotkeys": { "keyboardShortcuts": "Keyboard Shortcuts", @@ -452,6 +451,8 @@ "height": "Height", "scheduler": "Scheduler", "seed": "Seed", + "boundingBoxWidth": "Bounding Box Width", + "boundingBoxHeight": "Bounding Box Height", "imageToImage": "Image to Image", "randomizeSeed": "Randomize Seed", "shuffle": "Shuffle Seed", @@ -502,8 +503,8 @@ "isScheduled": "Canceling", "setType": "Set cancel type" }, - "promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)", - "negativePrompts": "Negative Prompts", + "positivePromptPlaceholder": "Positive Prompt", + "negativePromptPlaceholder": "Negative Prompt", "sendTo": "Send to", "sendToImg2Img": "Send to Image to Image", "sendToUnifiedCanvas": "Send To Unified Canvas", @@ -520,11 +521,12 @@ "initialImage": "Initial Image", "showOptionsPanel": "Show Options Panel", "hidePreview": "Hide Preview", - "showPreview": "Show Preview" + "showPreview": "Show Preview", + "controlNetControlMode": "Control Mode" }, "settings": { "models": "Models", - "displayInProgress": "Display In-Progress Images", + "displayInProgress": "Display Progress Images", "saveSteps": "Save images every n steps", "confirmOnDelete": "Confirm On Delete", "displayHelpIcons": "Display Help Icons", @@ -543,7 +545,8 @@ "general": "General", "generation": "Generation", "ui": "User Interface", - "availableSchedulers": "Available Schedulers" + "favoriteSchedulers": "Favorite Schedulers", + "favoriteSchedulersPlaceholder": "No schedulers favorited" }, "toast": { "serverError": "Server Error", @@ -564,6 +567,8 @@ "canvasMerged": "Canvas Merged", "sentToImageToImage": "Sent To Image To Image", "sentToUnifiedCanvas": "Sent to Unified Canvas", + "parameterSet": "Parameter set", + "parameterNotSet": "Parameter not set", "parametersSet": "Parameters Set", "parametersNotSet": "Parameters Not Set", "parametersNotSetDesc": "No metadata found for this image.", diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 40554356b1..5b3cf5925f 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -1,27 +1,31 @@ -import ImageUploader from 'common/components/ImageUploader'; -import SiteHeader from 'features/system/components/SiteHeader'; -import ProgressBar from 'features/system/components/ProgressBar'; -import InvokeTabs from 'features/ui/components/InvokeTabs'; -import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton'; -import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons'; import { Box, Flex, Grid, Portal } from '@chakra-ui/react'; -import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants'; +import { useLogger } from 'app/logging/useLogger'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { PartialAppConfig } from 'app/types/invokeai'; +import ImageUploader from 'common/components/ImageUploader'; +import Loading from 'common/components/Loading/Loading'; import GalleryDrawer from 'features/gallery/components/GalleryPanel'; import Lightbox from 'features/lightbox/components/Lightbox'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { memo, ReactNode, useCallback, useEffect, useState } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import Loading from 'common/components/Loading/Loading'; -import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady'; -import { PartialAppConfig } from 'app/types/invokeai'; -import { configChanged } from 'features/system/store/configSlice'; +import SiteHeader from 'features/system/components/SiteHeader'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { useLogger } from 'app/logging/useLogger'; -import ParametersDrawer from 'features/ui/components/ParametersDrawer'; +import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady'; +import { configChanged } from 'features/system/store/configSlice'; import { languageSelector } from 'features/system/store/systemSelectors'; +import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton'; +import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons'; +import InvokeTabs from 'features/ui/components/InvokeTabs'; +import ParametersDrawer from 'features/ui/components/ParametersDrawer'; +import { AnimatePresence, motion } from 'framer-motion'; import i18n from 'i18n'; -import Toaster from './Toaster'; +import { ReactNode, memo, useCallback, useEffect, useState } from 'react'; +import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants'; import GlobalHotkeys from './GlobalHotkeys'; +import Toaster from './Toaster'; +import DeleteImageModal from 'features/gallery/components/DeleteImageModal'; +import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; +import { useListModelsQuery } from 'services/api/endpoints/models'; +import DeleteBoardImagesModal from '../../features/gallery/components/Boards/DeleteBoardImagesModal'; const DEFAULT_CONFIG = {}; @@ -44,6 +48,18 @@ const App = ({ const isApplicationReady = useIsApplicationReady(); + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'main', + }); + const { data: controlnetModels } = useListModelsQuery({ + model_type: 'controlnet', + }); + const { data: vaeModels } = useListModelsQuery({ model_type: 'vae' }); + const { data: loraModels } = useListModelsQuery({ model_type: 'lora' }); + const { data: embeddingModels } = useListModelsQuery({ + model_type: 'embedding', + }); + const [loadingOverridden, setLoadingOverridden] = useState(false); const dispatch = useAppDispatch(); @@ -66,30 +82,39 @@ const App = ({ setIsReady(true); } + if (isApplicationReady) { + // TODO: This is a jank fix for canvas not filling the screen on first load + setTimeout(() => { + dispatch(requestCanvasRescale()); + }, 200); + } + return () => { setIsReady && setIsReady(false); }; - }, [isApplicationReady, setIsReady]); + }, [dispatch, isApplicationReady, setIsReady]); return ( <> {isLightboxEnabled && } - {headerComponent || } @@ -132,6 +157,9 @@ const App = ({ + + + diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx new file mode 100644 index 0000000000..6150259f66 --- /dev/null +++ b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx @@ -0,0 +1,89 @@ +import { + DndContext, + DragEndEvent, + DragOverlay, + DragStartEvent, + MouseSensor, + TouchSensor, + pointerWithin, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { PropsWithChildren, memo, useCallback, useState } from 'react'; +import OverlayDragImage from './OverlayDragImage'; +import { ImageDTO } from 'services/api/types'; +import { isImageDTO } from 'services/api/guards'; +import { snapCenterToCursor } from '@dnd-kit/modifiers'; +import { AnimatePresence, motion } from 'framer-motion'; + +type ImageDndContextProps = PropsWithChildren; + +const ImageDndContext = (props: ImageDndContextProps) => { + const [draggedImage, setDraggedImage] = useState(null); + + const handleDragStart = useCallback((event: DragStartEvent) => { + const dragData = event.active.data.current; + if (dragData && 'image' in dragData && isImageDTO(dragData.image)) { + setDraggedImage(dragData.image); + } + }, []); + + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + const handleDrop = event.over?.data.current?.handleDrop; + if (handleDrop && typeof handleDrop === 'function' && draggedImage) { + handleDrop(draggedImage); + } + setDraggedImage(null); + }, + [draggedImage] + ); + + const mouseSensor = useSensor(MouseSensor, { + activationConstraint: { delay: 150, tolerance: 5 }, + }); + + const touchSensor = useSensor(TouchSensor, { + activationConstraint: { delay: 150, tolerance: 5 }, + }); + // TODO: Use KeyboardSensor - needs composition of multiple collisionDetection algos + // Alternatively, fix `rectIntersection` collection detection to work with the drag overlay + // (currently the drag element collision rect is not correctly calculated) + // const keyboardSensor = useSensor(KeyboardSensor); + + const sensors = useSensors(mouseSensor, touchSensor); + + return ( + + {props.children} + + + {draggedImage && ( + + + + )} + + + + ); +}; + +export default memo(ImageDndContext); diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/OverlayDragImage.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/OverlayDragImage.tsx new file mode 100644 index 0000000000..611d1ceee9 --- /dev/null +++ b/invokeai/frontend/web/src/app/components/ImageDnd/OverlayDragImage.tsx @@ -0,0 +1,36 @@ +import { Box, Image } from '@chakra-ui/react'; +import { memo } from 'react'; +import { ImageDTO } from 'services/api/types'; + +type OverlayDragImageProps = { + image: ImageDTO; +}; + +const OverlayDragImage = (props: OverlayDragImageProps) => { + return ( + + + + ); +}; + +export default memo(OverlayDragImage); diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index 69b2756f96..7259f6105d 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -7,7 +7,7 @@ import React, { } from 'react'; import { Provider } from 'react-redux'; import { store } from 'app/store/store'; -import { OpenAPI } from 'services/api'; +// import { OpenAPI } from 'services/api/types'; import Loading from '../../common/components/Loading/Loading'; import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares'; @@ -16,6 +16,15 @@ import { PartialAppConfig } from 'app/types/invokeai'; import '../../i18n'; import { socketMiddleware } from 'services/events/middleware'; import { Middleware } from '@reduxjs/toolkit'; +import ImageDndContext from './ImageDnd/ImageDndContext'; +import { + DeleteImageContext, + DeleteImageContextProvider, +} from 'app/contexts/DeleteImageContext'; +import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal'; +import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext'; +import { $authToken, $baseUrl } from 'services/api/client'; +import { DeleteBoardImagesContextProvider } from '../contexts/DeleteBoardImagesContext'; const App = lazy(() => import('./App')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); @@ -40,12 +49,12 @@ const InvokeAIUI = ({ useEffect(() => { // configure API client token if (token) { - OpenAPI.TOKEN = token; + $authToken.set(token); } // configure API client base url if (apiUrl) { - OpenAPI.BASE = apiUrl; + $baseUrl.set(apiUrl); } // reset dynamically added middlewares @@ -62,6 +71,12 @@ const InvokeAIUI = ({ } else { addMiddleware(socketMiddleware()); } + + return () => { + // Reset the API client token and base url on unmount + $baseUrl.set(undefined); + $authToken.set(undefined); + }; }, [apiUrl, token, middleware]); return ( @@ -69,11 +84,19 @@ const InvokeAIUI = ({ }> - + + + + + + + + + diff --git a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx index 6aa38fc15b..1e86e0ce1b 100644 --- a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx +++ b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx @@ -3,18 +3,13 @@ import { createLocalStorageManager, extendTheme, } from '@chakra-ui/react'; -import { ReactNode, useEffect } from 'react'; +import { ReactNode, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { theme as invokeAITheme } from 'theme/theme'; -import { RootState } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; -import { greenTeaThemeColors } from 'theme/colors/greenTea'; -import { invokeAIThemeColors } from 'theme/colors/invokeAI'; -import { lightThemeColors } from 'theme/colors/lightTheme'; -import { oceanBlueColors } from 'theme/colors/oceanBlue'; - -import '@fontsource/inter/variable.css'; +import '@fontsource-variable/inter'; +import { MantineProvider } from '@mantine/core'; +import { mantineTheme } from 'mantine-theme/theme'; import 'overlayscrollbars/overlayscrollbars.css'; import 'theme/css/overlayscrollbars.css'; @@ -22,38 +17,30 @@ type ThemeLocaleProviderProps = { children: ReactNode; }; -const THEMES = { - dark: invokeAIThemeColors, - light: lightThemeColors, - green: greenTeaThemeColors, - ocean: oceanBlueColors, -}; - const manager = createLocalStorageManager('@@invokeai-color-mode'); function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) { const { i18n } = useTranslation(); - const currentTheme = useAppSelector( - (state: RootState) => state.ui.currentTheme - ); - const direction = i18n.dir(); - const theme = extendTheme({ - ...invokeAITheme, - colors: THEMES[currentTheme as keyof typeof THEMES], - direction, - }); + const theme = useMemo(() => { + return extendTheme({ + ...invokeAITheme, + direction, + }); + }, [direction]); useEffect(() => { document.body.dir = direction; }, [direction]); return ( - - {children} - + + + {children} + + ); } diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index d312d725ba..5fd413d915 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -1,45 +1,68 @@ -// TODO: use Enums? +import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas'; -export const SCHEDULERS = [ - 'ddim', - 'lms', +// zod needs the array to be `as const` to infer the type correctly +// this is the source of the `SchedulerParam` type, which is generated by zod +export const SCHEDULER_NAMES_AS_CONST = [ 'euler', - 'euler_k', - 'euler_a', + 'deis', + 'ddim', + 'ddpm', 'dpmpp_2s', 'dpmpp_2m', - 'dpmpp_2m_k', - 'kdpm_2', - 'kdpm_2_a', - 'deis', - 'ddpm', - 'pndm', + 'dpmpp_2m_sde', + 'dpmpp_sde', 'heun', - 'heun_k', + 'kdpm_2', + 'lms', + 'pndm', 'unipc', + 'euler_k', + 'dpmpp_2s_k', + 'dpmpp_2m_k', + 'dpmpp_2m_sde_k', + 'dpmpp_sde_k', + 'heun_k', + 'lms_k', + 'euler_a', + 'kdpm_2_a', ] as const; -export type Scheduler = (typeof SCHEDULERS)[number]; +export const DEFAULT_SCHEDULER_NAME = 'euler'; -export const isScheduler = (x: string): x is Scheduler => - SCHEDULERS.includes(x as Scheduler); +export const SCHEDULER_NAMES: SchedulerParam[] = [...SCHEDULER_NAMES_AS_CONST]; -// Valid image widths -export const WIDTHS: Array = Array.from(Array(64)).map( - (_x, i) => (i + 1) * 64 -); +export const SCHEDULER_LABEL_MAP: Record = { + euler: 'Euler', + deis: 'DEIS', + ddim: 'DDIM', + ddpm: 'DDPM', + dpmpp_sde: 'DPM++ SDE', + dpmpp_2s: 'DPM++ 2S', + dpmpp_2m: 'DPM++ 2M', + dpmpp_2m_sde: 'DPM++ 2M SDE', + heun: 'Heun', + kdpm_2: 'KDPM 2', + lms: 'LMS', + pndm: 'PNDM', + unipc: 'UniPC', + euler_k: 'Euler Karras', + dpmpp_sde_k: 'DPM++ SDE Karras', + dpmpp_2s_k: 'DPM++ 2S Karras', + dpmpp_2m_k: 'DPM++ 2M Karras', + dpmpp_2m_sde_k: 'DPM++ 2M SDE Karras', + heun_k: 'Heun Karras', + lms_k: 'LMS Karras', + euler_a: 'Euler Ancestral', + kdpm_2_a: 'KDPM 2 Ancestral', +}; -// Valid image heights -export const HEIGHTS: Array = Array.from(Array(64)).map( - (_x, i) => (i + 1) * 64 -); +export type Scheduler = (typeof SCHEDULER_NAMES)[number]; // Valid upscaling levels -export const UPSCALING_LEVELS: Array<{ key: string; value: number }> = [ - { key: '2x', value: 2 }, - { key: '4x', value: 4 }, +export const UPSCALING_LEVELS: Array<{ label: string; value: string }> = [ + { label: '2x', value: '2' }, + { label: '4x', value: '4' }, ]; - export const NUMPY_RAND_MIN = 0; export const NUMPY_RAND_MAX = 2147483647; diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx new file mode 100644 index 0000000000..f37f06d4b1 --- /dev/null +++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx @@ -0,0 +1,89 @@ +import { useDisclosure } from '@chakra-ui/react'; +import { PropsWithChildren, createContext, useCallback, useState } from 'react'; +import { ImageDTO } from 'services/api/types'; +import { useAddImageToBoardMutation } from 'services/api/endpoints/boardImages'; + +export type ImageUsage = { + isInitialImage: boolean; + isCanvasImage: boolean; + isNodesImage: boolean; + isControlNetImage: boolean; +}; + +type AddImageToBoardContextValue = { + /** + * Whether the move image dialog is open. + */ + isOpen: boolean; + /** + * Closes the move image dialog. + */ + onClose: () => void; + /** + * The image pending movement + */ + image?: ImageDTO; + onClickAddToBoard: (image: ImageDTO) => void; + handleAddToBoard: (boardId: string) => void; +}; + +export const AddImageToBoardContext = + createContext({ + isOpen: false, + onClose: () => undefined, + onClickAddToBoard: () => undefined, + handleAddToBoard: () => undefined, + }); + +type Props = PropsWithChildren; + +export const AddImageToBoardContextProvider = (props: Props) => { + const [imageToMove, setImageToMove] = useState(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + const [addImageToBoard, result] = useAddImageToBoardMutation(); + + // Clean up after deleting or dismissing the modal + const closeAndClearImageToDelete = useCallback(() => { + setImageToMove(undefined); + onClose(); + }, [onClose]); + + const onClickAddToBoard = useCallback( + (image?: ImageDTO) => { + if (!image) { + return; + } + setImageToMove(image); + onOpen(); + }, + [setImageToMove, onOpen] + ); + + const handleAddToBoard = useCallback( + (boardId: string) => { + if (imageToMove) { + addImageToBoard({ + board_id: boardId, + image_name: imageToMove.image_name, + }); + closeAndClearImageToDelete(); + } + }, + [addImageToBoard, closeAndClearImageToDelete, imageToMove] + ); + + return ( + + {props.children} + + ); +}; diff --git a/invokeai/frontend/web/src/app/contexts/DeleteBoardImagesContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteBoardImagesContext.tsx new file mode 100644 index 0000000000..38c89bfcf9 --- /dev/null +++ b/invokeai/frontend/web/src/app/contexts/DeleteBoardImagesContext.tsx @@ -0,0 +1,170 @@ +import { useDisclosure } from '@chakra-ui/react'; +import { PropsWithChildren, createContext, useCallback, useState } from 'react'; +import { BoardDTO } from 'services/api/types'; +import { useDeleteBoardMutation } from '../../services/api/endpoints/boards'; +import { defaultSelectorOptions } from '../store/util/defaultMemoizeOptions'; +import { createSelector } from '@reduxjs/toolkit'; +import { some } from 'lodash-es'; +import { canvasSelector } from '../../features/canvas/store/canvasSelectors'; +import { controlNetSelector } from '../../features/controlNet/store/controlNetSlice'; +import { selectImagesById } from '../../features/gallery/store/imagesSlice'; +import { nodesSelector } from '../../features/nodes/store/nodesSlice'; +import { generationSelector } from '../../features/parameters/store/generationSelectors'; +import { RootState } from '../store/store'; +import { useAppDispatch, useAppSelector } from '../store/storeHooks'; +import { ImageUsage } from './DeleteImageContext'; +import { requestedBoardImagesDeletion } from '../../features/gallery/store/actions'; + +export const selectBoardImagesUsage = createSelector( + [ + (state: RootState) => state, + generationSelector, + canvasSelector, + nodesSelector, + controlNetSelector, + (state: RootState, board_id?: string) => board_id, + ], + (state, generation, canvas, nodes, controlNet, board_id) => { + const initialImage = generation.initialImage + ? selectImagesById(state, generation.initialImage.imageName) + : undefined; + const isInitialImage = initialImage?.board_id === board_id; + + const isCanvasImage = canvas.layerState.objects.some((obj) => { + if (obj.kind === 'image') { + const image = selectImagesById(state, obj.imageName); + return image?.board_id === board_id; + } + return false; + }); + + const isNodesImage = nodes.nodes.some((node) => { + return some(node.data.inputs, (input) => { + if (input.type === 'image' && input.value) { + const image = selectImagesById(state, input.value.image_name); + return image?.board_id === board_id; + } + return false; + }); + }); + + const isControlNetImage = some(controlNet.controlNets, (c) => { + const controlImage = c.controlImage + ? selectImagesById(state, c.controlImage) + : undefined; + const processedControlImage = c.processedControlImage + ? selectImagesById(state, c.processedControlImage) + : undefined; + return ( + controlImage?.board_id === board_id || + processedControlImage?.board_id === board_id + ); + }); + + const imageUsage: ImageUsage = { + isInitialImage, + isCanvasImage, + isNodesImage, + isControlNetImage, + }; + + return imageUsage; + }, + defaultSelectorOptions +); + +type DeleteBoardImagesContextValue = { + /** + * Whether the move image dialog is open. + */ + isOpen: boolean; + /** + * Closes the move image dialog. + */ + onClose: () => void; + imagesUsage?: ImageUsage; + board?: BoardDTO; + onClickDeleteBoardImages: (board: BoardDTO) => void; + handleDeleteBoardImages: (boardId: string) => void; + handleDeleteBoardOnly: (boardId: string) => void; +}; + +export const DeleteBoardImagesContext = + createContext({ + isOpen: false, + onClose: () => undefined, + onClickDeleteBoardImages: () => undefined, + handleDeleteBoardImages: () => undefined, + handleDeleteBoardOnly: () => undefined, + }); + +type Props = PropsWithChildren; + +export const DeleteBoardImagesContextProvider = (props: Props) => { + const [boardToDelete, setBoardToDelete] = useState(); + const { isOpen, onOpen, onClose } = useDisclosure(); + const dispatch = useAppDispatch(); + + // Check where the board images to be deleted are used (eg init image, controlnet, etc.) + const imagesUsage = useAppSelector((state) => + selectBoardImagesUsage(state, boardToDelete?.board_id) + ); + + const [deleteBoard] = useDeleteBoardMutation(); + + // Clean up after deleting or dismissing the modal + const closeAndClearBoardToDelete = useCallback(() => { + setBoardToDelete(undefined); + onClose(); + }, [onClose]); + + const onClickDeleteBoardImages = useCallback( + (board?: BoardDTO) => { + console.log({ board }); + if (!board) { + return; + } + setBoardToDelete(board); + onOpen(); + }, + [setBoardToDelete, onOpen] + ); + + const handleDeleteBoardImages = useCallback( + (boardId: string) => { + if (boardToDelete) { + dispatch( + requestedBoardImagesDeletion({ board: boardToDelete, imagesUsage }) + ); + closeAndClearBoardToDelete(); + } + }, + [dispatch, closeAndClearBoardToDelete, boardToDelete, imagesUsage] + ); + + const handleDeleteBoardOnly = useCallback( + (boardId: string) => { + if (boardToDelete) { + deleteBoard(boardId); + closeAndClearBoardToDelete(); + } + }, + [deleteBoard, closeAndClearBoardToDelete, boardToDelete] + ); + + return ( + + {props.children} + + ); +}; diff --git a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx new file mode 100644 index 0000000000..6f4af7608f --- /dev/null +++ b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx @@ -0,0 +1,201 @@ +import { useDisclosure } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { requestedImageDeletion } from 'features/gallery/store/actions'; +import { systemSelector } from 'features/system/store/systemSelectors'; +import { + PropsWithChildren, + createContext, + useCallback, + useEffect, + useState, +} from 'react'; +import { ImageDTO } from 'services/api/types'; +import { RootState } from 'app/store/store'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; +import { nodesSelector } from 'features/nodes/store/nodesSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { some } from 'lodash-es'; + +export type ImageUsage = { + isInitialImage: boolean; + isCanvasImage: boolean; + isNodesImage: boolean; + isControlNetImage: boolean; +}; + +export const selectImageUsage = createSelector( + [ + generationSelector, + canvasSelector, + nodesSelector, + controlNetSelector, + (state: RootState, image_name?: string) => image_name, + ], + (generation, canvas, nodes, controlNet, image_name) => { + const isInitialImage = generation.initialImage?.imageName === image_name; + + const isCanvasImage = canvas.layerState.objects.some( + (obj) => obj.kind === 'image' && obj.imageName === image_name + ); + + const isNodesImage = nodes.nodes.some((node) => { + return some( + node.data.inputs, + (input) => input.type === 'image' && input.value === image_name + ); + }); + + const isControlNetImage = some( + controlNet.controlNets, + (c) => + c.controlImage === image_name || c.processedControlImage === image_name + ); + + const imageUsage: ImageUsage = { + isInitialImage, + isCanvasImage, + isNodesImage, + isControlNetImage, + }; + + return imageUsage; + }, + defaultSelectorOptions +); + +type DeleteImageContextValue = { + /** + * Whether the delete image dialog is open. + */ + isOpen: boolean; + /** + * Closes the delete image dialog. + */ + onClose: () => void; + /** + * Opens the delete image dialog and handles all deletion-related checks. + */ + onDelete: (image?: ImageDTO) => void; + /** + * The image pending deletion + */ + image?: ImageDTO; + /** + * The features in which this image is used + */ + imageUsage?: ImageUsage; + /** + * Immediately deletes an image. + * + * You probably don't want to use this - use `onDelete` instead. + */ + onImmediatelyDelete: () => void; +}; + +export const DeleteImageContext = createContext({ + isOpen: false, + onClose: () => undefined, + onImmediatelyDelete: () => undefined, + onDelete: () => undefined, +}); + +const selector = createSelector( + [systemSelector], + (system) => { + const { isProcessing, isConnected, shouldConfirmOnDelete } = system; + + return { + canDeleteImage: isConnected && !isProcessing, + shouldConfirmOnDelete, + }; + }, + defaultSelectorOptions +); + +type Props = PropsWithChildren; + +export const DeleteImageContextProvider = (props: Props) => { + const { canDeleteImage, shouldConfirmOnDelete } = useAppSelector(selector); + const [imageToDelete, setImageToDelete] = useState(); + const dispatch = useAppDispatch(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + // Check where the image to be deleted is used (eg init image, controlnet, etc.) + const imageUsage = useAppSelector((state) => + selectImageUsage(state, imageToDelete?.image_name) + ); + + // Clean up after deleting or dismissing the modal + const closeAndClearImageToDelete = useCallback(() => { + setImageToDelete(undefined); + onClose(); + }, [onClose]); + + // Dispatch the actual deletion action, to be handled by listener middleware + const handleActualDeletion = useCallback( + (image: ImageDTO) => { + dispatch(requestedImageDeletion({ image, imageUsage })); + closeAndClearImageToDelete(); + }, + [closeAndClearImageToDelete, dispatch, imageUsage] + ); + + // This is intended to be called by the delete button in the dialog + const onImmediatelyDelete = useCallback(() => { + if (canDeleteImage && imageToDelete) { + handleActualDeletion(imageToDelete); + } + closeAndClearImageToDelete(); + }, [ + canDeleteImage, + imageToDelete, + closeAndClearImageToDelete, + handleActualDeletion, + ]); + + const handleGatedDeletion = useCallback( + (image: ImageDTO) => { + if (shouldConfirmOnDelete || some(imageUsage)) { + // If we should confirm on delete, or if the image is in use, open the dialog + onOpen(); + } else { + handleActualDeletion(image); + } + }, + [imageUsage, shouldConfirmOnDelete, onOpen, handleActualDeletion] + ); + + // Consumers of the context call this to delete an image + const onDelete = useCallback((image?: ImageDTO) => { + if (!image) { + return; + } + // Set the image to delete, then let the effect call the actual deletion + setImageToDelete(image); + }, []); + + useEffect(() => { + // We need to use an effect here to trigger the image usage selector, else we get a stale value + if (imageToDelete) { + handleGatedDeletion(imageToDelete); + } + }, [handleGatedDeletion, imageToDelete]); + + return ( + + {props.children} + + ); +}; diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts index 52995e0da3..cb18d48301 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts @@ -1,12 +1,10 @@ import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist'; +import { controlNetDenylist } from 'features/controlNet/store/controlNetDenylist'; import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist'; -import { resultsPersistDenylist } from 'features/gallery/store/resultsPersistDenylist'; -import { uploadsPersistDenylist } from 'features/gallery/store/uploadsPersistDenylist'; import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersistDenylist'; import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist'; import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist'; import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist'; -import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist'; import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist'; import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist'; import { omit } from 'lodash-es'; @@ -19,14 +17,12 @@ const serializationDenylist: { gallery: galleryPersistDenylist, generation: generationPersistDenylist, lightbox: lightboxPersistDenylist, - models: modelsPersistDenylist, nodes: nodesPersistDenylist, postprocessing: postprocessingPersistDenylist, - results: resultsPersistDenylist, system: systemPersistDenylist, // config: configPersistDenyList, ui: uiPersistDenylist, - uploads: uploadsPersistDenylist, + controlNet: controlNetDenylist, // hotkeys: hotkeysPersistDenylist, }; diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts index 155a7786b3..8f40b0bb59 100644 --- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts +++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts @@ -1,13 +1,12 @@ import { initialCanvasState } from 'features/canvas/store/canvasSlice'; +import { initialControlNetState } from 'features/controlNet/store/controlNetSlice'; import { initialGalleryState } from 'features/gallery/store/gallerySlice'; -import { initialResultsState } from 'features/gallery/store/resultsSlice'; -import { initialUploadsState } from 'features/gallery/store/uploadsSlice'; +import { initialImagesState } from 'features/gallery/store/imagesSlice'; import { initialLightboxState } from 'features/lightbox/store/lightboxSlice'; import { initialNodesState } from 'features/nodes/store/nodesSlice'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice'; import { initialConfigState } from 'features/system/store/configSlice'; -import { initialModelsState } from 'features/system/store/modelSlice'; import { initialSystemState } from 'features/system/store/systemSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialUIState } from 'features/ui/store/uiSlice'; @@ -21,15 +20,14 @@ const initialStates: { gallery: initialGalleryState, generation: initialGenerationState, lightbox: initialLightboxState, - models: initialModelsState, nodes: initialNodesState, postprocessing: initialPostprocessingState, - results: initialResultsState, system: initialSystemState, config: initialConfigState, ui: initialUIState, - uploads: initialUploadsState, hotkeys: initialHotkeysState, + images: initialImagesState, + controlNet: initialControlNetState, }; export const unserialize: UnserializeFunction = (data, key) => { diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts index 24b85e0f83..143b16594c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts +++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts @@ -1,24 +1,14 @@ import { AnyAction } from '@reduxjs/toolkit'; import { isAnyGraphBuilt } from 'features/nodes/store/actions'; -import { forEach } from 'lodash-es'; -import { Graph } from 'services/api'; +import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice'; +import { receivedOpenAPISchema } from 'services/api/thunks/schema'; +import { Graph } from 'services/api/types'; export const actionSanitizer = (action: A): A => { if (isAnyGraphBuilt(action)) { if (action.payload.nodes) { const sanitizedNodes: Graph['nodes'] = {}; - // Sanitize nodes as needed - forEach(action.payload.nodes, (node, key) => { - // Don't log the whole freaking dataURL - if (node.type === 'dataURL_image') { - const { dataURL, ...rest } = node; - sanitizedNodes[key] = { ...rest, dataURL: '' }; - } else { - sanitizedNodes[key] = { ...node }; - } - }); - return { ...action, payload: { ...action.payload, nodes: sanitizedNodes }, @@ -26,5 +16,19 @@ export const actionSanitizer = (action: A): A => { } } + if (receivedOpenAPISchema.fulfilled.match(action)) { + return { + ...action, + payload: '', + }; + } + + if (nodeTemplatesBuilt.match(action)) { + return { + ...action, + payload: '', + }; + } + return action; }; diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts index 743537d7ea..eb54868735 100644 --- a/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts +++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts @@ -7,5 +7,6 @@ export const actionsDenylist = [ 'canvas/setBoundingBoxDimensions', 'canvas/setIsDrawing', 'canvas/addPointToCurrentLine', - 'socket/generatorProgress', + 'socket/socketGeneratorProgress', + 'socket/appSocketGeneratorProgress', ]; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 1fbc2f978c..a36141fafc 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -26,15 +26,15 @@ import { addCanvasSavedToGalleryListener } from './listeners/canvasSavedToGaller import { addCanvasDownloadedAsImageListener } from './listeners/canvasDownloadedAsImage'; import { addCanvasCopiedToClipboardListener } from './listeners/canvasCopiedToClipboard'; import { addCanvasMergedListener } from './listeners/canvasMerged'; -import { addGeneratorProgressListener } from './listeners/socketio/generatorProgress'; -import { addGraphExecutionStateCompleteListener } from './listeners/socketio/graphExecutionStateComplete'; -import { addInvocationCompleteListener } from './listeners/socketio/invocationComplete'; -import { addInvocationErrorListener } from './listeners/socketio/invocationError'; -import { addInvocationStartedListener } from './listeners/socketio/invocationStarted'; -import { addSocketConnectedListener } from './listeners/socketio/socketConnected'; -import { addSocketDisconnectedListener } from './listeners/socketio/socketDisconnected'; -import { addSocketSubscribedListener } from './listeners/socketio/socketSubscribed'; -import { addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed'; +import { addGeneratorProgressEventListener as addGeneratorProgressListener } from './listeners/socketio/socketGeneratorProgress'; +import { addGraphExecutionStateCompleteEventListener as addGraphExecutionStateCompleteListener } from './listeners/socketio/socketGraphExecutionStateComplete'; +import { addInvocationCompleteEventListener as addInvocationCompleteListener } from './listeners/socketio/socketInvocationComplete'; +import { addInvocationErrorEventListener as addInvocationErrorListener } from './listeners/socketio/socketInvocationError'; +import { addInvocationStartedEventListener as addInvocationStartedListener } from './listeners/socketio/socketInvocationStarted'; +import { addSocketConnectedEventListener as addSocketConnectedListener } from './listeners/socketio/socketConnected'; +import { addSocketDisconnectedEventListener as addSocketDisconnectedListener } from './listeners/socketio/socketDisconnected'; +import { addSocketSubscribedEventListener as addSocketSubscribedListener } from './listeners/socketio/socketSubscribed'; +import { addSocketUnsubscribedEventListener as addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed'; import { addSessionReadyToInvokeListener } from './listeners/sessionReadyToInvoke'; import { addImageMetadataReceivedFulfilledListener, @@ -60,13 +60,30 @@ import { addSessionCanceledRejectedListener, } from './listeners/sessionCanceled'; import { - addReceivedResultImagesPageFulfilledListener, - addReceivedResultImagesPageRejectedListener, -} from './listeners/receivedResultImagesPage'; + addImageUpdatedFulfilledListener, + addImageUpdatedRejectedListener, +} from './listeners/imageUpdated'; import { - addReceivedUploadImagesPageFulfilledListener, - addReceivedUploadImagesPageRejectedListener, -} from './listeners/receivedUploadImagesPage'; + addReceivedPageOfImagesFulfilledListener, + addReceivedPageOfImagesRejectedListener, +} from './listeners/receivedPageOfImages'; +import { addStagingAreaImageSavedListener } from './listeners/stagingAreaImageSaved'; +import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener'; +import { addImageCategoriesChangedListener } from './listeners/imageCategoriesChanged'; +import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed'; +import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess'; +import { addUpdateImageUrlsOnConnectListener } from './listeners/updateImageUrlsOnConnect'; +import { + addImageAddedToBoardFulfilledListener, + addImageAddedToBoardRejectedListener, +} from './listeners/imageAddedToBoard'; +import { addBoardIdSelectedListener } from './listeners/boardIdSelected'; +import { + addImageRemovedFromBoardFulfilledListener, + addImageRemovedFromBoardRejectedListener, +} from './listeners/imageRemovedFromBoard'; +import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema'; +import { addRequestedBoardImageDeletionListener } from './listeners/boardImagesDeleted'; export const listenerMiddleware = createListenerMiddleware(); @@ -86,10 +103,21 @@ export type AppListenerEffect = ListenerEffect< AppDispatch >; +/** + * The RTK listener middleware is a lightweight alternative sagas/observables. + * + * Most side effect logic should live in a listener. + */ + // Image uploaded addImageUploadedFulfilledListener(); addImageUploadedRejectedListener(); +// Image updated +addImageUpdatedFulfilledListener(); +addImageUpdatedRejectedListener(); + +// Image selected addInitialImageSelectedListener(); // Image deleted @@ -97,6 +125,7 @@ addRequestedImageDeletionListener(); addImageDeletedPendingListener(); addImageDeletedFulfilledListener(); addImageDeletedRejectedListener(); +addRequestedBoardImageDeletionListener(); // Image metadata addImageMetadataReceivedFulfilledListener(); @@ -118,8 +147,22 @@ addCanvasSavedToGalleryListener(); addCanvasDownloadedAsImageListener(); addCanvasCopiedToClipboardListener(); addCanvasMergedListener(); +addStagingAreaImageSavedListener(); +addCommitStagingAreaImageListener(); -// socketio +/** + * Socket.IO Events - these handle SIO events directly and pass on internal application actions. + * We don't handle SIO events in slices via `extraReducers` because some of these events shouldn't + * actually be handled at all. + * + * For example, we don't want to respond to progress events for canceled sessions. To avoid + * duplicating the logic to determine if an event should be responded to, we handle all of that + * "is this session canceled?" logic in these listeners. + * + * The `socketGeneratorProgress` listener will then only dispatch the `appSocketGeneratorProgress` + * action if it should be handled by the rest of the application. It is this `appSocketGeneratorProgress` + * action that is handled by reducers in slices. + */ addGeneratorProgressListener(); addGraphExecutionStateCompleteListener(); addInvocationCompleteListener(); @@ -145,8 +188,26 @@ addSessionCanceledPendingListener(); addSessionCanceledFulfilledListener(); addSessionCanceledRejectedListener(); -// Gallery pages -addReceivedResultImagesPageFulfilledListener(); -addReceivedResultImagesPageRejectedListener(); -addReceivedUploadImagesPageFulfilledListener(); -addReceivedUploadImagesPageRejectedListener(); +// Fetching images +addReceivedPageOfImagesFulfilledListener(); +addReceivedPageOfImagesRejectedListener(); + +// Gallery +addImageCategoriesChangedListener(); + +// ControlNet +addControlNetImageProcessedListener(); +addControlNetAutoProcessListener(); + +// Update image URLs on connect +// addUpdateImageUrlsOnConnectListener(); + +// Boards +addImageAddedToBoardFulfilledListener(); +addImageAddedToBoardRejectedListener(); +addImageRemovedFromBoardFulfilledListener(); +addImageRemovedFromBoardRejectedListener(); +addBoardIdSelectedListener(); + +// Node schemas +addReceivedOpenAPISchemaListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts new file mode 100644 index 0000000000..322cc51fa7 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts @@ -0,0 +1,42 @@ +import { startAppListening } from '..'; +import { log } from 'app/logging/useLogger'; +import { commitStagingAreaImage } from 'features/canvas/store/canvasSlice'; +import { sessionCanceled } from 'services/api/thunks/session'; + +const moduleLog = log.child({ namespace: 'canvas' }); + +export const addCommitStagingAreaImageListener = () => { + startAppListening({ + actionCreator: commitStagingAreaImage, + effect: async (action, { dispatch, getState }) => { + const state = getState(); + const { sessionId: session_id, isProcessing } = state.system; + const canvasSessionId = action.payload; + + if (!isProcessing) { + // Only need to cancel if we are processing + return; + } + + if (!canvasSessionId) { + moduleLog.debug('No canvas session, skipping cancel'); + return; + } + + if (canvasSessionId !== session_id) { + moduleLog.debug( + { + data: { + canvasSessionId, + session_id, + }, + }, + 'Canvas session does not match global session, skipping cancel' + ); + return; + } + + dispatch(sessionCanceled({ session_id })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts new file mode 100644 index 0000000000..1c96c5700d --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -0,0 +1,108 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { boardIdSelected } from 'features/gallery/store/boardSlice'; +import { selectImagesAll } from 'features/gallery/store/imagesSlice'; +import { + IMAGES_PER_PAGE, + receivedPageOfImages, +} from 'services/api/thunks/image'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { boardsApi } from 'services/api/endpoints/boards'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addBoardIdSelectedListener = () => { + startAppListening({ + actionCreator: boardIdSelected, + effect: (action, { getState, dispatch }) => { + const board_id = action.payload; + + // we need to check if we need to fetch more images + + const state = getState(); + const allImages = selectImagesAll(state); + + if (!board_id) { + // a board was unselected + dispatch(imageSelected(allImages[0]?.image_name)); + return; + } + + const { categories } = state.images; + + const filteredImages = allImages.filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = board_id ? i.board_id === board_id : true; + return isInCategory && isInSelectedBoard; + }); + + // get the board from the cache + const { data: boards } = + boardsApi.endpoints.listAllBoards.select()(state); + const board = boards?.find((b) => b.board_id === board_id); + + if (!board) { + // can't find the board in cache... + dispatch(imageSelected(allImages[0]?.image_name)); + return; + } + + dispatch(imageSelected(board.cover_image_name)); + + // if we haven't loaded one full page of images from this board, load more + if ( + filteredImages.length < board.image_count && + filteredImages.length < IMAGES_PER_PAGE + ) { + dispatch( + receivedPageOfImages({ categories, board_id, is_intermediate: false }) + ); + } + }, + }); +}; + +export const addBoardIdSelected_changeSelectedImage_listener = () => { + startAppListening({ + actionCreator: boardIdSelected, + effect: (action, { getState, dispatch }) => { + const board_id = action.payload; + + const state = getState(); + + // we need to check if we need to fetch more images + + if (!board_id) { + // a board was unselected - we don't need to do anything + return; + } + + const { categories } = state.images; + + const filteredImages = selectImagesAll(state).filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = board_id ? i.board_id === board_id : true; + return isInCategory && isInSelectedBoard; + }); + + // get the board from the cache + const { data: boards } = + boardsApi.endpoints.listAllBoards.select()(state); + const board = boards?.find((b) => b.board_id === board_id); + if (!board) { + // can't find the board in cache... + return; + } + + // if we haven't loaded one full page of images from this board, load more + if ( + filteredImages.length < board.image_count && + filteredImages.length < IMAGES_PER_PAGE + ) { + dispatch( + receivedPageOfImages({ categories, board_id, is_intermediate: false }) + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardImagesDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardImagesDeleted.ts new file mode 100644 index 0000000000..c4d3c5f0ba --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardImagesDeleted.ts @@ -0,0 +1,79 @@ +import { requestedBoardImagesDeletion } from 'features/gallery/store/actions'; +import { startAppListening } from '..'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { + imagesRemoved, + selectImagesAll, + selectImagesById, +} from 'features/gallery/store/imagesSlice'; +import { resetCanvas } from 'features/canvas/store/canvasSlice'; +import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; +import { clearInitialImage } from 'features/parameters/store/generationSlice'; +import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; +import { LIST_TAG, api } from 'services/api'; +import { boardsApi } from '../../../../../services/api/endpoints/boards'; + +export const addRequestedBoardImageDeletionListener = () => { + startAppListening({ + actionCreator: requestedBoardImagesDeletion, + effect: async (action, { dispatch, getState, condition }) => { + const { board, imagesUsage } = action.payload; + + const { board_id } = board; + + const state = getState(); + const selectedImage = state.gallery.selectedImage + ? selectImagesById(state, state.gallery.selectedImage) + : undefined; + + if (selectedImage && selectedImage.board_id === board_id) { + dispatch(imageSelected()); + } + + // We need to reset the features where the board images are in use - none of these work if their image(s) don't exist + + if (imagesUsage.isCanvasImage) { + dispatch(resetCanvas()); + } + + if (imagesUsage.isControlNetImage) { + dispatch(controlNetReset()); + } + + if (imagesUsage.isInitialImage) { + dispatch(clearInitialImage()); + } + + if (imagesUsage.isNodesImage) { + dispatch(nodeEditorReset()); + } + + // Preemptively remove from gallery + const images = selectImagesAll(state).reduce((acc: string[], img) => { + if (img.board_id === board_id) { + acc.push(img.image_name); + } + return acc; + }, []); + dispatch(imagesRemoved(images)); + + // Delete from server + dispatch(boardsApi.endpoints.deleteBoardAndImages.initiate(board_id)); + const result = + boardsApi.endpoints.deleteBoardAndImages.select(board_id)(state); + const { isSuccess } = result; + + // Wait for successful deletion, then trigger boards to re-fetch + const wasBoardDeleted = await condition(() => !!isSuccess, 30000); + + if (wasBoardDeleted) { + dispatch( + api.util.invalidateTags([ + { type: 'Board', id: board_id }, + { type: 'Image', id: LIST_TAG }, + ]) + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts index 16642f1f32..a7ddd8e917 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts @@ -28,6 +28,13 @@ export const addCanvasCopiedToClipboardListener = () => { } copyBlobToClipboard(blob); + + dispatch( + addToast({ + title: 'Canvas Copied to Clipboard', + status: 'success', + }) + ); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts index ef4c63b31c..c97df09cff 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts @@ -27,7 +27,8 @@ export const addCanvasDownloadedAsImageListener = () => { return; } - downloadBlob(blob, 'mergedCanvas.png'); + downloadBlob(blob, 'canvas.png'); + dispatch(addToast({ title: 'Canvas Downloaded', status: 'success' })); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts index fbc9c9c225..ce135ab3d0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts @@ -1,12 +1,11 @@ import { canvasMerged } from 'features/canvas/store/actions'; import { startAppListening } from '..'; import { log } from 'app/logging/useLogger'; -import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { addToast } from 'features/system/store/systemSlice'; -import { imageUploaded } from 'services/thunks/image'; -import { v4 as uuidv4 } from 'uuid'; +import { imageUploaded } from 'services/api/thunks/image'; import { setMergedCanvas } from 'features/canvas/store/canvasSlice'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; +import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob'; const moduleLog = log.child({ namespace: 'canvasCopiedToClipboardListener' }); @@ -14,9 +13,7 @@ export const addCanvasMergedListener = () => { startAppListening({ actionCreator: canvasMerged, effect: async (action, { dispatch, getState, take }) => { - const state = getState(); - - const blob = await getBaseLayerBlob(state, true); + const blob = await getFullBaseLayerBlob(); if (!blob) { moduleLog.error('Problem getting base layer blob'); @@ -48,29 +45,34 @@ export const addCanvasMergedListener = () => { relativeTo: canvasBaseLayer.getParent(), }); - const filename = `mergedCanvas_${uuidv4()}.png`; - - dispatch( + const imageUploadedRequest = dispatch( imageUploaded({ - formData: { - file: new File([blob], filename, { type: 'image/png' }), + file: new File([blob], 'mergedCanvas.png', { + type: 'image/png', + }), + image_category: 'general', + is_intermediate: true, + postUploadAction: { + type: 'TOAST_CANVAS_MERGED', }, }) ); const [{ payload }] = await take( - (action): action is ReturnType => - imageUploaded.fulfilled.match(action) && - action.meta.arg.formData.file.name === filename + ( + uploadedImageAction + ): uploadedImageAction is ReturnType => + imageUploaded.fulfilled.match(uploadedImageAction) && + uploadedImageAction.meta.requestId === imageUploadedRequest.requestId ); - const mergedCanvasImage = payload; + const { image_name } = payload; dispatch( setMergedCanvas({ kind: 'image', layer: 'base', - image: mergedCanvasImage, + imageName: image_name, ...baseLayerRect, }) ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts index 2df3dacea2..af55a1382e 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts @@ -1,16 +1,17 @@ import { canvasSavedToGallery } from 'features/canvas/store/actions'; import { startAppListening } from '..'; import { log } from 'app/logging/useLogger'; -import { imageUploaded } from 'services/thunks/image'; +import { imageUploaded } from 'services/api/thunks/image'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { addToast } from 'features/system/store/systemSlice'; +import { imageUpserted } from 'features/gallery/store/imagesSlice'; const moduleLog = log.child({ namespace: 'canvasSavedToGalleryListener' }); export const addCanvasSavedToGalleryListener = () => { startAppListening({ actionCreator: canvasSavedToGallery, - effect: async (action, { dispatch, getState }) => { + effect: async (action, { dispatch, getState, take }) => { const state = getState(); const blob = await getBaseLayerBlob(state); @@ -27,13 +28,28 @@ export const addCanvasSavedToGalleryListener = () => { return; } - dispatch( + const imageUploadedRequest = dispatch( imageUploaded({ - formData: { - file: new File([blob], 'mergedCanvas.png', { type: 'image/png' }), + file: new File([blob], 'savedCanvas.png', { + type: 'image/png', + }), + image_category: 'general', + is_intermediate: false, + postUploadAction: { + type: 'TOAST_CANVAS_SAVED_TO_GALLERY', }, }) ); + + const [{ payload: uploadedImageDTO }] = await take( + ( + uploadedImageAction + ): uploadedImageAction is ReturnType => + imageUploaded.fulfilled.match(uploadedImageAction) && + uploadedImageAction.meta.requestId === imageUploadedRequest.requestId + ); + + dispatch(imageUpserted(uploadedImageDTO)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts new file mode 100644 index 0000000000..dd2fb6f469 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts @@ -0,0 +1,71 @@ +import { AnyListenerPredicate } from '@reduxjs/toolkit'; +import { startAppListening } from '..'; +import { log } from 'app/logging/useLogger'; +import { controlNetImageProcessed } from 'features/controlNet/store/actions'; +import { + controlNetAutoConfigToggled, + controlNetImageChanged, + controlNetModelChanged, + controlNetProcessorParamsChanged, + controlNetProcessorTypeChanged, +} from 'features/controlNet/store/controlNetSlice'; +import { RootState } from 'app/store/store'; + +const moduleLog = log.child({ namespace: 'controlNet' }); + +const predicate: AnyListenerPredicate = (action, state) => { + const isActionMatched = + controlNetProcessorParamsChanged.match(action) || + controlNetModelChanged.match(action) || + controlNetImageChanged.match(action) || + controlNetProcessorTypeChanged.match(action) || + controlNetAutoConfigToggled.match(action); + + if (!isActionMatched) { + return false; + } + + const { controlImage, processorType, shouldAutoConfig } = + state.controlNet.controlNets[action.payload.controlNetId]; + + if (controlNetModelChanged.match(action) && !shouldAutoConfig) { + // do not process if the action is a model change but the processor settings are dirty + return false; + } + + const isProcessorSelected = processorType !== 'none'; + + const isBusy = state.system.isProcessing; + + const hasControlImage = Boolean(controlImage); + + return isProcessorSelected && !isBusy && hasControlImage; +}; + +/** + * Listener that automatically processes a ControlNet image when its processor parameters are changed. + * + * The network request is debounced by 1 second. + */ +export const addControlNetAutoProcessListener = () => { + startAppListening({ + predicate, + effect: async ( + action, + { dispatch, getState, cancelActiveListeners, delay } + ) => { + const { controlNetId } = action.payload; + + // Cancel any in-progress instances of this listener + cancelActiveListeners(); + moduleLog.trace( + { data: action.payload }, + 'ControlNet auto-process triggered' + ); + // Delay before starting actual work + await delay(300); + + dispatch(controlNetImageProcessed({ controlNetId })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts new file mode 100644 index 0000000000..3e11a5f98b --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts @@ -0,0 +1,89 @@ +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/api/thunks/image'; +import { log } from 'app/logging/useLogger'; +import { controlNetImageProcessed } from 'features/controlNet/store/actions'; +import { Graph } from 'services/api/types'; +import { sessionCreated } from 'services/api/thunks/session'; +import { sessionReadyToInvoke } from 'features/system/store/actions'; +import { socketInvocationComplete } from 'services/events/actions'; +import { isImageOutput } from 'services/api/guards'; +import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice'; + +const moduleLog = log.child({ namespace: 'controlNet' }); + +export const addControlNetImageProcessedListener = () => { + startAppListening({ + actionCreator: controlNetImageProcessed, + effect: async ( + action, + { dispatch, getState, take, unsubscribe, subscribe } + ) => { + const { controlNetId } = action.payload; + const controlNet = getState().controlNet.controlNets[controlNetId]; + + if (!controlNet.controlImage) { + moduleLog.error('Unable to process ControlNet image'); + return; + } + + // ControlNet one-off procressing graph is just the processor node, no edges. + // Also we need to grab the image. + const graph: Graph = { + nodes: { + [controlNet.processorNode.id]: { + ...controlNet.processorNode, + is_intermediate: true, + image: { image_name: controlNet.controlImage }, + }, + }, + }; + + // Create a session to run the graph & wait til it's ready to invoke + const sessionCreatedAction = dispatch(sessionCreated({ graph })); + const [sessionCreatedFulfilledAction] = await take( + (action): action is ReturnType => + sessionCreated.fulfilled.match(action) && + action.meta.requestId === sessionCreatedAction.requestId + ); + + const sessionId = sessionCreatedFulfilledAction.payload.id; + + // Invoke the session & wait til it's complete + dispatch(sessionReadyToInvoke()); + const [invocationCompleteAction] = await take( + (action): action is ReturnType => + socketInvocationComplete.match(action) && + action.payload.data.graph_execution_state_id === sessionId + ); + + // We still have to check the output type + if (isImageOutput(invocationCompleteAction.payload.data.result)) { + const { image_name } = + invocationCompleteAction.payload.data.result.image; + + // Wait for the ImageDTO to be received + const [imageMetadataReceivedAction] = await take( + ( + action + ): action is ReturnType => + imageMetadataReceived.fulfilled.match(action) && + action.payload.image_name === image_name + ); + const processedControlImage = imageMetadataReceivedAction.payload; + + moduleLog.debug( + { data: { arg: action.payload, processedControlImage } }, + 'ControlNet image processed' + ); + + // Update the processed image in the store + dispatch( + controlNetProcessedImageChanged({ + controlNetId, + processedControlImage: processedControlImage.image_name, + }) + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts new file mode 100644 index 0000000000..082dfc0efb --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts @@ -0,0 +1,40 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/api/thunks/image'; +import { boardImagesApi } from 'services/api/endpoints/boardImages'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addImageAddedToBoardFulfilledListener = () => { + startAppListening({ + matcher: boardImagesApi.endpoints.addImageToBoard.matchFulfilled, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Image added to board' + ); + + dispatch( + imageMetadataReceived({ + image_name, + }) + ); + }, + }); +}; + +export const addImageAddedToBoardRejectedListener = () => { + startAppListening({ + matcher: boardImagesApi.endpoints.addImageToBoard.matchRejected, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Problem adding image to board' + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts new file mode 100644 index 0000000000..25b7b7c11f --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageCategoriesChanged.ts @@ -0,0 +1,29 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { receivedPageOfImages } from 'services/api/thunks/image'; +import { + imageCategoriesChanged, + selectFilteredImagesAsArray, +} from 'features/gallery/store/imagesSlice'; + +const moduleLog = log.child({ namespace: 'gallery' }); + +export const addImageCategoriesChangedListener = () => { + startAppListening({ + actionCreator: imageCategoriesChanged, + effect: (action, { getState, dispatch }) => { + const state = getState(); + const filteredImagesCount = selectFilteredImagesAsArray(state).length; + + if (!filteredImagesCount) { + dispatch( + receivedPageOfImages({ + categories: action.payload, + board_id: state.boards.selectedBoardId, + is_intermediate: false, + }) + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index cd4771b96a..91cd509ca6 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -1,13 +1,20 @@ import { requestedImageDeletion } from 'features/gallery/store/actions'; import { startAppListening } from '..'; -import { imageDeleted } from 'services/thunks/image'; +import { imageDeleted } from 'services/api/thunks/image'; import { log } from 'app/logging/useLogger'; import { clamp } from 'lodash-es'; import { imageSelected } from 'features/gallery/store/gallerySlice'; -import { uploadsAdapter } from 'features/gallery/store/uploadsSlice'; -import { resultsAdapter } from 'features/gallery/store/resultsSlice'; +import { + imageRemoved, + selectImagesIds, +} from 'features/gallery/store/imagesSlice'; +import { resetCanvas } from 'features/canvas/store/canvasSlice'; +import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; +import { clearInitialImage } from 'features/parameters/store/generationSlice'; +import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; +import { api } from 'services/api'; -const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); +const moduleLog = log.child({ namespace: 'image' }); /** * Called when the user requests an image deletion @@ -15,26 +22,22 @@ const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); export const addRequestedImageDeletionListener = () => { startAppListening({ actionCreator: requestedImageDeletion, - effect: (action, { dispatch, getState }) => { - const image = action.payload; - if (!image) { - moduleLog.warn('No image provided'); - return; - } + effect: async (action, { dispatch, getState, condition }) => { + const { image, imageUsage } = action.payload; - const { image_name, image_type } = image; + const { image_name } = image; - const selectedImageName = getState().gallery.selectedImage?.image_name; + const state = getState(); + const selectedImage = state.gallery.selectedImage; - if (selectedImageName === image_name) { - const allIds = getState()[image_type].ids; - const allEntities = getState()[image_type].entities; + if (selectedImage === image_name) { + const ids = selectImagesIds(state); - const deletedImageIndex = allIds.findIndex( + const deletedImageIndex = ids.findIndex( (result) => result.toString() === image_name ); - const filteredIds = allIds.filter((id) => id.toString() !== image_name); + const filteredIds = ids.filter((id) => id.toString() !== image_name); const newSelectedImageIndex = clamp( deletedImageIndex, @@ -44,16 +47,50 @@ export const addRequestedImageDeletionListener = () => { const newSelectedImageId = filteredIds[newSelectedImageIndex]; - const newSelectedImage = allEntities[newSelectedImageId]; - if (newSelectedImageId) { - dispatch(imageSelected(newSelectedImage)); + dispatch(imageSelected(newSelectedImageId as string)); } else { dispatch(imageSelected()); } } - dispatch(imageDeleted({ imageName: image_name, imageType: image_type })); + // We need to reset the features where the image is in use - none of these work if their image(s) don't exist + + if (imageUsage.isCanvasImage) { + dispatch(resetCanvas()); + } + + if (imageUsage.isControlNetImage) { + dispatch(controlNetReset()); + } + + if (imageUsage.isInitialImage) { + dispatch(clearInitialImage()); + } + + if (imageUsage.isNodesImage) { + dispatch(nodeEditorReset()); + } + + // Preemptively remove from gallery + dispatch(imageRemoved(image_name)); + + // Delete from server + const { requestId } = dispatch(imageDeleted({ image_name })); + + // Wait for successful deletion, then trigger boards to re-fetch + const wasImageDeleted = await condition( + (action): action is ReturnType => + imageDeleted.fulfilled.match(action) && + action.meta.requestId === requestId, + 30000 + ); + + if (wasImageDeleted) { + dispatch( + api.util.invalidateTags([{ type: 'Board', id: image.board_id }]) + ); + } }, }); }; @@ -65,14 +102,7 @@ export const addImageDeletedPendingListener = () => { startAppListening({ actionCreator: imageDeleted.pending, effect: (action, { dispatch, getState }) => { - const { imageName, imageType } = action.meta.arg; - // Preemptively remove the image from the gallery - if (imageType === 'uploads') { - uploadsAdapter.removeOne(getState().uploads, imageName); - } - if (imageType === 'results') { - resultsAdapter.removeOne(getState().results, imageName); - } + // }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts index c93ed2820f..24265faaa9 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts @@ -1,14 +1,7 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; -import { imageMetadataReceived } from 'services/thunks/image'; -import { - ResultsImageDTO, - resultUpserted, -} from 'features/gallery/store/resultsSlice'; -import { - UploadsImageDTO, - uploadUpserted, -} from 'features/gallery/store/uploadsSlice'; +import { imageMetadataReceived, imageUpdated } from 'services/api/thunks/image'; +import { imageUpserted } from 'features/gallery/store/imagesSlice'; const moduleLog = log.child({ namespace: 'image' }); @@ -17,15 +10,30 @@ export const addImageMetadataReceivedFulfilledListener = () => { actionCreator: imageMetadataReceived.fulfilled, effect: (action, { getState, dispatch }) => { const image = action.payload; + + const state = getState(); + + if ( + image.session_id === state.canvas.layerState.stagingArea.sessionId && + state.canvas.shouldAutoSave + ) { + dispatch( + imageUpdated({ + image_name: image.image_name, + is_intermediate: image.is_intermediate, + }) + ); + } else if (image.is_intermediate) { + // No further actions needed for intermediate images + moduleLog.trace( + { data: { image } }, + 'Image metadata received (intermediate), skipping' + ); + return; + } + moduleLog.debug({ data: { image } }, 'Image metadata received'); - - if (image.image_type === 'results') { - dispatch(resultUpserted(action.payload as ResultsImageDTO)); - } - - if (image.image_type === 'uploads') { - dispatch(uploadUpserted(action.payload as UploadsImageDTO)); - } + dispatch(imageUpserted(image)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts new file mode 100644 index 0000000000..5c056474e3 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard.ts @@ -0,0 +1,40 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/api/thunks/image'; +import { boardImagesApi } from 'services/api/endpoints/boardImages'; + +const moduleLog = log.child({ namespace: 'boards' }); + +export const addImageRemovedFromBoardFulfilledListener = () => { + startAppListening({ + matcher: boardImagesApi.endpoints.removeImageFromBoard.matchFulfilled, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Image added to board' + ); + + dispatch( + imageMetadataReceived({ + image_name, + }) + ); + }, + }); +}; + +export const addImageRemovedFromBoardRejectedListener = () => { + startAppListening({ + matcher: boardImagesApi.endpoints.removeImageFromBoard.matchRejected, + effect: (action, { getState, dispatch }) => { + const { board_id, image_name } = action.meta.arg.originalArgs; + + moduleLog.debug( + { data: { board_id, image_name } }, + 'Problem adding image to board' + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUpdated.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUpdated.ts new file mode 100644 index 0000000000..2e235aeb33 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUpdated.ts @@ -0,0 +1,26 @@ +import { startAppListening } from '..'; +import { imageUpdated } from 'services/api/thunks/image'; +import { log } from 'app/logging/useLogger'; + +const moduleLog = log.child({ namespace: 'image' }); + +export const addImageUpdatedFulfilledListener = () => { + startAppListening({ + actionCreator: imageUpdated.fulfilled, + effect: (action, { dispatch, getState }) => { + moduleLog.debug( + { oldImage: action.meta.arg, updatedImage: action.payload }, + 'Image updated' + ); + }, + }); +}; + +export const addImageUpdatedRejectedListener = () => { + startAppListening({ + actionCreator: imageUpdated.rejected, + effect: (action, { dispatch }) => { + moduleLog.debug({ oldImage: action.meta.arg }, 'Image update failed'); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index 5b177eae91..f55ed11c8f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -1,51 +1,74 @@ import { startAppListening } from '..'; -import { uploadUpserted } from 'features/gallery/store/uploadsSlice'; -import { imageSelected } from 'features/gallery/store/gallerySlice'; -import { imageUploaded } from 'services/thunks/image'; +import { imageUploaded } from 'services/api/thunks/image'; import { addToast } from 'features/system/store/systemSlice'; -import { initialImageSelected } from 'features/parameters/store/actions'; -import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; -import { resultUpserted } from 'features/gallery/store/resultsSlice'; -import { isResultsImageDTO, isUploadsImageDTO } from 'services/types/guards'; import { log } from 'app/logging/useLogger'; +import { imageUpserted } from 'features/gallery/store/imagesSlice'; +import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; +import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlice'; +import { initialImageChanged } from 'features/parameters/store/generationSlice'; +import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; const moduleLog = log.child({ namespace: 'image' }); export const addImageUploadedFulfilledListener = () => { startAppListening({ - predicate: (action): action is ReturnType => - imageUploaded.fulfilled.match(action) && - action.payload.is_intermediate === false, + actionCreator: imageUploaded.fulfilled, effect: (action, { dispatch, getState }) => { const image = action.payload; moduleLog.debug({ arg: '', image }, 'Image uploaded'); - const state = getState(); - - // Handle uploads - if (isUploadsImageDTO(image)) { - dispatch(uploadUpserted(image)); - - dispatch(addToast({ title: 'Image Uploaded', status: 'success' })); - - if (state.gallery.shouldAutoSwitchToNewImages) { - dispatch(imageSelected(image)); - } - - if (action.meta.arg.activeTabName === 'img2img') { - dispatch(initialImageSelected(image)); - } - - if (action.meta.arg.activeTabName === 'unifiedCanvas') { - dispatch(setInitialCanvasImage(image)); - } + if (action.payload.is_intermediate) { + // No further actions needed for intermediate images + return; } - // Handle results - // TODO: Can this ever happen? I don't think so... - if (isResultsImageDTO(image)) { - dispatch(resultUpserted(image)); + dispatch(imageUpserted(image)); + + const { postUploadAction } = action.meta.arg; + + if (postUploadAction?.type === 'TOAST_CANVAS_SAVED_TO_GALLERY') { + dispatch( + addToast({ title: 'Canvas Saved to Gallery', status: 'success' }) + ); + return; + } + + if (postUploadAction?.type === 'TOAST_CANVAS_MERGED') { + dispatch(addToast({ title: 'Canvas Merged', status: 'success' })); + return; + } + + if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') { + dispatch(setInitialCanvasImage(image)); + return; + } + + if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') { + const { controlNetId } = postUploadAction; + dispatch( + controlNetImageChanged({ + controlNetId, + controlImage: image.image_name, + }) + ); + return; + } + + if (postUploadAction?.type === 'SET_INITIAL_IMAGE') { + dispatch(initialImageChanged(image)); + return; + } + + if (postUploadAction?.type === 'SET_NODES_IMAGE') { + const { nodeId, fieldName } = postUploadAction; + dispatch(fieldValueChanged({ nodeId, fieldName, value: image })); + return; + } + + if (postUploadAction?.type === 'TOAST_UPLOADED') { + dispatch(addToast({ title: 'Image Uploaded', status: 'success' })); + return; } }, }); @@ -55,6 +78,9 @@ export const addImageUploadedRejectedListener = () => { startAppListening({ actionCreator: imageUploaded.rejected, effect: (action, { dispatch }) => { + const { formData, ...rest } = action.meta.arg; + const sanitizedData = { arg: { ...rest, formData: { file: '' } } }; + moduleLog.error({ data: sanitizedData }, 'Image upload failed'); dispatch( addToast({ title: 'Image Upload Failed', diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts index 4ff2a02118..c663c64361 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts @@ -1,8 +1,7 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; -import { imageUrlsReceived } from 'services/thunks/image'; -import { resultsAdapter } from 'features/gallery/store/resultsSlice'; -import { uploadsAdapter } from 'features/gallery/store/uploadsSlice'; +import { imageUrlsReceived } from 'services/api/thunks/image'; +import { imageUpdatedOne } from 'features/gallery/store/imagesSlice'; const moduleLog = log.child({ namespace: 'image' }); @@ -13,27 +12,14 @@ export const addImageUrlsReceivedFulfilledListener = () => { const image = action.payload; moduleLog.debug({ data: { image } }, 'Image URLs received'); - const { image_type, image_name, image_url, thumbnail_url } = image; + const { image_name, image_url, thumbnail_url } = image; - if (image_type === 'results') { - resultsAdapter.updateOne(getState().results, { + dispatch( + imageUpdatedOne({ id: image_name, - changes: { - image_url, - thumbnail_url, - }, - }); - } - - if (image_type === 'uploads') { - uploadsAdapter.updateOne(getState().uploads, { - id: image_name, - changes: { - image_url, - thumbnail_url, - }, - }); - } + changes: { image_url, thumbnail_url }, + }) + ); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts index d6cfc260f3..9aca82a32b 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts @@ -1,15 +1,11 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice'; -import { selectResultsById } from 'features/gallery/store/resultsSlice'; -import { selectUploadsById } from 'features/gallery/store/uploadsSlice'; import { t } from 'i18next'; import { addToast } from 'features/system/store/systemSlice'; import { startAppListening } from '..'; -import { - initialImageSelected, - isImageDTO, -} from 'features/parameters/store/actions'; +import { initialImageSelected } from 'features/parameters/store/actions'; import { makeToast } from 'app/components/Toaster'; -import { ImageDTO } from 'services/api'; +import { selectImagesById } from 'features/gallery/store/imagesSlice'; +import { isImageDTO } from 'services/api/guards'; export const addInitialImageSelectedListener = () => { startAppListening({ @@ -30,16 +26,8 @@ export const addInitialImageSelectedListener = () => { return; } - const { image_name, image_type } = action.payload; - - let image: ImageDTO | undefined; - const state = getState(); - - if (image_type === 'results') { - image = selectResultsById(state, image_name); - } else if (image_type === 'uploads') { - image = selectUploadsById(state, image_name); - } + const imageName = action.payload; + const image = selectImagesById(getState(), imageName); if (!image) { dispatch( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedOpenAPISchema.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedOpenAPISchema.ts new file mode 100644 index 0000000000..3cc3147b45 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedOpenAPISchema.ts @@ -0,0 +1,35 @@ +import { receivedOpenAPISchema } from 'services/api/thunks/schema'; +import { startAppListening } from '..'; +import { log } from 'app/logging/useLogger'; +import { parseSchema } from 'features/nodes/util/parseSchema'; +import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice'; +import { size } from 'lodash-es'; + +const schemaLog = log.child({ namespace: 'schema' }); + +export const addReceivedOpenAPISchemaListener = () => { + startAppListening({ + actionCreator: receivedOpenAPISchema.fulfilled, + effect: (action, { dispatch, getState }) => { + const schemaJSON = action.payload; + + schemaLog.info({ data: { schemaJSON } }, 'Dereferenced OpenAPI schema'); + + const nodeTemplates = parseSchema(schemaJSON); + + schemaLog.info( + { data: { nodeTemplates } }, + `Built ${size(nodeTemplates)} node templates` + ); + + dispatch(nodeTemplatesBuilt(nodeTemplates)); + }, + }); + + startAppListening({ + actionCreator: receivedOpenAPISchema.rejected, + effect: (action, { dispatch, getState }) => { + schemaLog.error('Problem dereferencing OpenAPI Schema'); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedPageOfImages.ts similarity index 50% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedPageOfImages.ts index bcdd11ef97..e357d38dc3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedPageOfImages.ts @@ -1,31 +1,31 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; -import { receivedResultImagesPage } from 'services/thunks/gallery'; import { serializeError } from 'serialize-error'; +import { receivedPageOfImages } from 'services/api/thunks/image'; const moduleLog = log.child({ namespace: 'gallery' }); -export const addReceivedResultImagesPageFulfilledListener = () => { +export const addReceivedPageOfImagesFulfilledListener = () => { startAppListening({ - actionCreator: receivedResultImagesPage.fulfilled, + actionCreator: receivedPageOfImages.fulfilled, effect: (action, { getState, dispatch }) => { const page = action.payload; moduleLog.debug( - { data: { page } }, - `Received ${page.items.length} results` + { data: { payload: action.payload } }, + `Received ${page.items.length} images` ); }, }); }; -export const addReceivedResultImagesPageRejectedListener = () => { +export const addReceivedPageOfImagesRejectedListener = () => { startAppListening({ - actionCreator: receivedResultImagesPage.rejected, + actionCreator: receivedPageOfImages.rejected, effect: (action, { getState, dispatch }) => { if (action.payload) { moduleLog.debug( - { data: { error: serializeError(action.payload.error) } }, - 'Problem receiving results' + { data: { error: serializeError(action.payload) } }, + 'Problem receiving images' ); } }, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts deleted file mode 100644 index 68813aae27..0000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { log } from 'app/logging/useLogger'; -import { startAppListening } from '..'; -import { receivedUploadImagesPage } from 'services/thunks/gallery'; -import { serializeError } from 'serialize-error'; - -const moduleLog = log.child({ namespace: 'gallery' }); - -export const addReceivedUploadImagesPageFulfilledListener = () => { - startAppListening({ - actionCreator: receivedUploadImagesPage.fulfilled, - effect: (action, { getState, dispatch }) => { - const page = action.payload; - moduleLog.debug( - { data: { page } }, - `Received ${page.items.length} uploads` - ); - }, - }); -}; - -export const addReceivedUploadImagesPageRejectedListener = () => { - startAppListening({ - actionCreator: receivedUploadImagesPage.rejected, - effect: (action, { getState, dispatch }) => { - if (action.payload) { - moduleLog.debug( - { data: { error: serializeError(action.payload.error) } }, - 'Problem receiving uploads' - ); - } - }, - }); -}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts index 6274ad4dc8..85e2627d88 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts @@ -1,6 +1,6 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; -import { sessionCanceled } from 'services/thunks/session'; +import { sessionCanceled } from 'services/api/thunks/session'; import { serializeError } from 'serialize-error'; const moduleLog = log.child({ namespace: 'session' }); @@ -18,10 +18,10 @@ export const addSessionCanceledFulfilledListener = () => { startAppListening({ actionCreator: sessionCanceled.fulfilled, effect: (action, { getState, dispatch }) => { - const { sessionId } = action.meta.arg; + const { session_id } = action.meta.arg; moduleLog.debug( - { data: { sessionId } }, - `Session canceled (${sessionId})` + { data: { session_id } }, + `Session canceled (${session_id})` ); }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts index fb8a64d2e3..c502b4e38c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts @@ -1,6 +1,6 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; -import { sessionCreated } from 'services/thunks/session'; +import { sessionCreated } from 'services/api/thunks/session'; import { serializeError } from 'serialize-error'; const moduleLog = log.child({ namespace: 'session' }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts index 272d1d9e1d..6aff246cbe 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts @@ -1,6 +1,6 @@ import { log } from 'app/logging/useLogger'; import { startAppListening } from '..'; -import { sessionInvoked } from 'services/thunks/session'; +import { sessionInvoked } from 'services/api/thunks/session'; import { serializeError } from 'serialize-error'; const moduleLog = log.child({ namespace: 'session' }); @@ -18,10 +18,10 @@ export const addSessionInvokedFulfilledListener = () => { startAppListening({ actionCreator: sessionInvoked.fulfilled, effect: (action, { getState, dispatch }) => { - const { sessionId } = action.meta.arg; + const { session_id } = action.meta.arg; moduleLog.debug( - { data: { sessionId } }, - `Session invoked (${sessionId})` + { data: { session_id } }, + `Session invoked (${session_id})` ); }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts index 8d4262e7da..cc77403709 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts @@ -1,5 +1,5 @@ import { startAppListening } from '..'; -import { sessionInvoked } from 'services/thunks/session'; +import { sessionInvoked } from 'services/api/thunks/session'; import { log } from 'app/logging/useLogger'; import { sessionReadyToInvoke } from 'features/system/store/actions'; @@ -9,13 +9,13 @@ export const addSessionReadyToInvokeListener = () => { startAppListening({ actionCreator: sessionReadyToInvoke, effect: (action, { getState, dispatch }) => { - const { sessionId } = getState().system; - if (sessionId) { + const { sessionId: session_id } = getState().system; + if (session_id) { moduleLog.debug( - { sessionId }, - `Session ready to invoke (${sessionId})})` + { session_id }, + `Session ready to invoke (${session_id})})` ); - dispatch(sessionInvoked({ sessionId })); + dispatch(sessionInvoked({ session_id })); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index bc9ecbec1e..976c1558d0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,16 +1,12 @@ -import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { socketConnected } from 'services/events/actions'; -import { - receivedResultImagesPage, - receivedUploadImagesPage, -} from 'services/thunks/gallery'; -import { receivedModels } from 'services/thunks/model'; -import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { appSocketConnected, socketConnected } from 'services/events/actions'; +import { receivedPageOfImages } from 'services/api/thunks/image'; +import { receivedOpenAPISchema } from 'services/api/thunks/schema'; +import { startAppListening } from '../..'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addSocketConnectedListener = () => { +export const addSocketConnectedEventListener = () => { startAppListening({ actionCreator: socketConnected, effect: (action, { dispatch, getState }) => { @@ -18,26 +14,25 @@ export const addSocketConnectedListener = () => { moduleLog.debug({ timestamp }, 'Connected'); - const { results, uploads, models, nodes, config } = getState(); + const { nodes, config, images } = getState(); const { disabledTabs } = config; - // These thunks need to be dispatch in middleware; cannot handle in a reducer - if (!results.ids.length) { - dispatch(receivedResultImagesPage()); - } - - if (!uploads.ids.length) { - dispatch(receivedUploadImagesPage()); - } - - if (!models.ids.length) { - dispatch(receivedModels()); + if (!images.ids.length) { + dispatch( + receivedPageOfImages({ + categories: ['general'], + is_intermediate: false, + }) + ); } if (!nodes.schema && !disabledTabs.includes('nodes')) { dispatch(receivedOpenAPISchema()); } + + // pass along the socket event as an application action + dispatch(appSocketConnected(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected.ts index 131c3ba18f..d5e8914cef 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected.ts @@ -1,14 +1,19 @@ import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { socketDisconnected } from 'services/events/actions'; +import { + socketDisconnected, + appSocketDisconnected, +} from 'services/events/actions'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addSocketDisconnectedListener = () => { +export const addSocketDisconnectedEventListener = () => { startAppListening({ actionCreator: socketDisconnected, effect: (action, { dispatch, getState }) => { moduleLog.debug(action.payload, 'Disconnected'); + // pass along the socket event as an application action + dispatch(appSocketDisconnected(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/generatorProgress.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress.ts similarity index 65% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/generatorProgress.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress.ts index 341b5e46d3..756444d644 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/generatorProgress.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress.ts @@ -1,12 +1,15 @@ import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { generatorProgress } from 'services/events/actions'; +import { + appSocketGeneratorProgress, + socketGeneratorProgress, +} from 'services/events/actions'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addGeneratorProgressListener = () => { +export const addGeneratorProgressEventListener = () => { startAppListening({ - actionCreator: generatorProgress, + actionCreator: socketGeneratorProgress, effect: (action, { dispatch, getState }) => { if ( getState().system.canceledSession === @@ -23,6 +26,9 @@ export const addGeneratorProgressListener = () => { action.payload, `Generator progress (${action.payload.data.node.type})` ); + + // pass along the socket event as an application action + dispatch(appSocketGeneratorProgress(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/graphExecutionStateComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketGraphExecutionStateComplete.ts similarity index 50% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/graphExecutionStateComplete.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketGraphExecutionStateComplete.ts index a66a7fb547..7297825e32 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/graphExecutionStateComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketGraphExecutionStateComplete.ts @@ -1,17 +1,22 @@ import { log } from 'app/logging/useLogger'; -import { graphExecutionStateComplete } from 'services/events/actions'; +import { + appSocketGraphExecutionStateComplete, + socketGraphExecutionStateComplete, +} from 'services/events/actions'; import { startAppListening } from '../..'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addGraphExecutionStateCompleteListener = () => { +export const addGraphExecutionStateCompleteEventListener = () => { startAppListening({ - actionCreator: graphExecutionStateComplete, + actionCreator: socketGraphExecutionStateComplete, effect: (action, { dispatch, getState }) => { moduleLog.debug( action.payload, `Session invocation complete (${action.payload.data.graph_execution_state_id})` ); + // pass along the socket event as an application action + dispatch(appSocketGraphExecutionStateComplete(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts similarity index 53% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 95e6d831c0..3686816d5c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -1,32 +1,36 @@ import { addImageToStagingArea } from 'features/canvas/store/canvasSlice'; import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { invocationComplete } from 'services/events/actions'; -import { imageMetadataReceived } from 'services/thunks/image'; -import { sessionCanceled } from 'services/thunks/session'; -import { isImageOutput } from 'services/types/guards'; +import { + appSocketInvocationComplete, + socketInvocationComplete, +} from 'services/events/actions'; +import { imageMetadataReceived } from 'services/api/thunks/image'; +import { sessionCanceled } from 'services/api/thunks/session'; +import { isImageOutput } from 'services/api/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; -import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { boardImagesApi } from 'services/api/endpoints/boardImages'; const moduleLog = log.child({ namespace: 'socketio' }); const nodeDenylist = ['dataURL_image']; -export const addInvocationCompleteListener = () => { +export const addInvocationCompleteEventListener = () => { startAppListening({ - actionCreator: invocationComplete, + actionCreator: socketInvocationComplete, effect: async (action, { dispatch, getState, take }) => { moduleLog.debug( - action.payload, + { data: action.payload }, `Invocation complete (${action.payload.data.node.type})` ); - const sessionId = action.payload.data.graph_execution_state_id; + const session_id = action.payload.data.graph_execution_state_id; - const { cancelType, isCancelScheduled } = getState().system; + const { cancelType, isCancelScheduled, boardIdToAddTo } = + getState().system; // Handle scheduled cancelation if (cancelType === 'scheduled' && isCancelScheduled) { - dispatch(sessionCanceled({ sessionId })); + dispatch(sessionCanceled({ session_id })); } const { data } = action.payload; @@ -34,13 +38,12 @@ export const addInvocationCompleteListener = () => { // This complete event has an associated image output if (isImageOutput(result) && !nodeDenylist.includes(node.type)) { - const { image_name, image_type } = result.image; + const { image_name } = result.image; // Get its metadata dispatch( imageMetadataReceived({ - imageName: image_name, - imageType: image_type, + image_name, }) ); @@ -48,27 +51,27 @@ export const addInvocationCompleteListener = () => { imageMetadataReceived.fulfilled.match ); - if (getState().gallery.shouldAutoSwitchToNewImages) { - dispatch(imageSelected(imageDTO)); - } - // Handle canvas image if ( graph_execution_state_id === getState().canvas.layerState.stagingArea.sessionId ) { - const [{ payload: image }] = await take( - ( - action - ): action is ReturnType => - imageMetadataReceived.fulfilled.match(action) && - action.payload.image_name === image_name + dispatch(addImageToStagingArea(imageDTO)); + } + + if (boardIdToAddTo && !imageDTO.is_intermediate) { + dispatch( + boardImagesApi.endpoints.addImageToBoard.initiate({ + board_id: boardIdToAddTo, + image_name, + }) ); - dispatch(addImageToStagingArea(image)); } dispatch(progressImageSet(null)); } + // pass along the socket event as an application action + dispatch(appSocketInvocationComplete(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationError.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError.ts similarity index 58% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationError.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError.ts index 3a98af120a..51480bbad4 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationError.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError.ts @@ -1,17 +1,21 @@ import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { invocationError } from 'services/events/actions'; +import { + appSocketInvocationError, + socketInvocationError, +} from 'services/events/actions'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addInvocationErrorListener = () => { +export const addInvocationErrorEventListener = () => { startAppListening({ - actionCreator: invocationError, + actionCreator: socketInvocationError, effect: (action, { dispatch, getState }) => { moduleLog.error( action.payload, `Invocation error (${action.payload.data.node.type})` ); + dispatch(appSocketInvocationError(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationStarted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted.ts similarity index 70% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationStarted.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted.ts index f898c62b23..978be2fef5 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationStarted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted.ts @@ -1,12 +1,15 @@ import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { invocationStarted } from 'services/events/actions'; +import { + appSocketInvocationStarted, + socketInvocationStarted, +} from 'services/events/actions'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addInvocationStartedListener = () => { +export const addInvocationStartedEventListener = () => { startAppListening({ - actionCreator: invocationStarted, + actionCreator: socketInvocationStarted, effect: (action, { dispatch, getState }) => { if ( getState().system.canceledSession === @@ -23,6 +26,7 @@ export const addInvocationStartedListener = () => { action.payload, `Invocation started (${action.payload.data.node.type})` ); + dispatch(appSocketInvocationStarted(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketSubscribed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketSubscribed.ts index 400f8a1689..871222981d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketSubscribed.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketSubscribed.ts @@ -1,10 +1,10 @@ import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { socketSubscribed } from 'services/events/actions'; +import { appSocketSubscribed, socketSubscribed } from 'services/events/actions'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addSocketSubscribedListener = () => { +export const addSocketSubscribedEventListener = () => { startAppListening({ actionCreator: socketSubscribed, effect: (action, { dispatch, getState }) => { @@ -12,6 +12,7 @@ export const addSocketSubscribedListener = () => { action.payload, `Subscribed (${action.payload.sessionId}))` ); + dispatch(appSocketSubscribed(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed.ts index af15c55d42..ff85379907 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed.ts @@ -1,10 +1,13 @@ import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; -import { socketUnsubscribed } from 'services/events/actions'; +import { + appSocketUnsubscribed, + socketUnsubscribed, +} from 'services/events/actions'; const moduleLog = log.child({ namespace: 'socketio' }); -export const addSocketUnsubscribedListener = () => { +export const addSocketUnsubscribedEventListener = () => { startAppListening({ actionCreator: socketUnsubscribed, effect: (action, { dispatch, getState }) => { @@ -12,6 +15,7 @@ export const addSocketUnsubscribedListener = () => { action.payload, `Unsubscribed (${action.payload.sessionId})` ); + dispatch(appSocketUnsubscribed(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved.ts new file mode 100644 index 0000000000..bc2c1d1c27 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved.ts @@ -0,0 +1,51 @@ +import { stagingAreaImageSaved } from 'features/canvas/store/actions'; +import { startAppListening } from '..'; +import { log } from 'app/logging/useLogger'; +import { imageUpdated } from 'services/api/thunks/image'; +import { imageUpserted } from 'features/gallery/store/imagesSlice'; +import { addToast } from 'features/system/store/systemSlice'; + +const moduleLog = log.child({ namespace: 'canvas' }); + +export const addStagingAreaImageSavedListener = () => { + startAppListening({ + actionCreator: stagingAreaImageSaved, + effect: async (action, { dispatch, getState, take }) => { + const { imageName } = action.payload; + + dispatch( + imageUpdated({ + image_name: imageName, + is_intermediate: false, + }) + ); + + const [imageUpdatedAction] = await take( + (action) => + (imageUpdated.fulfilled.match(action) || + imageUpdated.rejected.match(action)) && + action.meta.arg.image_name === imageName + ); + + if (imageUpdated.rejected.match(imageUpdatedAction)) { + moduleLog.error( + { data: { arg: imageUpdatedAction.meta.arg } }, + 'Image saving failed' + ); + dispatch( + addToast({ + title: 'Image Saving Failed', + description: imageUpdatedAction.error.message, + status: 'error', + }) + ); + return; + } + + if (imageUpdated.fulfilled.match(imageUpdatedAction)) { + dispatch(imageUpserted(imageUpdatedAction.payload)); + dispatch(addToast({ title: 'Image Saved', status: 'success' })); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts new file mode 100644 index 0000000000..670d762d24 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateImageUrlsOnConnect.ts @@ -0,0 +1,91 @@ +import { socketConnected } from 'services/events/actions'; +import { startAppListening } from '..'; +import { createSelector } from '@reduxjs/toolkit'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { nodesSelector } from 'features/nodes/store/nodesSlice'; +import { controlNetSelector } from 'features/controlNet/store/controlNetSlice'; +import { forEach, uniqBy } from 'lodash-es'; +import { imageUrlsReceived } from 'services/api/thunks/image'; +import { log } from 'app/logging/useLogger'; +import { selectImagesEntities } from 'features/gallery/store/imagesSlice'; + +const moduleLog = log.child({ namespace: 'images' }); + +const selectAllUsedImages = createSelector( + [ + generationSelector, + canvasSelector, + nodesSelector, + controlNetSelector, + selectImagesEntities, + ], + (generation, canvas, nodes, controlNet, imageEntities) => { + const allUsedImages: string[] = []; + + if (generation.initialImage) { + allUsedImages.push(generation.initialImage.imageName); + } + + canvas.layerState.objects.forEach((obj) => { + if (obj.kind === 'image') { + allUsedImages.push(obj.imageName); + } + }); + + nodes.nodes.forEach((node) => { + forEach(node.data.inputs, (input) => { + if (input.type === 'image' && input.value) { + allUsedImages.push(input.value); + } + }); + }); + + forEach(controlNet.controlNets, (c) => { + if (c.controlImage) { + allUsedImages.push(c.controlImage); + } + if (c.processedControlImage) { + allUsedImages.push(c.processedControlImage); + } + }); + + forEach(imageEntities, (image) => { + if (image) { + allUsedImages.push(image.image_name); + } + }); + + const uniqueImages = uniqBy(allUsedImages, 'image_name'); + + return uniqueImages; + } +); + +export const addUpdateImageUrlsOnConnectListener = () => { + startAppListening({ + actionCreator: socketConnected, + effect: async (action, { dispatch, getState, take }) => { + const state = getState(); + + if (!state.config.shouldUpdateImagesOnConnect) { + return; + } + + const allUsedImages = selectAllUsedImages(state); + + moduleLog.trace( + { data: allUsedImages }, + `Fetching new image URLs for ${allUsedImages.length} images` + ); + + allUsedImages.forEach((image_name) => { + dispatch( + imageUrlsReceived({ + image_name, + }) + ); + }); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts index ae388b85cf..1f9f773392 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts @@ -1,11 +1,10 @@ import { startAppListening } from '..'; -import { sessionCreated } from 'services/thunks/session'; -import { buildCanvasGraphComponents } from 'features/nodes/util/graphBuilders/buildCanvasGraph'; +import { sessionCreated } from 'services/api/thunks/session'; +import { buildCanvasGraph } from 'features/nodes/util/graphBuilders/buildCanvasGraph'; import { log } from 'app/logging/useLogger'; import { canvasGraphBuilt } from 'features/nodes/store/actions'; -import { imageUpdated, imageUploaded } from 'services/thunks/image'; -import { v4 as uuidv4 } from 'uuid'; -import { Graph } from 'services/api'; +import { imageUpdated, imageUploaded } from 'services/api/thunks/image'; +import { ImageDTO } from 'services/api/types'; import { canvasSessionIdChanged, stagingAreaInitialized, @@ -67,114 +66,102 @@ export const addUserInvokedCanvasListener = () => { moduleLog.debug(`Generation mode: ${generationMode}`); - // Build the canvas graph - const graphComponents = await buildCanvasGraphComponents( + // Temp placeholders for the init and mask images + let canvasInitImage: ImageDTO | undefined; + let canvasMaskImage: ImageDTO | undefined; + + // For img2img and inpaint/outpaint, we need to upload the init images + if (['img2img', 'inpaint', 'outpaint'].includes(generationMode)) { + // upload the image, saving the request id + const { requestId: initImageUploadedRequestId } = dispatch( + imageUploaded({ + file: new File([baseBlob], 'canvasInitImage.png', { + type: 'image/png', + }), + image_category: 'general', + is_intermediate: true, + }) + ); + + // Wait for the image to be uploaded, matching by request id + const [{ payload }] = await take( + (action): action is ReturnType => + imageUploaded.fulfilled.match(action) && + action.meta.requestId === initImageUploadedRequestId + ); + + canvasInitImage = payload; + } + + // For inpaint/outpaint, we also need to upload the mask layer + if (['inpaint', 'outpaint'].includes(generationMode)) { + // upload the image, saving the request id + const { requestId: maskImageUploadedRequestId } = dispatch( + imageUploaded({ + file: new File([maskBlob], 'canvasMaskImage.png', { + type: 'image/png', + }), + image_category: 'mask', + is_intermediate: true, + }) + ); + + // Wait for the image to be uploaded, matching by request id + const [{ payload }] = await take( + (action): action is ReturnType => + imageUploaded.fulfilled.match(action) && + action.meta.requestId === maskImageUploadedRequestId + ); + + canvasMaskImage = payload; + } + + const graph = buildCanvasGraph( state, - generationMode + generationMode, + canvasInitImage, + canvasMaskImage ); - if (!graphComponents) { - moduleLog.error('Problem building graph'); - return; - } - - const { rangeNode, iterateNode, baseNode, edges } = graphComponents; - - // Assemble! Note that this graph *does not have the init or mask image set yet!* - const nodes: Graph['nodes'] = { - [rangeNode.id]: rangeNode, - [iterateNode.id]: iterateNode, - [baseNode.id]: baseNode, - }; - - const graph = { nodes, edges }; + moduleLog.debug({ graph }, `Canvas graph built`); + // currently this action is just listened to for logging dispatch(canvasGraphBuilt(graph)); - moduleLog.debug({ data: graph }, 'Canvas graph built'); + // Create the session, store the request id + const { requestId: sessionCreatedRequestId } = dispatch( + sessionCreated({ graph }) + ); - // If we are generating img2img or inpaint, we need to upload the init images - if (baseNode.type === 'img2img' || baseNode.type === 'inpaint') { - const baseFilename = `${uuidv4()}.png`; - dispatch( - imageUploaded({ - formData: { - file: new File([baseBlob], baseFilename, { type: 'image/png' }), - }, - isIntermediate: true, - }) - ); - - // Wait for the image to be uploaded - const [{ payload: baseImageDTO }] = await take( - (action): action is ReturnType => - imageUploaded.fulfilled.match(action) && - action.meta.arg.formData.file.name === baseFilename - ); - - // Update the base node with the image name and type - baseNode.image = { - image_name: baseImageDTO.image_name, - image_type: baseImageDTO.image_type, - }; - } - - // For inpaint, we also need to upload the mask layer - if (baseNode.type === 'inpaint') { - const maskFilename = `${uuidv4()}.png`; - dispatch( - imageUploaded({ - formData: { - file: new File([maskBlob], maskFilename, { type: 'image/png' }), - }, - isIntermediate: true, - }) - ); - - // Wait for the mask to be uploaded - const [{ payload: maskImageDTO }] = await take( - (action): action is ReturnType => - imageUploaded.fulfilled.match(action) && - action.meta.arg.formData.file.name === maskFilename - ); - - // Update the base node with the image name and type - baseNode.mask = { - image_name: maskImageDTO.image_name, - image_type: maskImageDTO.image_type, - }; - } - - // Create the session and wait for response - dispatch(sessionCreated({ graph })); - const [sessionCreatedAction] = await take(sessionCreated.fulfilled.match); + // Take the session created action, matching by its request id + const [sessionCreatedAction] = await take( + (action): action is ReturnType => + sessionCreated.fulfilled.match(action) && + action.meta.requestId === sessionCreatedRequestId + ); const sessionId = sessionCreatedAction.payload.id; // Associate the init image with the session, now that we have the session ID - if ( - (baseNode.type === 'img2img' || baseNode.type === 'inpaint') && - baseNode.image - ) { + if (['img2img', 'inpaint'].includes(generationMode) && canvasInitImage) { dispatch( imageUpdated({ - imageName: baseNode.image.image_name, - imageType: baseNode.image.image_type, - requestBody: { session_id: sessionId }, + image_name: canvasInitImage.image_name, + session_id: sessionId, }) ); } // Associate the mask image with the session, now that we have the session ID - if (baseNode.type === 'inpaint' && baseNode.mask) { + if (['inpaint'].includes(generationMode) && canvasMaskImage) { dispatch( imageUpdated({ - imageName: baseNode.mask.image_name, - imageType: baseNode.mask.image_type, - requestBody: { session_id: sessionId }, + image_name: canvasMaskImage.image_name, + session_id: sessionId, }) ); } + // Prep the canvas staging area if it is not yet initialized if (!state.canvas.layerState.stagingArea.boundingBox) { dispatch( stagingAreaInitialized({ diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts index 7dcbe8a41d..202dd6f487 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts @@ -1,10 +1,10 @@ import { startAppListening } from '..'; -import { buildImageToImageGraph } from 'features/nodes/util/graphBuilders/buildImageToImageGraph'; -import { sessionCreated } from 'services/thunks/session'; +import { sessionCreated } from 'services/api/thunks/session'; import { log } from 'app/logging/useLogger'; import { imageToImageGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions'; +import { buildLinearImageToImageGraph } from 'features/nodes/util/graphBuilders/buildLinearImageToImageGraph'; const moduleLog = log.child({ namespace: 'invoke' }); @@ -15,7 +15,7 @@ export const addUserInvokedImageToImageListener = () => { effect: async (action, { getState, dispatch, take }) => { const state = getState(); - const graph = buildImageToImageGraph(state); + const graph = buildLinearImageToImageGraph(state); dispatch(imageToImageGraphBuilt(graph)); moduleLog.debug({ data: graph }, 'Image to Image graph built'); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts index 6fda3db0d6..9fb0009b70 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts @@ -1,5 +1,5 @@ import { startAppListening } from '..'; -import { sessionCreated } from 'services/thunks/session'; +import { sessionCreated } from 'services/api/thunks/session'; import { buildNodesGraph } from 'features/nodes/util/graphBuilders/buildNodesGraph'; import { log } from 'app/logging/useLogger'; import { nodesGraphBuilt } from 'features/nodes/store/actions'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts index 6042d86cb7..b024e94d9c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts @@ -1,10 +1,10 @@ import { startAppListening } from '..'; -import { buildTextToImageGraph } from 'features/nodes/util/graphBuilders/buildTextToImageGraph'; -import { sessionCreated } from 'services/thunks/session'; +import { sessionCreated } from 'services/api/thunks/session'; import { log } from 'app/logging/useLogger'; import { textToImageGraphBuilt } from 'features/nodes/store/actions'; import { userInvoked } from 'app/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions'; +import { buildLinearTextToImageGraph } from 'features/nodes/util/graphBuilders/buildLinearTextToImageGraph'; const moduleLog = log.child({ namespace: 'invoke' }); @@ -15,7 +15,7 @@ export const addUserInvokedTextToImageListener = () => { effect: async (action, { getState, dispatch, take }) => { const state = getState(); - const graph = buildTextToImageGraph(state); + const graph = buildLinearTextToImageGraph(state); dispatch(textToImageGraphBuilt(graph)); diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 4e9c154f3a..e92a422d68 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -5,49 +5,52 @@ import { configureStore, } from '@reduxjs/toolkit'; -import { rememberReducer, rememberEnhancer } from 'redux-remember'; import dynamicMiddlewares from 'redux-dynamic-middlewares'; +import { rememberEnhancer, rememberReducer } from 'redux-remember'; import canvasReducer from 'features/canvas/store/canvasSlice'; +import controlNetReducer from 'features/controlNet/store/controlNetSlice'; import galleryReducer from 'features/gallery/store/gallerySlice'; -import resultsReducer from 'features/gallery/store/resultsSlice'; -import uploadsReducer from 'features/gallery/store/uploadsSlice'; +import imagesReducer from 'features/gallery/store/imagesSlice'; import lightboxReducer from 'features/lightbox/store/lightboxSlice'; import generationReducer from 'features/parameters/store/generationSlice'; import postprocessingReducer from 'features/parameters/store/postprocessingSlice'; import systemReducer from 'features/system/store/systemSlice'; // import sessionReducer from 'features/system/store/sessionSlice'; -import configReducer from 'features/system/store/configSlice'; -import uiReducer from 'features/ui/store/uiSlice'; -import hotkeysReducer from 'features/ui/store/hotkeysSlice'; -import modelsReducer from 'features/system/store/modelSlice'; import nodesReducer from 'features/nodes/store/nodesSlice'; +import boardsReducer from 'features/gallery/store/boardSlice'; +import configReducer from 'features/system/store/configSlice'; +import hotkeysReducer from 'features/ui/store/hotkeysSlice'; +import uiReducer from 'features/ui/store/uiSlice'; +import dynamicPromptsReducer from 'features/dynamicPrompts/store/slice'; import { listenerMiddleware } from './middleware/listenerMiddleware'; import { actionSanitizer } from './middleware/devtools/actionSanitizer'; -import { stateSanitizer } from './middleware/devtools/stateSanitizer'; import { actionsDenylist } from './middleware/devtools/actionsDenylist'; - +import { stateSanitizer } from './middleware/devtools/stateSanitizer'; +import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; import { unserialize } from './enhancers/reduxRemember/unserialize'; -import { LOCALSTORAGE_PREFIX } from './constants'; +import { api } from 'services/api'; const allReducers = { canvas: canvasReducer, gallery: galleryReducer, generation: generationReducer, lightbox: lightboxReducer, - models: modelsReducer, nodes: nodesReducer, postprocessing: postprocessingReducer, - results: resultsReducer, system: systemReducer, config: configReducer, ui: uiReducer, - uploads: uploadsReducer, hotkeys: hotkeysReducer, + images: imagesReducer, + controlNet: controlNetReducer, + boards: boardsReducer, // session: sessionReducer, + dynamicPrompts: dynamicPromptsReducer, + [api.reducerPath]: api.reducer, }; const rootReducer = combineReducers(allReducers); @@ -59,14 +62,14 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'gallery', 'generation', 'lightbox', - // 'models', 'nodes', 'postprocessing', 'system', 'ui', + 'controlNet', + 'dynamicPrompts', + // 'boards', // 'hotkeys', - // 'results', - // 'uploads', // 'config', ]; @@ -85,6 +88,7 @@ export const store = configureStore({ immutableCheck: false, serializableCheck: false, }) + .concat(api.middleware) .concat(dynamicMiddlewares) .prepend(listenerMiddleware.middleware), devTools: { @@ -99,3 +103,4 @@ export type AppGetState = typeof store.getState; export type RootState = ReturnType; export type AppThunkDispatch = ThunkDispatch; export type AppDispatch = typeof store.dispatch; +export const stateSelector = (state: RootState) => state; diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 68f7568779..a89ba01130 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -1,316 +1,86 @@ -/** - * Types for images, the things they are made of, and the things - * they make up. - * - * Generated images are txt2img and img2img images. They may have - * had additional postprocessing done on them when they were first - * generated. - * - * Postprocessed images are images which were not generated here - * but only postprocessed by the app. They only get postprocessing - * metadata and have a different image type, e.g. 'esrgan' or - * 'gfpgan'. - */ - -import { SelectedImage } from 'features/parameters/store/actions'; +import { + CONTROLNET_MODELS, + CONTROLNET_PROCESSORS, +} from 'features/controlNet/store/constants'; import { InvokeTabName } from 'features/ui/store/tabMap'; -import { IRect } from 'konva/lib/types'; -import { ImageResponseMetadata, ImageType } from 'services/api'; import { O } from 'ts-toolbelt'; -/** - * TODO: - * Once an image has been generated, if it is postprocessed again, - * additional postprocessing steps are added to its postprocessing - * array. - * - * TODO: Better documentation of types. - */ +// These are old types from the model management UI -export type PromptItem = { - prompt: string; - weight: number; -}; +// export type ModelStatus = 'active' | 'cached' | 'not loaded'; -// TECHDEBT: We need to retain compatibility with plain prompt strings and the structure Prompt type -export type Prompt = Array | string; - -export type SeedWeightPair = { - seed: number; - weight: number; -}; - -export type SeedWeights = Array; - -// All generated images contain these metadata. -export type CommonGeneratedImageMetadata = { - postprocessing: null | Array; - sampler: - | 'ddim' - | 'ddpm' - | 'deis' - | 'lms' - | 'pndm' - | 'heun' - | 'heun_k' - | 'euler' - | 'euler_k' - | 'euler_a' - | 'kdpm_2' - | 'kdpm_2_a' - | 'dpmpp_2s' - | 'dpmpp_2m' - | 'dpmpp_2m_k' - | 'unipc'; - prompt: Prompt; - seed: number; - variations: SeedWeights; - steps: number; - cfg_scale: number; - width: number; - height: number; - seamless: boolean; - hires_fix: boolean; - extra: null | Record; // Pending development of RFC #266 -}; - -// txt2img and img2img images have some unique attributes. -export type Txt2ImgMetadata = CommonGeneratedImageMetadata & { - type: 'txt2img'; -}; - -export type Img2ImgMetadata = CommonGeneratedImageMetadata & { - type: 'img2img'; - orig_hash: string; - strength: number; - fit: boolean; - init_image_path: string; - mask_image_path?: string; -}; - -// Superset of generated image metadata types. -export type GeneratedImageMetadata = Txt2ImgMetadata | Img2ImgMetadata; - -// All post processed images contain these metadata. -export type CommonPostProcessedImageMetadata = { - orig_path: string; - orig_hash: string; -}; - -// esrgan and gfpgan images have some unique attributes. -export type ESRGANMetadata = CommonPostProcessedImageMetadata & { - type: 'esrgan'; - scale: 2 | 4; - strength: number; - denoise_str: number; -}; - -export type FacetoolMetadata = CommonPostProcessedImageMetadata & { - type: 'gfpgan' | 'codeformer'; - strength: number; - fidelity?: number; -}; - -// Superset of all postprocessed image metadata types.. -export type PostProcessedImageMetadata = ESRGANMetadata | FacetoolMetadata; - -// Metadata includes the system config and image metadata. -// export type Metadata = SystemGenerationMetadata & { -// image: GeneratedImageMetadata | PostProcessedImageMetadata; +// export type Model = { +// status: ModelStatus; +// description: string; +// weights: string; +// config?: string; +// vae?: string; +// width?: number; +// height?: number; +// default?: boolean; +// format?: string; // }; -/** - * ResultImage - */ -// export ty`pe Image = { +// export type DiffusersModel = { +// status: ModelStatus; +// description: string; +// repo_id?: string; +// path?: string; +// vae?: { +// repo_id?: string; +// path?: string; +// }; +// format?: string; +// default?: boolean; +// }; + +// export type ModelList = Record; + +// export type FoundModel = { // name: string; -// type: ImageType; -// url: string; -// thumbnail: string; -// metadata: ImageResponseMetadata; +// location: string; // }; -// export const isInvokeAIImage = (obj: Image | SelectedImage): obj is Image => { -// if ('url' in obj && 'thumbnail' in obj) { -// return true; -// } - -// return false; +// export type InvokeModelConfigProps = { +// name: string | undefined; +// description: string | undefined; +// config: string | undefined; +// weights: string | undefined; +// vae: string | undefined; +// width: number | undefined; +// height: number | undefined; +// default: boolean | undefined; +// format: string | undefined; // }; -/** - * Types related to the system status. - */ - -// // This represents the processing status of the backend. -// export type SystemStatus = { -// isProcessing: boolean; -// currentStep: number; -// totalSteps: number; -// currentIteration: number; -// totalIterations: number; -// currentStatus: string; -// currentStatusHasSteps: boolean; -// hasError: boolean; +// export type InvokeDiffusersModelConfigProps = { +// name: string | undefined; +// description: string | undefined; +// repo_id: string | undefined; +// path: string | undefined; +// default: boolean | undefined; +// format: string | undefined; +// vae: { +// repo_id: string | undefined; +// path: string | undefined; +// }; // }; -// export type SystemGenerationMetadata = { -// model: string; -// model_weights?: string; -// model_id?: string; -// model_hash: string; -// app_id: string; -// app_version: string; +// export type InvokeModelConversionProps = { +// model_name: string; +// save_location: string; +// custom_location: string | null; // }; -// export type SystemConfig = SystemGenerationMetadata & { -// model_list: ModelList; -// infill_methods: string[]; +// export type InvokeModelMergingProps = { +// models_to_merge: string[]; +// alpha: number; +// interp: 'weighted_sum' | 'sigmoid' | 'inv_sigmoid' | 'add_difference'; +// force: boolean; +// merged_model_name: string; +// model_merge_save_path: string | null; // }; -export type ModelStatus = 'active' | 'cached' | 'not loaded'; - -export type Model = { - status: ModelStatus; - description: string; - weights: string; - config?: string; - vae?: string; - width?: number; - height?: number; - default?: boolean; - format?: string; -}; - -export type DiffusersModel = { - status: ModelStatus; - description: string; - repo_id?: string; - path?: string; - vae?: { - repo_id?: string; - path?: string; - }; - format?: string; - default?: boolean; -}; - -export type ModelList = Record; - -export type FoundModel = { - name: string; - location: string; -}; - -export type InvokeModelConfigProps = { - name: string | undefined; - description: string | undefined; - config: string | undefined; - weights: string | undefined; - vae: string | undefined; - width: number | undefined; - height: number | undefined; - default: boolean | undefined; - format: string | undefined; -}; - -export type InvokeDiffusersModelConfigProps = { - name: string | undefined; - description: string | undefined; - repo_id: string | undefined; - path: string | undefined; - default: boolean | undefined; - format: string | undefined; - vae: { - repo_id: string | undefined; - path: string | undefined; - }; -}; - -export type InvokeModelConversionProps = { - model_name: string; - save_location: string; - custom_location: string | null; -}; - -export type InvokeModelMergingProps = { - models_to_merge: string[]; - alpha: number; - interp: 'weighted_sum' | 'sigmoid' | 'inv_sigmoid' | 'add_difference'; - force: boolean; - merged_model_name: string; - model_merge_save_path: string | null; -}; - -/** - * These types type data received from the server via socketio. - */ - -export type ModelChangeResponse = { - model_name: string; - model_list: ModelList; -}; - -export type ModelConvertedResponse = { - converted_model_name: string; - model_list: ModelList; -}; - -export type ModelsMergedResponse = { - merged_models: string[]; - merged_model_name: string; - model_list: ModelList; -}; - -export type ModelAddedResponse = { - new_model_name: string; - model_list: ModelList; - update: boolean; -}; - -export type ModelDeletedResponse = { - deleted_model_name: string; - model_list: ModelList; -}; - -export type FoundModelResponse = { - search_folder: string; - found_models: FoundModel[]; -}; - -// export type SystemStatusResponse = SystemStatus; - -// export type SystemConfigResponse = SystemConfig; - -export type ImageResultResponse = Omit & { - boundingBox?: IRect; - generationMode: InvokeTabName; -}; - -export type ImageUploadResponse = { - // image: Omit; - url: string; - mtime: number; - width: number; - height: number; - thumbnail: string; - // bbox: [number, number, number, number]; -}; - -export type ErrorResponse = { - message: string; - additionalData?: string; -}; - -export type ImageUrlResponse = { - url: string; -}; - -export type UploadOutpaintingMergeImagePayload = { - dataURL: string; - name: string; -}; - /** * A disable-able application feature */ @@ -322,12 +92,14 @@ export type AppFeature = | 'githubLink' | 'discordLink' | 'bugLink' - | 'localization'; + | 'localization' + | 'consoleLogging'; /** * A disable-able Stable Diffusion feature */ export type SDFeature = + | 'controlNet' | 'noise' | 'variation' | 'symmetry' @@ -340,17 +112,17 @@ export type SDFeature = */ export type AppConfig = { /** - * Whether or not URLs should be transformed to use a different host - */ - shouldTransformUrls: boolean; - /** - * Whether or not we need to re-fetch images + * Whether or not we should update image urls when image loading errors */ + shouldUpdateImagesOnConnect: boolean; disabledTabs: InvokeTabName[]; disabledFeatures: AppFeature[]; disabledSDFeatures: SDFeature[]; canRestoreDeletedImagesFromBin: boolean; sd: { + defaultModel?: string; + disabledControlNetModels: (keyof typeof CONTROLNET_MODELS)[]; + disabledControlNetProcessors: (keyof typeof CONTROLNET_PROCESSORS)[]; iterations: { initial: number; min: number; @@ -399,6 +171,14 @@ export type AppConfig = { fineStep: number; coarseStep: number; }; + dynamicPrompts: { + maxPrompts: { + initial: number; + min: number; + sliderMax: number; + inputMax: number; + }; + }; }; }; diff --git a/invokeai/frontend/web/src/common/components/IAICheckbox.tsx b/invokeai/frontend/web/src/common/components/IAICheckbox.tsx deleted file mode 100644 index eb423b2b27..0000000000 --- a/invokeai/frontend/web/src/common/components/IAICheckbox.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Checkbox, CheckboxProps } from '@chakra-ui/react'; -import { memo, ReactNode } from 'react'; - -type IAICheckboxProps = CheckboxProps & { - label: string | ReactNode; -}; - -const IAICheckbox = (props: IAICheckboxProps) => { - const { label, ...rest } = props; - return ( - - {label} - - ); -}; - -export default memo(IAICheckbox); diff --git a/invokeai/frontend/web/src/common/components/IAICollapse.tsx b/invokeai/frontend/web/src/common/components/IAICollapse.tsx index 161caca24d..5db26f3841 100644 --- a/invokeai/frontend/web/src/common/components/IAICollapse.tsx +++ b/invokeai/frontend/web/src/common/components/IAICollapse.tsx @@ -1,6 +1,14 @@ import { ChevronUpIcon } from '@chakra-ui/icons'; -import { Box, Collapse, Flex, Spacer, Switch } from '@chakra-ui/react'; +import { + Box, + Collapse, + Flex, + Spacer, + Switch, + useColorMode, +} from '@chakra-ui/react'; import { PropsWithChildren, memo } from 'react'; +import { mode } from 'theme/util/mode'; export type IAIToggleCollapseProps = PropsWithChildren & { label: string; @@ -11,6 +19,7 @@ export type IAIToggleCollapseProps = PropsWithChildren & { const IAICollapse = (props: IAIToggleCollapseProps) => { const { label, isOpen, onToggle, children, withSwitch = false } = props; + const { colorMode } = useColorMode(); return ( { px: 4, borderTopRadius: 'base', borderBottomRadius: isOpen ? 0 : 'base', - bg: isOpen ? 'base.750' : 'base.800', - color: 'base.100', + bg: isOpen + ? mode('base.200', 'base.750')(colorMode) + : mode('base.150', 'base.800')(colorMode), + color: mode('base.900', 'base.100')(colorMode), _hover: { - bg: isOpen ? 'base.700' : 'base.750', + bg: isOpen + ? mode('base.250', 'base.700')(colorMode) + : mode('base.200', 'base.750')(colorMode), }, fontSize: 'sm', fontWeight: 600, @@ -49,8 +62,14 @@ const IAICollapse = (props: IAIToggleCollapseProps) => { /> )} - - + + {children} diff --git a/invokeai/frontend/web/src/common/components/IAICustomSelect.tsx b/invokeai/frontend/web/src/common/components/IAICustomSelect.tsx deleted file mode 100644 index d9610346ec..0000000000 --- a/invokeai/frontend/web/src/common/components/IAICustomSelect.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { CheckIcon } from '@chakra-ui/icons'; -import { - Box, - Flex, - FlexProps, - FormControl, - FormControlProps, - FormLabel, - Grid, - GridItem, - List, - ListItem, - Select, - Text, - Tooltip, - TooltipProps, -} from '@chakra-ui/react'; -import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react-dom'; -import { useSelect } from 'downshift'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; - -import { memo } from 'react'; - -type IAICustomSelectProps = { - label?: string; - items: string[]; - selectedItem: string; - setSelectedItem: (v: string | null | undefined) => void; - withCheckIcon?: boolean; - formControlProps?: FormControlProps; - buttonProps?: FlexProps; - tooltip?: string; - tooltipProps?: Omit; -}; - -const IAICustomSelect = (props: IAICustomSelectProps) => { - const { - label, - items, - setSelectedItem, - selectedItem, - withCheckIcon, - formControlProps, - tooltip, - buttonProps, - tooltipProps, - } = props; - - const { - isOpen, - getToggleButtonProps, - getLabelProps, - getMenuProps, - highlightedIndex, - getItemProps, - } = useSelect({ - items, - selectedItem, - onSelectedItemChange: ({ selectedItem: newSelectedItem }) => - setSelectedItem(newSelectedItem), - }); - - const { refs, floatingStyles } = useFloating({ - whileElementsMounted: autoUpdate, - middleware: [offset(4), shift({ crossAxis: true, padding: 8 })], - }); - - return ( - - {label && ( - { - refs.floating.current && refs.floating.current.focus(); - }} - > - {label} - - )} - - - - - {isOpen && ( - - - {items.map((item, index) => ( - - {withCheckIcon ? ( - - - {selectedItem === item && } - - - - {item} - - - - ) : ( - - {item} - - )} - - ))} - - - )} - - - ); -}; - -export default memo(IAICustomSelect); diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx new file mode 100644 index 0000000000..d0652dc8b9 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -0,0 +1,209 @@ +import { + Box, + ChakraProps, + Flex, + Icon, + IconButtonProps, + Image, + useColorMode, +} from '@chakra-ui/react'; +import { useDraggable, useDroppable } from '@dnd-kit/core'; +import { useCombinedRefs } from '@dnd-kit/utilities'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; +import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; +import { AnimatePresence } from 'framer-motion'; +import { ReactElement, SyntheticEvent } from 'react'; +import { memo, useRef } from 'react'; +import { FaImage, FaUndo, FaUpload } from 'react-icons/fa'; +import { ImageDTO } from 'services/api/types'; +import { v4 as uuidv4 } from 'uuid'; +import IAIDropOverlay from './IAIDropOverlay'; +import { PostUploadAction } from 'services/api/thunks/image'; +import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; +import { mode } from 'theme/util/mode'; + +type IAIDndImageProps = { + image: ImageDTO | null | undefined; + onDrop: (droppedImage: ImageDTO) => void; + onReset?: () => void; + onError?: (event: SyntheticEvent) => void; + onLoad?: (event: SyntheticEvent) => void; + resetIconSize?: IconButtonProps['size']; + withResetIcon?: boolean; + withMetadataOverlay?: boolean; + isDragDisabled?: boolean; + isDropDisabled?: boolean; + isUploadDisabled?: boolean; + fallback?: ReactElement; + payloadImage?: ImageDTO | null | undefined; + minSize?: number; + postUploadAction?: PostUploadAction; + imageSx?: ChakraProps['sx']; + fitContainer?: boolean; +}; + +const IAIDndImage = (props: IAIDndImageProps) => { + const { + image, + onDrop, + onReset, + onError, + resetIconSize = 'md', + withResetIcon = false, + withMetadataOverlay = false, + isDropDisabled = false, + isDragDisabled = false, + isUploadDisabled = false, + fallback = , + payloadImage, + minSize = 24, + postUploadAction, + imageSx, + fitContainer = false, + } = props; + + const dndId = useRef(uuidv4()); + const { colorMode } = useColorMode(); + + const { + isOver, + setNodeRef: setDroppableRef, + active: isDropActive, + } = useDroppable({ + id: dndId.current, + disabled: isDropDisabled, + data: { + handleDrop: onDrop, + }, + }); + + const { + attributes, + listeners, + setNodeRef: setDraggableRef, + isDragging, + } = useDraggable({ + id: dndId.current, + data: { + image: payloadImage ? payloadImage : image, + }, + disabled: isDragDisabled || !image, + }); + + const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({ + postUploadAction, + isDisabled: isUploadDisabled, + }); + + const setNodeRef = useCombinedRefs(setDroppableRef, setDraggableRef); + + const uploadButtonStyles = isUploadDisabled + ? {} + : { + cursor: 'pointer', + bg: mode('base.200', 'base.800')(colorMode), + _hover: { + bg: mode('base.300', 'base.650')(colorMode), + color: mode('base.500', 'base.300')(colorMode), + }, + }; + + return ( + + {image && ( + + + {withMetadataOverlay && } + {onReset && withResetIcon && ( + + } + onClick={onReset} + /> + + )} + + {isDropActive && } + + + )} + {!image && ( + <> + + + + + + {isDropActive && } + + + )} + + ); +}; + +export default memo(IAIDndImage); diff --git a/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx new file mode 100644 index 0000000000..8ae54c30ab --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx @@ -0,0 +1,97 @@ +import { Flex, Text, useColorMode } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { memo, useRef } from 'react'; +import { mode } from 'theme/util/mode'; +import { v4 as uuidv4 } from 'uuid'; + +type Props = { + isOver: boolean; + label?: string; +}; + +export const IAIDropOverlay = (props: Props) => { + const { isOver, label = 'Drop' } = props; + const motionId = useRef(uuidv4()); + const { colorMode } = useColorMode(); + return ( + + + + + + + {label} + + + + + ); +}; + +export default memo(IAIDropOverlay); diff --git a/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx b/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx new file mode 100644 index 0000000000..97ff24689c --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx @@ -0,0 +1,25 @@ +import { + Checkbox, + CheckboxProps, + FormControl, + FormControlProps, + FormLabel, +} from '@chakra-ui/react'; +import { memo, ReactNode } from 'react'; + +type IAIFullCheckboxProps = CheckboxProps & { + label: string | ReactNode; + formControlProps?: FormControlProps; +}; + +const IAIFullCheckbox = (props: IAIFullCheckboxProps) => { + const { label, formControlProps, ...rest } = props; + return ( + + {label} + + + ); +}; + +export default memo(IAIFullCheckbox); diff --git a/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx new file mode 100644 index 0000000000..4cff351aee --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx @@ -0,0 +1,73 @@ +import { + As, + Flex, + FlexProps, + Icon, + IconProps, + Spinner, + SpinnerProps, + useColorMode, +} from '@chakra-ui/react'; +import { FaImage } from 'react-icons/fa'; +import { mode } from 'theme/util/mode'; + +type Props = FlexProps & { + spinnerProps?: SpinnerProps; +}; + +export const IAIImageLoadingFallback = (props: Props) => { + const { spinnerProps, ...rest } = props; + const { sx, ...restFlexProps } = rest; + const { colorMode } = useColorMode(); + return ( + + + + ); +}; + +type IAINoImageFallbackProps = { + flexProps?: FlexProps; + iconProps?: IconProps; + as?: As; +}; + +export const IAINoImageFallback = (props: IAINoImageFallbackProps) => { + const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} }; + const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} }; + const { colorMode } = useColorMode(); + + return ( + + + + ); +}; diff --git a/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx new file mode 100644 index 0000000000..39ec6fd245 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx @@ -0,0 +1,124 @@ +import { Tooltip, useColorMode, useToken } from '@chakra-ui/react'; +import { MultiSelect, MultiSelectProps } from '@mantine/core'; +import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens'; +import { memo } from 'react'; +import { mode } from 'theme/util/mode'; + +type IAIMultiSelectProps = MultiSelectProps & { + tooltip?: string; +}; + +const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { + const { searchable = true, tooltip, ...rest } = props; + const { + base50, + base100, + base200, + base300, + base400, + base500, + base600, + base700, + base800, + base900, + accent200, + accent300, + accent400, + accent500, + accent600, + } = useChakraThemeTokens(); + const [boxShadow] = useToken('shadows', ['dark-lg']); + const { colorMode } = useColorMode(); + + return ( + + ({ + label: { + color: mode(base700, base300)(colorMode), + fontWeight: 'normal', + }, + searchInput: { + ':placeholder': { + color: mode(base300, base700)(colorMode), + }, + }, + input: { + backgroundColor: mode(base50, base900)(colorMode), + borderWidth: '2px', + borderColor: mode(base200, base800)(colorMode), + color: mode(base900, base100)(colorMode), + paddingRight: 24, + fontWeight: 600, + '&:hover': { borderColor: mode(base300, base600)(colorMode) }, + '&:focus': { + borderColor: mode(accent300, accent600)(colorMode), + }, + '&:is(:focus, :hover)': { + borderColor: mode(base400, base500)(colorMode), + }, + '&:focus-within': { + borderColor: mode(accent200, accent600)(colorMode), + }, + '&:disabled': { + backgroundColor: mode(base300, base700)(colorMode), + color: mode(base600, base400)(colorMode), + }, + }, + value: { + backgroundColor: mode(base200, base800)(colorMode), + color: mode(base900, base100)(colorMode), + button: { + color: mode(base900, base100)(colorMode), + }, + '&:hover': { + backgroundColor: mode(base300, base700)(colorMode), + cursor: 'pointer', + }, + }, + dropdown: { + backgroundColor: mode(base200, base800)(colorMode), + borderColor: mode(base200, base800)(colorMode), + boxShadow, + }, + item: { + backgroundColor: mode(base200, base800)(colorMode), + color: mode(base800, base200)(colorMode), + padding: 6, + '&[data-hovered]': { + color: mode(base900, base100)(colorMode), + backgroundColor: mode(base300, base700)(colorMode), + }, + '&[data-active]': { + backgroundColor: mode(base300, base700)(colorMode), + '&:hover': { + color: mode(base900, base100)(colorMode), + backgroundColor: mode(base300, base700)(colorMode), + }, + }, + '&[data-selected]': { + backgroundColor: mode(accent400, accent600)(colorMode), + color: mode(base50, base100)(colorMode), + fontWeight: 600, + '&:hover': { + backgroundColor: mode(accent500, accent500)(colorMode), + color: mode('white', base50)(colorMode), + }, + }, + }, + rightSection: { + width: 24, + padding: 20, + button: { + color: mode(base900, base100)(colorMode), + }, + }, + })} + {...rest} + /> + + ); +}; + +export default memo(IAIMantineMultiSelect); diff --git a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx new file mode 100644 index 0000000000..9b023fd2d7 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx @@ -0,0 +1,126 @@ +import { Tooltip, useColorMode, useToken } from '@chakra-ui/react'; +import { Select, SelectProps } from '@mantine/core'; +import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens'; +import { memo } from 'react'; +import { mode } from 'theme/util/mode'; + +export type IAISelectDataType = { + value: string; + label: string; + tooltip?: string; +}; + +type IAISelectProps = SelectProps & { + tooltip?: string; +}; + +const IAIMantineSelect = (props: IAISelectProps) => { + const { searchable = true, tooltip, ...rest } = props; + const { + base50, + base100, + base200, + base300, + base400, + base500, + base600, + base700, + base800, + base900, + accent200, + accent300, + accent400, + accent500, + accent600, + } = useChakraThemeTokens(); + + const { colorMode } = useColorMode(); + + const [boxShadow] = useToken('shadows', ['dark-lg']); + + return ( + + // hidden, handles native upload functionality + */ +export const useImageUploadButton = ({ + postUploadAction, + isDisabled, +}: UseImageUploadButtonArgs) => { + const dispatch = useAppDispatch(); + const onDropAccepted = useCallback( + (files: File[]) => { + const file = files[0]; + if (!file) { + return; + } + + dispatch( + imageUploaded({ + file, + image_category: 'user', + is_intermediate: false, + postUploadAction, + }) + ); + }, + [dispatch, postUploadAction] + ); + + const { + getRootProps: getUploadButtonProps, + getInputProps: getUploadInputProps, + open: openUploader, + } = useDropzone({ + accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] }, + onDropAccepted, + disabled: isDisabled, + noDrag: true, + multiple: false, + }); + + return { getUploadButtonProps, getUploadInputProps, openUploader }; +}; diff --git a/invokeai/frontend/web/src/app/selectors/readinessSelector.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts similarity index 78% rename from invokeai/frontend/web/src/app/selectors/readinessSelector.ts rename to invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts index 2b77fe9f47..d410c3917c 100644 --- a/invokeai/frontend/web/src/app/selectors/readinessSelector.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts @@ -1,33 +1,22 @@ import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { validateSeedWeights } from 'common/util/seedWeightPairs'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { systemSelector } from 'features/system/store/systemSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -import { isEqual } from 'lodash-es'; -export const readinessSelector = createSelector( +const readinessSelector = createSelector( [generationSelector, systemSelector, activeTabNameSelector], (generation, system, activeTabName) => { - const { - positivePrompt: prompt, - shouldGenerateVariations, - seedWeights, - initialImage, - seed, - } = generation; + const { shouldGenerateVariations, seedWeights, initialImage, seed } = + generation; const { isProcessing, isConnected } = system; let isReady = true; const reasonsWhyNotReady: string[] = []; - // Cannot generate without a prompt - if (!prompt || Boolean(prompt.match(/^[\s\r\n]+$/))) { - isReady = false; - reasonsWhyNotReady.push('Missing prompt'); - } - if (activeTabName === 'img2img' && !initialImage) { isReady = false; reasonsWhyNotReady.push('No initial image selected'); @@ -60,3 +49,8 @@ export const readinessSelector = createSelector( }, defaultSelectorOptions ); + +export const useIsReadyToInvoke = () => { + const { isReady } = useAppSelector(readinessSelector); + return isReady; +}; diff --git a/invokeai/frontend/web/src/common/util/_parseMetadataZod.ts b/invokeai/frontend/web/src/common/util/_parseMetadataZod.ts deleted file mode 100644 index 584399233f..0000000000 --- a/invokeai/frontend/web/src/common/util/_parseMetadataZod.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * PARTIAL ZOD IMPLEMENTATION - * - * doesn't work well bc like most validators, zod is not built to skip invalid values. - * it mostly works but just seems clearer and simpler to manually parse for now. - * - * in the future it would be really nice if we could use zod for some things: - * - zodios (axios + zod): https://github.com/ecyrbe/zodios - * - openapi to zodios: https://github.com/astahmer/openapi-zod-client - */ - -// import { z } from 'zod'; - -// const zMetadataStringField = z.string(); -// export type MetadataStringField = z.infer; - -// const zMetadataIntegerField = z.number().int(); -// export type MetadataIntegerField = z.infer; - -// const zMetadataFloatField = z.number(); -// export type MetadataFloatField = z.infer; - -// const zMetadataBooleanField = z.boolean(); -// export type MetadataBooleanField = z.infer; - -// const zMetadataImageField = z.object({ -// image_type: z.union([ -// z.literal('results'), -// z.literal('uploads'), -// z.literal('intermediates'), -// ]), -// image_name: z.string().min(1), -// }); -// export type MetadataImageField = z.infer; - -// const zMetadataLatentsField = z.object({ -// latents_name: z.string().min(1), -// }); -// export type MetadataLatentsField = z.infer; - -// /** -// * zod Schema for any node field. Use a `transform()` to manually parse, skipping invalid values. -// */ -// const zAnyMetadataField = z.any().transform((val, ctx) => { -// // Grab the field name from the path -// const fieldName = String(ctx.path[ctx.path.length - 1]); - -// // `id` and `type` must be strings if they exist -// if (['id', 'type'].includes(fieldName)) { -// const reservedStringPropertyResult = zMetadataStringField.safeParse(val); -// if (reservedStringPropertyResult.success) { -// return reservedStringPropertyResult.data; -// } - -// return; -// } - -// // Parse the rest of the fields, only returning the data if the parsing is successful - -// const stringFieldResult = zMetadataStringField.safeParse(val); -// if (stringFieldResult.success) { -// return stringFieldResult.data; -// } - -// const integerFieldResult = zMetadataIntegerField.safeParse(val); -// if (integerFieldResult.success) { -// return integerFieldResult.data; -// } - -// const floatFieldResult = zMetadataFloatField.safeParse(val); -// if (floatFieldResult.success) { -// return floatFieldResult.data; -// } - -// const booleanFieldResult = zMetadataBooleanField.safeParse(val); -// if (booleanFieldResult.success) { -// return booleanFieldResult.data; -// } - -// const imageFieldResult = zMetadataImageField.safeParse(val); -// if (imageFieldResult.success) { -// return imageFieldResult.data; -// } - -// const latentsFieldResult = zMetadataImageField.safeParse(val); -// if (latentsFieldResult.success) { -// return latentsFieldResult.data; -// } -// }); - -// /** -// * The node metadata schema. -// */ -// const zNodeMetadata = z.object({ -// session_id: z.string().min(1).optional(), -// node: z.record(z.string().min(1), zAnyMetadataField).optional(), -// }); - -// export type NodeMetadata = z.infer; - -// const zMetadata = z.object({ -// invokeai: zNodeMetadata.optional(), -// 'sd-metadata': z.record(z.string().min(1), z.any()).optional(), -// }); -// export type Metadata = z.infer; - -// export const parseMetadata = ( -// metadata: Record -// ): Metadata | undefined => { -// const result = zMetadata.safeParse(metadata); -// if (!result.success) { -// console.log(result.error.issues); -// return; -// } - -// return result.data; -// }; - -export default {}; diff --git a/invokeai/frontend/web/src/common/util/getUrl.ts b/invokeai/frontend/web/src/common/util/getUrl.ts deleted file mode 100644 index 72607057e4..0000000000 --- a/invokeai/frontend/web/src/common/util/getUrl.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; -import { useCallback } from 'react'; -import { OpenAPI } from 'services/api'; - -export const getUrlAlt = (url: string, shouldTransformUrls: boolean) => { - if (OpenAPI.BASE && shouldTransformUrls) { - return [OpenAPI.BASE, url].join('/'); - } - - return url; -}; - -export const useGetUrl = () => { - const shouldTransformUrls = useAppSelector( - (state: RootState) => state.config.shouldTransformUrls - ); - - const getUrl = useCallback( - (url?: string) => { - if (OpenAPI.BASE && shouldTransformUrls) { - return [OpenAPI.BASE, url].join('/'); - } - - return url; - }, - [shouldTransformUrls] - ); - - return { - shouldTransformUrls, - getUrl, - }; -}; diff --git a/invokeai/frontend/web/src/common/util/parseMetadata.ts b/invokeai/frontend/web/src/common/util/parseMetadata.ts deleted file mode 100644 index bb3999d6d0..0000000000 --- a/invokeai/frontend/web/src/common/util/parseMetadata.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { forEach, size } from 'lodash-es'; -import { - ImageField, - LatentsField, - ConditioningField, - ControlField, -} from 'services/api'; - -const OBJECT_TYPESTRING = '[object Object]'; -const STRING_TYPESTRING = '[object String]'; -const NUMBER_TYPESTRING = '[object Number]'; -const BOOLEAN_TYPESTRING = '[object Boolean]'; -const ARRAY_TYPESTRING = '[object Array]'; - -const isObject = (obj: unknown): obj is Record => - Object.prototype.toString.call(obj) === OBJECT_TYPESTRING; - -const isString = (obj: unknown): obj is string => - Object.prototype.toString.call(obj) === STRING_TYPESTRING; - -const isNumber = (obj: unknown): obj is number => - Object.prototype.toString.call(obj) === NUMBER_TYPESTRING; - -const isBoolean = (obj: unknown): obj is boolean => - Object.prototype.toString.call(obj) === BOOLEAN_TYPESTRING; - -const isArray = (obj: unknown): obj is Array => - Object.prototype.toString.call(obj) === ARRAY_TYPESTRING; - -const parseImageField = (imageField: unknown): ImageField | undefined => { - // Must be an object - if (!isObject(imageField)) { - return; - } - - // An ImageField must have both `image_name` and `image_type` - if (!('image_name' in imageField && 'image_type' in imageField)) { - return; - } - - // An ImageField's `image_type` must be one of the allowed values - if ( - !['results', 'uploads', 'intermediates'].includes(imageField.image_type) - ) { - return; - } - - // An ImageField's `image_name` must be a string - if (typeof imageField.image_name !== 'string') { - return; - } - - // Build a valid ImageField - return { - image_type: imageField.image_type, - image_name: imageField.image_name, - }; -}; - -const parseLatentsField = (latentsField: unknown): LatentsField | undefined => { - // Must be an object - if (!isObject(latentsField)) { - return; - } - - // A LatentsField must have a `latents_name` - if (!('latents_name' in latentsField)) { - return; - } - - // A LatentsField's `latents_name` must be a string - if (typeof latentsField.latents_name !== 'string') { - return; - } - - // Build a valid LatentsField - return { - latents_name: latentsField.latents_name, - }; -}; - -const parseConditioningField = ( - conditioningField: unknown -): ConditioningField | undefined => { - // Must be an object - if (!isObject(conditioningField)) { - return; - } - - // A ConditioningField must have a `conditioning_name` - if (!('conditioning_name' in conditioningField)) { - return; - } - - // A ConditioningField's `conditioning_name` must be a string - if (typeof conditioningField.conditioning_name !== 'string') { - return; - } - - // Build a valid ConditioningField - return { - conditioning_name: conditioningField.conditioning_name, - }; -}; - -const parseControlField = (controlField: unknown): ControlField | undefined => { - // Must be an object - if (!isObject(controlField)) { - return; - } - - // A ControlField must have a `control` - if (!('control' in controlField)) { - return; - } - // console.log(typeof controlField.control); - - // Build a valid ControlField - return { - control: controlField.control, - }; -}; - -type NodeMetadata = { - [key: string]: - | string - | number - | boolean - | ImageField - | LatentsField - | ConditioningField - | ControlField; -}; - -type InvokeAIMetadata = { - session_id?: string; - node?: NodeMetadata; -}; - -export const parseNodeMetadata = ( - nodeMetadata: Record -): NodeMetadata | undefined => { - if (!isObject(nodeMetadata)) { - return; - } - - const parsed: NodeMetadata = {}; - - forEach(nodeMetadata, (nodeItem, nodeKey) => { - // `id` and `type` must be strings if they are present - if (['id', 'type'].includes(nodeKey)) { - if (isString(nodeItem)) { - parsed[nodeKey] = nodeItem; - } - return; - } - - // the only valid object types are ImageField, LatentsField, ConditioningField, ControlField - if (isObject(nodeItem)) { - if ('image_name' in nodeItem || 'image_type' in nodeItem) { - const imageField = parseImageField(nodeItem); - if (imageField) { - parsed[nodeKey] = imageField; - } - return; - } - - if ('latents_name' in nodeItem) { - const latentsField = parseLatentsField(nodeItem); - if (latentsField) { - parsed[nodeKey] = latentsField; - } - return; - } - - if ('conditioning_name' in nodeItem) { - const conditioningField = parseConditioningField(nodeItem); - if (conditioningField) { - parsed[nodeKey] = conditioningField; - } - return; - } - - if ('control' in nodeItem) { - const controlField = parseControlField(nodeItem); - if (controlField) { - parsed[nodeKey] = controlField; - } - return; - } - } - - // otherwise we accept any string, number or boolean - if (isString(nodeItem) || isNumber(nodeItem) || isBoolean(nodeItem)) { - parsed[nodeKey] = nodeItem; - return; - } - }); - - if (size(parsed) === 0) { - return; - } - - return parsed; -}; - -export const parseInvokeAIMetadata = ( - metadata: Record | undefined -): InvokeAIMetadata | undefined => { - if (metadata === undefined) { - return; - } - - if (!isObject(metadata)) { - return; - } - - const parsed: InvokeAIMetadata = {}; - - forEach(metadata, (item, key) => { - if (key === 'session_id' && isString(item)) { - parsed['session_id'] = item; - } - - if (key === 'node' && isObject(item)) { - const nodeMetadata = parseNodeMetadata(item); - - if (nodeMetadata) { - parsed['node'] = nodeMetadata; - } - } - }); - - if (size(parsed) === 0) { - return; - } - - return parsed; -}; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx index ad47b77041..1b97acba71 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx @@ -1,8 +1,7 @@ // Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/ -import { useToken } from '@chakra-ui/react'; +import { useColorMode, useToken } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { isEqual, range } from 'lodash-es'; @@ -24,14 +23,14 @@ const selector = createSelector( ); const IAICanvasGrid = () => { - const currentTheme = useAppSelector( - (state: RootState) => state.ui.currentTheme - ); const { stageScale, stageCoordinates, stageDimensions } = useAppSelector(selector); + const { colorMode } = useColorMode(); const [gridLines, setGridLines] = useState([]); - - const [gridLineColor] = useToken('colors', ['gridLineColor']); + const [darkGridLineColor, lightGridLineColor] = useToken('colors', [ + 'base.800', + 'base.200', + ]); const unscale = useCallback( (value: number) => { @@ -89,7 +88,7 @@ const IAICanvasGrid = () => { x={fullRect.x1 + i * 64} y={fullRect.y1} points={[0, 0, 0, ySize]} - stroke={gridLineColor} + stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor} strokeWidth={1} /> )); @@ -99,7 +98,7 @@ const IAICanvasGrid = () => { x={fullRect.x1} y={fullRect.y1 + i * 64} points={[0, 0, xSize, 0]} - stroke={gridLineColor} + stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor} strokeWidth={1} /> )); @@ -109,9 +108,10 @@ const IAICanvasGrid = () => { stageScale, stageCoordinates, stageDimensions, - currentTheme, unscale, - gridLineColor, + colorMode, + darkGridLineColor, + lightGridLineColor, ]); return {gridLines}; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx index b8757eff0c..eb41857e46 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasImage.tsx @@ -1,14 +1,27 @@ -import { Image } from 'react-konva'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; +import { Image, Rect } from 'react-konva'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import useImage from 'use-image'; +import { CanvasImage } from '../store/canvasTypes'; +import { $authToken } from 'services/api/client'; type IAICanvasImageProps = { - url: string; - x: number; - y: number; + canvasImage: CanvasImage; }; const IAICanvasImage = (props: IAICanvasImageProps) => { - const { url, x, y } = props; - const [image] = useImage(url, 'anonymous'); + const { width, height, x, y, imageName } = props.canvasImage; + const { currentData: imageDTO, isError } = useGetImageDTOQuery( + imageName ?? skipToken + ); + const [image] = useImage( + imageDTO?.image_url ?? '', + $authToken.get() ? 'use-credentials' : 'anonymous' + ); + + if (isError) { + return ; + } + return ; }; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx index 745825a975..ea5e9a6486 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx @@ -1,18 +1,24 @@ import { createSelector } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; -import { useGetUrl } from 'common/util/getUrl'; -import { GalleryState } from 'features/gallery/store/gallerySlice'; +import { systemSelector } from 'features/system/store/systemSelectors'; import { ImageConfig } from 'konva/lib/shapes/Image'; import { isEqual } from 'lodash-es'; import { useEffect, useState } from 'react'; import { Image as KonvaImage } from 'react-konva'; +import { canvasSelector } from '../store/canvasSelectors'; const selector = createSelector( - [(state: RootState) => state.gallery], - (gallery: GalleryState) => { - return gallery.intermediateImage ? gallery.intermediateImage : null; + [systemSelector, canvasSelector], + (system, canvas) => { + const { progressImage, sessionId } = system; + const { sessionId: canvasSessionId, boundingBox } = + canvas.layerState.stagingArea; + + return { + boundingBox, + progressImage: sessionId === canvasSessionId ? progressImage : undefined, + }; }, { memoizeOptions: { @@ -25,33 +31,34 @@ type Props = Omit; const IAICanvasIntermediateImage = (props: Props) => { const { ...rest } = props; - const intermediateImage = useAppSelector(selector); - const { getUrl } = useGetUrl(); + const { progressImage, boundingBox } = useAppSelector(selector); const [loadedImageElement, setLoadedImageElement] = useState(null); useEffect(() => { - if (!intermediateImage) return; + if (!progressImage) { + return; + } + const tempImage = new Image(); tempImage.onload = () => { setLoadedImageElement(tempImage); }; - tempImage.src = getUrl(intermediateImage.url); - }, [intermediateImage, getUrl]); - if (!intermediateImage?.boundingBox) return null; + tempImage.src = progressImage.dataURL; + }, [progressImage]); - const { - boundingBox: { x, y, width, height }, - } = intermediateImage; + if (!(progressImage && boundingBox)) { + return null; + } return loadedImageElement ? ( { const { objects } = useAppSelector(selector); - const { getUrl } = useGetUrl(); if (!objects) return null; @@ -41,14 +39,7 @@ const IAICanvasObjectRenderer = () => { {objects.map((obj, i) => { if (isCanvasBaseImage(obj)) { - return ( - - ); + return ; } else if (isCanvasBaseLine(obj)) { const line = ( { width, height, } = useAppSelector(selector); - const { getUrl } = useGetUrl(); return ( {shouldShowStagingImage && currentStagingAreaImage && ( - + )} {shouldShowStagingOutline && ( diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx index 64c752fce0..eb9129e4c1 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx @@ -1,6 +1,5 @@ import { ButtonGroup, Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -// import { saveStagingAreaImageToGallery } from 'app/socketio/actions'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; @@ -26,13 +25,14 @@ import { FaPlus, FaSave, } from 'react-icons/fa'; +import { stagingAreaImageSaved } from '../store/actions'; const selector = createSelector( [canvasSelector], (canvas) => { const { layerState: { - stagingArea: { images, selectedImageIndex }, + stagingArea: { images, selectedImageIndex, sessionId }, }, shouldShowStagingOutline, shouldShowStagingImage, @@ -45,6 +45,7 @@ const selector = createSelector( isOnLastImage: selectedImageIndex === images.length - 1, shouldShowStagingImage, shouldShowStagingOutline, + sessionId, }; }, { @@ -61,6 +62,7 @@ const IAICanvasStagingAreaToolbar = () => { isOnLastImage, currentStagingAreaImage, shouldShowStagingImage, + sessionId, } = useAppSelector(selector); const { t } = useTranslation(); @@ -106,9 +108,20 @@ const IAICanvasStagingAreaToolbar = () => { } ); - const handlePrevImage = () => dispatch(prevStagingAreaImage()); - const handleNextImage = () => dispatch(nextStagingAreaImage()); - const handleAccept = () => dispatch(commitStagingAreaImage()); + const handlePrevImage = useCallback( + () => dispatch(prevStagingAreaImage()), + [dispatch] + ); + + const handleNextImage = useCallback( + () => dispatch(nextStagingAreaImage()), + [dispatch] + ); + + const handleAccept = useCallback( + () => dispatch(commitStagingAreaImage(sessionId)), + [dispatch, sessionId] + ); if (!currentStagingAreaImage) return null; @@ -157,19 +170,19 @@ const IAICanvasStagingAreaToolbar = () => { } colorScheme="accent" /> - {/* } onClick={() => dispatch( - saveStagingAreaImageToGallery( - currentStagingAreaImage.image.image_url - ) + stagingAreaImageSaved({ + imageName: currentStagingAreaImage.imageName, + }) ) } colorScheme="accent" - /> */} + /> { margin: 1, borderRadius: 'base', pointerEvents: 'none', - bg: 'base.800', + bg: 'base.200', + _dark: { + bg: 'base.800', + }, }} > { } > - - diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index 94a990bb4c..ae03df8409 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -1,7 +1,7 @@ import { Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; @@ -16,7 +16,6 @@ import { setShouldShowIntermediates, setShouldSnapToGrid, } from 'features/canvas/store/canvasSlice'; -import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal'; import { isEqual } from 'lodash-es'; import { ChangeEvent } from 'react'; @@ -102,50 +101,50 @@ const IAICanvasSettingsButtonPopover = () => { } > - dispatch(setShouldShowIntermediates(e.target.checked)) } /> - dispatch(setShouldShowGrid(e.target.checked))} /> - - dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)) } /> - dispatch(setShouldAutoSave(e.target.checked))} /> - dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)) } /> - dispatch(setShouldRestrictStrokesToBox(e.target.checked)) } /> - @@ -153,13 +152,12 @@ const IAICanvasSettingsButtonPopover = () => { } /> - dispatch(setShouldAntialias(e.target.checked))} /> - ); diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx index 69eed2b46a..8c46d67de3 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx @@ -1,8 +1,7 @@ -import { ButtonGroup, Flex } from '@chakra-ui/react'; +import { Box, ButtonGroup, Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; -import IAISelect from 'common/components/IAISelect'; import useImageUploader from 'common/hooks/useImageUploader'; import { useSingleAndDoubleClick } from 'common/hooks/useSingleAndDoubleClick'; import { @@ -25,7 +24,13 @@ import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { systemSelector } from 'features/system/store/systemSelectors'; import { isEqual } from 'lodash-es'; -import { ChangeEvent } from 'react'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { + canvasCopiedToClipboard, + canvasDownloadedAsImage, + canvasMerged, + canvasSavedToGallery, +} from 'features/canvas/store/actions'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { @@ -43,12 +48,6 @@ import IAICanvasRedoButton from './IAICanvasRedoButton'; import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover'; import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions'; import IAICanvasUndoButton from './IAICanvasUndoButton'; -import { - canvasCopiedToClipboard, - canvasDownloadedAsImage, - canvasMerged, - canvasSavedToGallery, -} from 'features/canvas/store/actions'; export const selector = createSelector( [systemSelector, canvasSelector, isStagingSelector], @@ -197,8 +196,8 @@ const IAICanvasToolbar = () => { dispatch(canvasDownloadedAsImage()); }; - const handleChangeLayer = (e: ChangeEvent) => { - const newLayer = e.target.value as CanvasLayer; + const handleChangeLayer = (v: string) => { + const newLayer = v as CanvasLayer; dispatch(setLayer(newLayer)); if (newLayer === 'mask' && !isMaskEnabled) { dispatch(setIsMaskEnabled(true)); @@ -210,16 +209,18 @@ const IAICanvasToolbar = () => { sx={{ alignItems: 'center', gap: 2, + flexWrap: 'wrap', }} > - + + + diff --git a/invokeai/frontend/web/src/features/canvas/store/actions.ts b/invokeai/frontend/web/src/features/canvas/store/actions.ts index 2dce8af694..d10662b3c9 100644 --- a/invokeai/frontend/web/src/features/canvas/store/actions.ts +++ b/invokeai/frontend/web/src/features/canvas/store/actions.ts @@ -11,3 +11,7 @@ export const canvasDownloadedAsImage = createAction( ); export const canvasMerged = createAction('canvas/canvasMerged'); + +export const stagingAreaImageSaved = createAction<{ imageName: string }>( + 'canvas/stagingAreaImageSaved' +); diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 0ebe5b264c..371719e087 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -28,7 +28,13 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; -import { ImageDTO } from 'services/api'; +import { ImageDTO } from 'services/api/types'; +import { sessionCanceled } from 'services/api/thunks/session'; +import { + setActiveTab, + setShouldUseCanvasBetaLayout, +} from 'features/ui/store/uiSlice'; +import { imageUrlsReceived } from 'services/api/thunks/image'; export const initialLayerState: CanvasLayerState = { objects: [], @@ -197,7 +203,7 @@ export const canvasSlice = createSlice({ y: 0, width: width, height: height, - image: image, + imageName: image.image_name, }, ], }; @@ -319,7 +325,7 @@ export const canvasSlice = createSlice({ kind: 'image', layer: 'base', ...state.layerState.stagingArea.boundingBox, - image, + imageName: image.image_name, }); state.layerState.stagingArea.selectedImageIndex = @@ -696,7 +702,10 @@ export const canvasSlice = createSlice({ 0 ); }, - commitStagingAreaImage: (state) => { + commitStagingAreaImage: ( + state, + action: PayloadAction + ) => { if (!state.layerState.stagingArea.images.length) { return; } @@ -841,6 +850,41 @@ export const canvasSlice = createSlice({ state.isTransformingBoundingBox = false; }, }, + extraReducers: (builder) => { + builder.addCase(sessionCanceled.pending, (state) => { + if (!state.layerState.stagingArea.images.length) { + state.layerState.stagingArea = initialLayerState.stagingArea; + } + }); + + builder.addCase(setShouldUseCanvasBetaLayout, (state, action) => { + state.doesCanvasNeedScaling = true; + }); + + builder.addCase(setActiveTab, (state, action) => { + state.doesCanvasNeedScaling = true; + }); + + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; + + // state.layerState.objects.forEach((object) => { + // if (object.kind === 'image') { + // if (object.image.image_name === image_name) { + // object.image.image_url = image_url; + // object.image.thumbnail_url = thumbnail_url; + // } + // } + // }); + + // state.layerState.stagingArea.images.forEach((stagedImage) => { + // if (stagedImage.image.image_name === image_name) { + // stagedImage.image.image_url = image_url; + // stagedImage.image.thumbnail_url = thumbnail_url; + // } + // }); + // }); + }, }); export const { diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index 804e06f88f..54eafd9780 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -1,11 +1,11 @@ import * as InvokeAI from 'app/types/invokeai'; import { IRect, Vector2d } from 'konva/lib/types'; import { RgbaColor } from 'react-colorful'; -import { ImageDTO } from 'services/api'; +import { ImageDTO } from 'services/api/types'; export const LAYER_NAMES_DICT = [ - { key: 'Base', value: 'base' }, - { key: 'Mask', value: 'mask' }, + { label: 'Base', value: 'base' }, + { label: 'Mask', value: 'mask' }, ]; export const LAYER_NAMES = ['base', 'mask'] as const; @@ -13,9 +13,9 @@ export const LAYER_NAMES = ['base', 'mask'] as const; export type CanvasLayer = (typeof LAYER_NAMES)[number]; export const BOUNDING_BOX_SCALES_DICT = [ - { key: 'Auto', value: 'auto' }, - { key: 'Manual', value: 'manual' }, - { key: 'None', value: 'none' }, + { label: 'Auto', value: 'auto' }, + { label: 'Manual', value: 'manual' }, + { label: 'None', value: 'none' }, ]; export const BOUNDING_BOX_SCALES = ['none', 'auto', 'manual'] as const; @@ -38,7 +38,7 @@ export type CanvasImage = { y: number; width: number; height: number; - image: ImageDTO; + imageName: string; }; export type CanvasMaskLine = { diff --git a/invokeai/frontend/web/src/features/canvas/util/createMaskStage.ts b/invokeai/frontend/web/src/features/canvas/util/createMaskStage.ts index 96ac592711..b417b3a786 100644 --- a/invokeai/frontend/web/src/features/canvas/util/createMaskStage.ts +++ b/invokeai/frontend/web/src/features/canvas/util/createMaskStage.ts @@ -9,7 +9,8 @@ import { IRect } from 'konva/lib/types'; */ const createMaskStage = async ( lines: CanvasMaskLine[], - boundingBox: IRect + boundingBox: IRect, + shouldInvertMask: boolean ): Promise => { // create an offscreen canvas and add the mask to it const { width, height } = boundingBox; @@ -29,7 +30,7 @@ const createMaskStage = async ( baseLayer.add( new Konva.Rect({ ...boundingBox, - fill: 'white', + fill: shouldInvertMask ? 'black' : 'white', }) ); @@ -37,7 +38,7 @@ const createMaskStage = async ( maskLayer.add( new Konva.Line({ points: line.points, - stroke: 'black', + stroke: shouldInvertMask ? 'white' : 'black', strokeWidth: line.strokeWidth * 2, tension: 0, lineCap: 'round', diff --git a/invokeai/frontend/web/src/features/canvas/util/getBaseLayerBlob.ts b/invokeai/frontend/web/src/features/canvas/util/getBaseLayerBlob.ts index a576551d72..20ac482710 100644 --- a/invokeai/frontend/web/src/features/canvas/util/getBaseLayerBlob.ts +++ b/invokeai/frontend/web/src/features/canvas/util/getBaseLayerBlob.ts @@ -2,10 +2,10 @@ import { getCanvasBaseLayer } from './konvaInstanceProvider'; import { RootState } from 'app/store/store'; import { konvaNodeToBlob } from './konvaNodeToBlob'; -export const getBaseLayerBlob = async ( - state: RootState, - withoutBoundingBox?: boolean -) => { +/** + * Get the canvas base layer blob, with or without bounding box according to `shouldCropToBoundingBoxOnSave` + */ +export const getBaseLayerBlob = async (state: RootState) => { const canvasBaseLayer = getCanvasBaseLayer(); if (!canvasBaseLayer) { @@ -24,15 +24,14 @@ export const getBaseLayerBlob = async ( const absPos = clonedBaseLayer.getAbsolutePosition(); - const boundingBox = - shouldCropToBoundingBoxOnSave && !withoutBoundingBox - ? { - x: boundingBoxCoordinates.x + absPos.x, - y: boundingBoxCoordinates.y + absPos.y, - width: boundingBoxDimensions.width, - height: boundingBoxDimensions.height, - } - : clonedBaseLayer.getClientRect(); + const boundingBox = shouldCropToBoundingBoxOnSave + ? { + x: boundingBoxCoordinates.x + absPos.x, + y: boundingBoxCoordinates.y + absPos.y, + width: boundingBoxDimensions.width, + height: boundingBoxDimensions.height, + } + : clonedBaseLayer.getClientRect(); return konvaNodeToBlob(clonedBaseLayer, boundingBox); }; diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts index 21a33aa349..d0190878e2 100644 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts @@ -25,6 +25,7 @@ export const getCanvasData = async (state: RootState) => { boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, + shouldPreserveMaskedArea, } = state.canvas; const boundingBox = { @@ -58,7 +59,8 @@ export const getCanvasData = async (state: RootState) => { // For the mask layer, use the normal boundingBox const maskStage = await createMaskStage( isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], // only include mask lines, and only if mask is enabled - boundingBox + boundingBox, + shouldPreserveMaskedArea ); const maskBlob = await konvaNodeToBlob(maskStage, boundingBox); const maskImageData = await konvaNodeToImageData(maskStage, boundingBox); diff --git a/invokeai/frontend/web/src/features/canvas/util/getFullBaseLayerBlob.ts b/invokeai/frontend/web/src/features/canvas/util/getFullBaseLayerBlob.ts new file mode 100644 index 0000000000..ba855723fb --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/util/getFullBaseLayerBlob.ts @@ -0,0 +1,19 @@ +import { getCanvasBaseLayer } from './konvaInstanceProvider'; +import { konvaNodeToBlob } from './konvaNodeToBlob'; + +/** + * Gets the canvas base layer blob, without bounding box + */ +export const getFullBaseLayerBlob = async () => { + const canvasBaseLayer = getCanvasBaseLayer(); + + if (!canvasBaseLayer) { + return; + } + + const clonedBaseLayer = canvasBaseLayer.clone(); + + clonedBaseLayer.scale({ x: 1, y: 1 }); + + return konvaNodeToBlob(clonedBaseLayer, clonedBaseLayer.getClientRect()); +}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx new file mode 100644 index 0000000000..bb01416e1d --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -0,0 +1,219 @@ +import { Box, ChakraProps, Flex, useColorMode } from '@chakra-ui/react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { memo, useCallback } from 'react'; +import { FaCopy, FaTrash } from 'react-icons/fa'; +import { + ControlNetConfig, + controlNetAdded, + controlNetRemoved, + controlNetToggled, +} from '../store/controlNetSlice'; +import ParamControlNetModel from './parameters/ParamControlNetModel'; +import ParamControlNetWeight from './parameters/ParamControlNetWeight'; + +import { ChevronUpIcon } from '@chakra-ui/icons'; +import IAIIconButton from 'common/components/IAIIconButton'; +import IAISwitch from 'common/components/IAISwitch'; +import { useToggle } from 'react-use'; +import { v4 as uuidv4 } from 'uuid'; +import ControlNetImagePreview from './ControlNetImagePreview'; +import ControlNetProcessorComponent from './ControlNetProcessorComponent'; +import ParamControlNetShouldAutoConfig from './ParamControlNetShouldAutoConfig'; +import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd'; +import ParamControlNetControlMode from './parameters/ParamControlNetControlMode'; +import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; +import { mode } from 'theme/util/mode'; + +const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 }; + +type ControlNetProps = { + controlNet: ControlNetConfig; +}; + +const ControlNet = (props: ControlNetProps) => { + const { + controlNetId, + isEnabled, + model, + weight, + beginStepPct, + endStepPct, + controlMode, + controlImage, + processedControlImage, + processorNode, + processorType, + shouldAutoConfig, + } = props.controlNet; + const dispatch = useAppDispatch(); + const [isExpanded, toggleIsExpanded] = useToggle(false); + const { colorMode } = useColorMode(); + const handleDelete = useCallback(() => { + dispatch(controlNetRemoved({ controlNetId })); + }, [controlNetId, dispatch]); + + const handleDuplicate = useCallback(() => { + dispatch( + controlNetAdded({ controlNetId: uuidv4(), controlNet: props.controlNet }) + ); + }, [dispatch, props.controlNet]); + + const handleToggleIsEnabled = useCallback(() => { + dispatch(controlNetToggled({ controlNetId })); + }, [controlNetId, dispatch]); + + return ( + + + + + + + } + /> + } + /> + + } + /> + {!shouldAutoConfig && ( + + )} + + {isEnabled && ( + <> + + + + + + + {!isExpanded && ( + + + + )} + + + + + {isExpanded && ( + <> + + + + + + + + )} + + )} + + ); +}; + +export default memo(ControlNet); diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx new file mode 100644 index 0000000000..36d82dc2ee --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx @@ -0,0 +1,173 @@ +import { memo, useCallback, useState } from 'react'; +import { ImageDTO } from 'services/api/types'; +import { + ControlNetConfig, + controlNetImageChanged, + controlNetSelector, +} from '../store/controlNetSlice'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { Box, Flex, SystemStyleObject } from '@chakra-ui/react'; +import IAIDndImage from 'common/components/IAIDndImage'; +import { createSelector } from '@reduxjs/toolkit'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { FaUndo } from 'react-icons/fa'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; + +const selector = createSelector( + controlNetSelector, + (controlNet) => { + const { pendingControlImages } = controlNet; + return { pendingControlImages }; + }, + defaultSelectorOptions +); + +type Props = { + controlNet: ControlNetConfig; + height: SystemStyleObject['h']; +}; + +const ControlNetImagePreview = (props: Props) => { + const { height } = props; + const { + controlNetId, + controlImage: controlImageName, + processedControlImage: processedControlImageName, + processorType, + } = props.controlNet; + const dispatch = useAppDispatch(); + const { pendingControlImages } = useAppSelector(selector); + + const [isMouseOverImage, setIsMouseOverImage] = useState(false); + + const { + currentData: controlImage, + isLoading: isLoadingControlImage, + isError: isErrorControlImage, + isSuccess: isSuccessControlImage, + } = useGetImageDTOQuery(controlImageName ?? skipToken); + + const { + currentData: processedControlImage, + isLoading: isLoadingProcessedControlImage, + isError: isErrorProcessedControlImage, + isSuccess: isSuccessProcessedControlImage, + } = useGetImageDTOQuery(processedControlImageName ?? skipToken); + + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (controlImageName === droppedImage.image_name) { + return; + } + setIsMouseOverImage(false); + dispatch( + controlNetImageChanged({ + controlNetId, + controlImage: droppedImage.image_name, + }) + ); + }, + [controlImageName, controlNetId, dispatch] + ); + + const handleResetControlImage = useCallback(() => { + dispatch(controlNetImageChanged({ controlNetId, controlImage: null })); + }, [controlNetId, dispatch]); + const handleMouseEnter = useCallback(() => { + setIsMouseOverImage(true); + }, []); + + const handleMouseLeave = useCallback(() => { + setIsMouseOverImage(false); + }, []); + + const shouldShowProcessedImage = + controlImage && + processedControlImage && + !isMouseOverImage && + !pendingControlImages.includes(controlNetId) && + processorType !== 'none'; + + return ( + + + + + + {pendingControlImages.includes(controlNetId) && ( + + + + )} + {controlImage && ( + + } + variant="link" + sx={{ + p: 2, + color: 'base.50', + }} + /> + + )} + + ); +}; + +export default memo(ControlNetImagePreview); diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetPreprocessButton.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetPreprocessButton.tsx new file mode 100644 index 0000000000..95a4f968e5 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetPreprocessButton.tsx @@ -0,0 +1,36 @@ +import IAIButton from 'common/components/IAIButton'; +import { memo, useCallback } from 'react'; +import { ControlNetConfig } from '../store/controlNetSlice'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { controlNetImageProcessed } from '../store/actions'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +type Props = { + controlNet: ControlNetConfig; +}; + +const ControlNetPreprocessButton = (props: Props) => { + const { controlNetId, controlImage } = props.controlNet; + const dispatch = useAppDispatch(); + const isReady = useIsReadyToInvoke(); + + const handleProcess = useCallback(() => { + dispatch( + controlNetImageProcessed({ + controlNetId, + }) + ); + }, [controlNetId, dispatch]); + + return ( + + Preprocess + + ); +}; + +export default memo(ControlNetPreprocessButton); diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorComponent.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorComponent.tsx new file mode 100644 index 0000000000..4649f89b35 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorComponent.tsx @@ -0,0 +1,131 @@ +import { memo } from 'react'; +import { RequiredControlNetProcessorNode } from '../store/types'; +import CannyProcessor from './processors/CannyProcessor'; +import HedProcessor from './processors/HedProcessor'; +import LineartProcessor from './processors/LineartProcessor'; +import LineartAnimeProcessor from './processors/LineartAnimeProcessor'; +import ContentShuffleProcessor from './processors/ContentShuffleProcessor'; +import MediapipeFaceProcessor from './processors/MediapipeFaceProcessor'; +import MidasDepthProcessor from './processors/MidasDepthProcessor'; +import MlsdImageProcessor from './processors/MlsdImageProcessor'; +import NormalBaeProcessor from './processors/NormalBaeProcessor'; +import OpenposeProcessor from './processors/OpenposeProcessor'; +import PidiProcessor from './processors/PidiProcessor'; +import ZoeDepthProcessor from './processors/ZoeDepthProcessor'; + +export type ControlNetProcessorProps = { + controlNetId: string; + processorNode: RequiredControlNetProcessorNode; +}; + +const ControlNetProcessorComponent = (props: ControlNetProcessorProps) => { + const { controlNetId, processorNode } = props; + if (processorNode.type === 'canny_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'hed_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'lineart_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'content_shuffle_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'lineart_anime_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'mediapipe_face_processor') { + return ( + + ); + } + + if (processorNode.type === 'midas_depth_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'mlsd_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'normalbae_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'openpose_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'pidi_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'zoe_depth_image_processor') { + return ( + + ); + } + + return null; +}; + +export default memo(ControlNetProcessorComponent); diff --git a/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx new file mode 100644 index 0000000000..1b3553e3be --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx @@ -0,0 +1,31 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISwitch from 'common/components/IAISwitch'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; +import { controlNetAutoConfigToggled } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type Props = { + controlNetId: string; + shouldAutoConfig: boolean; +}; + +const ParamControlNetShouldAutoConfig = (props: Props) => { + const { controlNetId, shouldAutoConfig } = props; + const dispatch = useAppDispatch(); + const isReady = useIsReadyToInvoke(); + const handleShouldAutoConfigChanged = useCallback(() => { + dispatch(controlNetAutoConfigToggled({ controlNetId })); + }, [controlNetId, dispatch]); + + return ( + + ); +}; + +export default memo(ParamControlNetShouldAutoConfig); diff --git a/invokeai/frontend/web/src/features/controlNet/components/hooks/useProcessorNodeChanged.ts b/invokeai/frontend/web/src/features/controlNet/components/hooks/useProcessorNodeChanged.ts new file mode 100644 index 0000000000..79a502cb0e --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/hooks/useProcessorNodeChanged.ts @@ -0,0 +1,20 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import { controlNetProcessorParamsChanged } from 'features/controlNet/store/controlNetSlice'; +import { ControlNetProcessorNode } from 'features/controlNet/store/types'; +import { useCallback } from 'react'; + +export const useProcessorNodeChanged = () => { + const dispatch = useAppDispatch(); + const handleProcessorNodeChanged = useCallback( + (controlNetId: string, changes: Partial) => { + dispatch( + controlNetProcessorParamsChanged({ + controlNetId, + changes, + }) + ); + }, + [dispatch] + ); + return handleProcessorNodeChanged; +}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx new file mode 100644 index 0000000000..7d0c53fe40 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx @@ -0,0 +1,117 @@ +import { + ChakraProps, + FormControl, + FormLabel, + HStack, + RangeSlider, + RangeSliderFilledTrack, + RangeSliderMark, + RangeSliderThumb, + RangeSliderTrack, + Tooltip, +} from '@chakra-ui/react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { + controlNetBeginStepPctChanged, + controlNetEndStepPctChanged, +} from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +const SLIDER_MARK_STYLES: ChakraProps['sx'] = { + mt: 1.5, + fontSize: '2xs', + fontWeight: '500', + color: 'base.400', +}; + +type Props = { + controlNetId: string; + beginStepPct: number; + endStepPct: number; + mini?: boolean; +}; + +const formatPct = (v: number) => `${Math.round(v * 100)}%`; + +const ParamControlNetBeginEnd = (props: Props) => { + const { controlNetId, beginStepPct, mini = false, endStepPct } = props; + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const handleStepPctChanged = useCallback( + (v: number[]) => { + dispatch( + controlNetBeginStepPctChanged({ controlNetId, beginStepPct: v[0] }) + ); + dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct: v[1] })); + }, + [controlNetId, dispatch] + ); + + const handleStepPctReset = useCallback(() => { + dispatch(controlNetBeginStepPctChanged({ controlNetId, beginStepPct: 0 })); + dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct: 1 })); + }, [controlNetId, dispatch]); + + return ( + + Begin / End Step Percentage + + + + + + + + + + + + {!mini && ( + <> + + 0% + + + 50% + + + 100% + + + )} + + + + ); +}; + +export default memo(ParamControlNetBeginEnd); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetControlMode.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetControlMode.tsx new file mode 100644 index 0000000000..b8737004fd --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetControlMode.tsx @@ -0,0 +1,45 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { + ControlModes, + controlNetControlModeChanged, +} from 'features/controlNet/store/controlNetSlice'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +type ParamControlNetControlModeProps = { + controlNetId: string; + controlMode: string; +}; + +const CONTROL_MODE_DATA = [ + { label: 'Balanced', value: 'balanced' }, + { label: 'Prompt', value: 'more_prompt' }, + { label: 'Control', value: 'more_control' }, + { label: 'Mega Control', value: 'unbalanced' }, +]; + +export default function ParamControlNetControlMode( + props: ParamControlNetControlModeProps +) { + const { controlNetId, controlMode = false } = props; + const dispatch = useAppDispatch(); + + const { t } = useTranslation(); + + const handleControlModeChange = useCallback( + (controlMode: ControlModes) => { + dispatch(controlNetControlModeChanged({ controlNetId, controlMode })); + }, + [controlNetId, dispatch] + ); + + return ( + + ); +} diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsEnabled.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsEnabled.tsx new file mode 100644 index 0000000000..d7f519a7b6 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsEnabled.tsx @@ -0,0 +1,28 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISwitch from 'common/components/IAISwitch'; +import { controlNetToggled } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamControlNetIsEnabledProps = { + controlNetId: string; + isEnabled: boolean; +}; + +const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => { + const { controlNetId, isEnabled } = props; + const dispatch = useAppDispatch(); + + const handleIsEnabledChanged = useCallback(() => { + dispatch(controlNetToggled({ controlNetId })); + }, [dispatch, controlNetId]); + + return ( + + ); +}; + +export default memo(ParamControlNetIsEnabled); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsPreprocessed.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsPreprocessed.tsx new file mode 100644 index 0000000000..6db61a0d15 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsPreprocessed.tsx @@ -0,0 +1,36 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAIFullCheckbox from 'common/components/IAIFullCheckbox'; +import IAISwitch from 'common/components/IAISwitch'; +import { + controlNetToggled, + isControlNetImagePreprocessedToggled, +} from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamControlNetIsEnabledProps = { + controlNetId: string; + isControlImageProcessed: boolean; +}; + +const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => { + const { controlNetId, isControlImageProcessed } = props; + const dispatch = useAppDispatch(); + + const handleIsControlImageProcessedToggled = useCallback(() => { + dispatch( + isControlNetImagePreprocessedToggled({ + controlNetId, + }) + ); + }, [controlNetId, dispatch]); + + return ( + + ); +}; + +export default memo(ParamControlNetIsEnabled); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx new file mode 100644 index 0000000000..7a6def717a --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx @@ -0,0 +1,61 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIMantineSelect, { + IAISelectDataType, +} from 'common/components/IAIMantineSelect'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; +import { + CONTROLNET_MODELS, + ControlNetModelName, +} from 'features/controlNet/store/constants'; +import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice'; +import { configSelector } from 'features/system/store/configSelectors'; +import { map } from 'lodash-es'; +import { memo, useCallback } from 'react'; + +type ParamControlNetModelProps = { + controlNetId: string; + model: ControlNetModelName; +}; + +const selector = createSelector(configSelector, (config) => { + const controlNetModels: IAISelectDataType[] = map(CONTROLNET_MODELS, (m) => ({ + label: m.label, + value: m.type, + })).filter( + (d) => + !config.sd.disabledControlNetModels.includes( + d.value as ControlNetModelName + ) + ); + + return controlNetModels; +}); + +const ParamControlNetModel = (props: ParamControlNetModelProps) => { + const { controlNetId, model } = props; + const controlNetModels = useAppSelector(selector); + const dispatch = useAppDispatch(); + const isReady = useIsReadyToInvoke(); + + const handleModelChanged = useCallback( + (val: string | null) => { + // TODO: do not cast + const model = val as ControlNetModelName; + dispatch(controlNetModelChanged({ controlNetId, model })); + }, + [controlNetId, dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamControlNetModel); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx new file mode 100644 index 0000000000..9f333e1c33 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx @@ -0,0 +1,85 @@ +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; + +import IAIMantineSelect, { + IAISelectDataType, +} from 'common/components/IAIMantineSelect'; +import { map } from 'lodash-es'; +import { memo, useCallback } from 'react'; +import { CONTROLNET_PROCESSORS } from '../../store/constants'; +import { controlNetProcessorTypeChanged } from '../../store/controlNetSlice'; +import { + ControlNetProcessorNode, + ControlNetProcessorType, +} from '../../store/types'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; +import { createSelector } from '@reduxjs/toolkit'; +import { configSelector } from 'features/system/store/configSelectors'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; + +type ParamControlNetProcessorSelectProps = { + controlNetId: string; + processorNode: ControlNetProcessorNode; +}; + +const selector = createSelector( + configSelector, + (config) => { + const controlNetProcessors: IAISelectDataType[] = map( + CONTROLNET_PROCESSORS, + (p) => ({ + value: p.type, + label: p.label, + }) + ) + .sort((a, b) => + // sort 'none' to the top + a.value === 'none' + ? -1 + : b.value === 'none' + ? 1 + : a.label.localeCompare(b.label) + ) + .filter( + (d) => + !config.sd.disabledControlNetProcessors.includes( + d.value as ControlNetProcessorType + ) + ); + + return controlNetProcessors; + }, + defaultSelectorOptions +); + +const ParamControlNetProcessorSelect = ( + props: ParamControlNetProcessorSelectProps +) => { + const { controlNetId, processorNode } = props; + const dispatch = useAppDispatch(); + const isReady = useIsReadyToInvoke(); + const controlNetProcessors = useAppSelector(selector); + + const handleProcessorTypeChanged = useCallback( + (v: string | null) => { + dispatch( + controlNetProcessorTypeChanged({ + controlNetId, + processorType: v as ControlNetProcessorType, + }) + ); + }, + [controlNetId, dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamControlNetProcessorSelect); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx new file mode 100644 index 0000000000..c2b77125d0 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx @@ -0,0 +1,38 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { controlNetWeightChanged } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamControlNetWeightProps = { + controlNetId: string; + weight: number; + mini?: boolean; +}; + +const ParamControlNetWeight = (props: ParamControlNetWeightProps) => { + const { controlNetId, weight, mini = false } = props; + const dispatch = useAppDispatch(); + + const handleWeightChanged = useCallback( + (weight: number) => { + dispatch(controlNetWeightChanged({ controlNetId, weight })); + }, + [controlNetId, dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamControlNetWeight); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx new file mode 100644 index 0000000000..1b62056c7a --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx @@ -0,0 +1,78 @@ +import IAISlider from 'common/components/IAISlider'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredCannyImageProcessorInvocation } from 'features/controlNet/store/types'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor.default; + +type CannyProcessorProps = { + controlNetId: string; + processorNode: RequiredCannyImageProcessorInvocation; +}; + +const CannyProcessor = (props: CannyProcessorProps) => { + const { controlNetId, processorNode } = props; + const { low_threshold, high_threshold } = processorNode; + const isReady = useIsReadyToInvoke(); + const processorChanged = useProcessorNodeChanged(); + + const handleLowThresholdChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { low_threshold: v }); + }, + [controlNetId, processorChanged] + ); + + const handleLowThresholdReset = useCallback(() => { + processorChanged(controlNetId, { + low_threshold: DEFAULTS.low_threshold, + }); + }, [controlNetId, processorChanged]); + + const handleHighThresholdChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { high_threshold: v }); + }, + [controlNetId, processorChanged] + ); + + const handleHighThresholdReset = useCallback(() => { + processorChanged(controlNetId, { + high_threshold: DEFAULTS.high_threshold, + }); + }, [controlNetId, processorChanged]); + + return ( + + + + + ); +}; + +export default memo(CannyProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/ContentShuffleProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/ContentShuffleProcessor.tsx new file mode 100644 index 0000000000..70f97fc6ad --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/ContentShuffleProcessor.tsx @@ -0,0 +1,153 @@ +import IAISlider from 'common/components/IAISlider'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredContentShuffleImageProcessorInvocation } from 'features/controlNet/store/types'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredContentShuffleImageProcessorInvocation; +}; + +const ContentShuffleProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, w, h, f } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleWChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { w: v }); + }, + [controlNetId, processorChanged] + ); + + const handleWReset = useCallback(() => { + processorChanged(controlNetId, { + w: DEFAULTS.w, + }); + }, [controlNetId, processorChanged]); + + const handleHChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { h: v }); + }, + [controlNetId, processorChanged] + ); + + const handleHReset = useCallback(() => { + processorChanged(controlNetId, { + h: DEFAULTS.h, + }); + }, [controlNetId, processorChanged]); + + const handleFChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { f: v }); + }, + [controlNetId, processorChanged] + ); + + const handleFReset = useCallback(() => { + processorChanged(controlNetId, { + f: DEFAULTS.f, + }); + }, [controlNetId, processorChanged]); + + return ( + + + + + + + + ); +}; + +export default memo(ContentShuffleProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/HedProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/HedProcessor.tsx new file mode 100644 index 0000000000..c51a44d1c3 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/HedProcessor.tsx @@ -0,0 +1,94 @@ +import IAISlider from 'common/components/IAISlider'; +import IAISwitch from 'common/components/IAISwitch'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredHedImageProcessorInvocation } from 'features/controlNet/store/types'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor.default; + +type HedProcessorProps = { + controlNetId: string; + processorNode: RequiredHedImageProcessorInvocation; +}; + +const HedPreprocessor = (props: HedProcessorProps) => { + const { + controlNetId, + processorNode: { detect_resolution, image_resolution, scribble }, + } = props; + const isReady = useIsReadyToInvoke(); + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleScribbleChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { scribble: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + return ( + + + + + + ); +}; + +export default memo(HedPreprocessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/LineartAnimeProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartAnimeProcessor.tsx new file mode 100644 index 0000000000..bc64e3c843 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartAnimeProcessor.tsx @@ -0,0 +1,78 @@ +import IAISlider from 'common/components/IAISlider'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredLineartAnimeImageProcessorInvocation } from 'features/controlNet/store/types'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredLineartAnimeImageProcessorInvocation; +}; + +const LineartAnimeProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + return ( + + + + + ); +}; + +export default memo(LineartAnimeProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/LineartProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartProcessor.tsx new file mode 100644 index 0000000000..11245bf4a7 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartProcessor.tsx @@ -0,0 +1,92 @@ +import IAISlider from 'common/components/IAISlider'; +import IAISwitch from 'common/components/IAISwitch'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredLineartImageProcessorInvocation } from 'features/controlNet/store/types'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor.default; + +type LineartProcessorProps = { + controlNetId: string; + processorNode: RequiredLineartImageProcessorInvocation; +}; + +const LineartProcessor = (props: LineartProcessorProps) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, coarse } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleCoarseChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { coarse: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + + ); +}; + +export default memo(LineartProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/MediapipeFaceProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/MediapipeFaceProcessor.tsx new file mode 100644 index 0000000000..27aa22ca40 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/MediapipeFaceProcessor.tsx @@ -0,0 +1,75 @@ +import IAISlider from 'common/components/IAISlider'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredMediapipeFaceProcessorInvocation } from 'features/controlNet/store/types'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredMediapipeFaceProcessorInvocation; +}; + +const MediapipeFaceProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { max_faces, min_confidence } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleMaxFacesChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { max_faces: v }); + }, + [controlNetId, processorChanged] + ); + + const handleMinConfidenceChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { min_confidence: v }); + }, + [controlNetId, processorChanged] + ); + + const handleMaxFacesReset = useCallback(() => { + processorChanged(controlNetId, { max_faces: DEFAULTS.max_faces }); + }, [controlNetId, processorChanged]); + + const handleMinConfidenceReset = useCallback(() => { + processorChanged(controlNetId, { min_confidence: DEFAULTS.min_confidence }); + }, [controlNetId, processorChanged]); + + return ( + + + + + ); +}; + +export default memo(MediapipeFaceProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/MidasDepthProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/MidasDepthProcessor.tsx new file mode 100644 index 0000000000..ffecb68061 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/MidasDepthProcessor.tsx @@ -0,0 +1,76 @@ +import IAISlider from 'common/components/IAISlider'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredMidasDepthImageProcessorInvocation } from 'features/controlNet/store/types'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredMidasDepthImageProcessorInvocation; +}; + +const MidasDepthProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { a_mult, bg_th } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleAMultChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { a_mult: v }); + }, + [controlNetId, processorChanged] + ); + + const handleBgThChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { bg_th: v }); + }, + [controlNetId, processorChanged] + ); + + const handleAMultReset = useCallback(() => { + processorChanged(controlNetId, { a_mult: DEFAULTS.a_mult }); + }, [controlNetId, processorChanged]); + + const handleBgThReset = useCallback(() => { + processorChanged(controlNetId, { bg_th: DEFAULTS.bg_th }); + }, [controlNetId, processorChanged]); + + return ( + + + + + ); +}; + +export default memo(MidasDepthProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/MlsdImageProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/MlsdImageProcessor.tsx new file mode 100644 index 0000000000..728c4b44fa --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/MlsdImageProcessor.tsx @@ -0,0 +1,126 @@ +import IAISlider from 'common/components/IAISlider'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredMlsdImageProcessorInvocation } from 'features/controlNet/store/types'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredMlsdImageProcessorInvocation; +}; + +const MlsdImageProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleThrDChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { thr_d: v }); + }, + [controlNetId, processorChanged] + ); + + const handleThrVChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { thr_v: v }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleThrDReset = useCallback(() => { + processorChanged(controlNetId, { thr_d: DEFAULTS.thr_d }); + }, [controlNetId, processorChanged]); + + const handleThrVReset = useCallback(() => { + processorChanged(controlNetId, { thr_v: DEFAULTS.thr_v }); + }, [controlNetId, processorChanged]); + + return ( + + + + + + + ); +}; + +export default memo(MlsdImageProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/NormalBaeProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/NormalBaeProcessor.tsx new file mode 100644 index 0000000000..d5dce8d492 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/NormalBaeProcessor.tsx @@ -0,0 +1,78 @@ +import IAISlider from 'common/components/IAISlider'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredNormalbaeImageProcessorInvocation } from 'features/controlNet/store/types'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredNormalbaeImageProcessorInvocation; +}; + +const NormalBaeProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + return ( + + + + + ); +}; + +export default memo(NormalBaeProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/OpenposeProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/OpenposeProcessor.tsx new file mode 100644 index 0000000000..e97b933b6b --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/OpenposeProcessor.tsx @@ -0,0 +1,92 @@ +import IAISlider from 'common/components/IAISlider'; +import IAISwitch from 'common/components/IAISwitch'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredOpenposeImageProcessorInvocation } from 'features/controlNet/store/types'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredOpenposeImageProcessorInvocation; +}; + +const OpenposeProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, hand_and_face } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleHandAndFaceChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { hand_and_face: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + + ); +}; + +export default memo(OpenposeProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/PidiProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/PidiProcessor.tsx new file mode 100644 index 0000000000..8251447195 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/PidiProcessor.tsx @@ -0,0 +1,104 @@ +import IAISlider from 'common/components/IAISlider'; +import IAISwitch from 'common/components/IAISwitch'; +import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants'; +import { RequiredPidiImageProcessorInvocation } from 'features/controlNet/store/types'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import ProcessorWrapper from './common/ProcessorWrapper'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; + +const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor.default; + +type Props = { + controlNetId: string; + processorNode: RequiredPidiImageProcessorInvocation; +}; + +const PidiProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, scribble, safe } = processorNode; + const processorChanged = useProcessorNodeChanged(); + const isReady = useIsReadyToInvoke(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleDetectResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + detect_resolution: DEFAULTS.detect_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleImageResolutionReset = useCallback(() => { + processorChanged(controlNetId, { + image_resolution: DEFAULTS.image_resolution, + }); + }, [controlNetId, processorChanged]); + + const handleScribbleChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { scribble: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + const handleSafeChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { safe: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + + + ); +}; + +export default memo(PidiProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/ZoeDepthProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/ZoeDepthProcessor.tsx new file mode 100644 index 0000000000..d0a34784bf --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/ZoeDepthProcessor.tsx @@ -0,0 +1,14 @@ +import { RequiredZoeDepthImageProcessorInvocation } from 'features/controlNet/store/types'; +import { memo } from 'react'; + +type Props = { + controlNetId: string; + processorNode: RequiredZoeDepthImageProcessorInvocation; +}; + +const ZoeDepthProcessor = (props: Props) => { + // Has no parameters? + return null; +}; + +export default memo(ZoeDepthProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/common/ProcessorWrapper.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/common/ProcessorWrapper.tsx new file mode 100644 index 0000000000..5dc0a909d5 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/common/ProcessorWrapper.tsx @@ -0,0 +1,8 @@ +import { Flex } from '@chakra-ui/react'; +import { PropsWithChildren } from 'react'; + +type Props = PropsWithChildren; + +export default function ProcessorWrapper(props: Props) { + return {props.children}; +} diff --git a/invokeai/frontend/web/src/features/controlNet/store/actions.ts b/invokeai/frontend/web/src/features/controlNet/store/actions.ts new file mode 100644 index 0000000000..3d9f56a36b --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/store/actions.ts @@ -0,0 +1,5 @@ +import { createAction } from '@reduxjs/toolkit'; + +export const controlNetImageProcessed = createAction<{ + controlNetId: string; +}>('controlNet/imageProcessed'); diff --git a/invokeai/frontend/web/src/features/controlNet/store/constants.ts b/invokeai/frontend/web/src/features/controlNet/store/constants.ts new file mode 100644 index 0000000000..df6ee653dc --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/store/constants.ts @@ -0,0 +1,263 @@ +import { + ControlNetProcessorType, + RequiredControlNetProcessorNode, +} from './types'; + +type ControlNetProcessorsDict = Record< + string, + { + type: ControlNetProcessorType | 'none'; + label: string; + description: string; + default: RequiredControlNetProcessorNode | { type: 'none' }; + } +>; + +/** + * A dict of ControlNet processors, including: + * - type + * - label + * - description + * - default values + * + * TODO: Generate from the OpenAPI schema + */ +export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { + none: { + type: 'none', + label: 'none', + description: '', + default: { + type: 'none', + }, + }, + canny_image_processor: { + type: 'canny_image_processor', + label: 'Canny', + description: '', + default: { + id: 'canny_image_processor', + type: 'canny_image_processor', + low_threshold: 100, + high_threshold: 200, + }, + }, + content_shuffle_image_processor: { + type: 'content_shuffle_image_processor', + label: 'Content Shuffle', + description: '', + default: { + id: 'content_shuffle_image_processor', + type: 'content_shuffle_image_processor', + detect_resolution: 512, + image_resolution: 512, + h: 512, + w: 512, + f: 256, + }, + }, + hed_image_processor: { + type: 'hed_image_processor', + label: 'HED', + description: '', + default: { + id: 'hed_image_processor', + type: 'hed_image_processor', + detect_resolution: 512, + image_resolution: 512, + scribble: false, + }, + }, + lineart_anime_image_processor: { + type: 'lineart_anime_image_processor', + label: 'Lineart Anime', + description: '', + default: { + id: 'lineart_anime_image_processor', + type: 'lineart_anime_image_processor', + detect_resolution: 512, + image_resolution: 512, + }, + }, + lineart_image_processor: { + type: 'lineart_image_processor', + label: 'Lineart', + description: '', + default: { + id: 'lineart_image_processor', + type: 'lineart_image_processor', + detect_resolution: 512, + image_resolution: 512, + coarse: false, + }, + }, + mediapipe_face_processor: { + type: 'mediapipe_face_processor', + label: 'Mediapipe Face', + description: '', + default: { + id: 'mediapipe_face_processor', + type: 'mediapipe_face_processor', + max_faces: 1, + min_confidence: 0.5, + }, + }, + midas_depth_image_processor: { + type: 'midas_depth_image_processor', + label: 'Depth (Midas)', + description: '', + default: { + id: 'midas_depth_image_processor', + type: 'midas_depth_image_processor', + a_mult: 2, + bg_th: 0.1, + }, + }, + mlsd_image_processor: { + type: 'mlsd_image_processor', + label: 'M-LSD', + description: '', + default: { + id: 'mlsd_image_processor', + type: 'mlsd_image_processor', + detect_resolution: 512, + image_resolution: 512, + thr_d: 0.1, + thr_v: 0.1, + }, + }, + normalbae_image_processor: { + type: 'normalbae_image_processor', + label: 'Normal BAE', + description: '', + default: { + id: 'normalbae_image_processor', + type: 'normalbae_image_processor', + detect_resolution: 512, + image_resolution: 512, + }, + }, + openpose_image_processor: { + type: 'openpose_image_processor', + label: 'Openpose', + description: '', + default: { + id: 'openpose_image_processor', + type: 'openpose_image_processor', + detect_resolution: 512, + image_resolution: 512, + hand_and_face: false, + }, + }, + pidi_image_processor: { + type: 'pidi_image_processor', + label: 'PIDI', + description: '', + default: { + id: 'pidi_image_processor', + type: 'pidi_image_processor', + detect_resolution: 512, + image_resolution: 512, + scribble: false, + safe: false, + }, + }, + zoe_depth_image_processor: { + type: 'zoe_depth_image_processor', + label: 'Depth (Zoe)', + description: '', + default: { + id: 'zoe_depth_image_processor', + type: 'zoe_depth_image_processor', + }, + }, +}; + +type ControlNetModelsDict = Record; + +type ControlNetModel = { + type: string; + label: string; + description?: string; + defaultProcessor?: ControlNetProcessorType; +}; + +export const CONTROLNET_MODELS: ControlNetModelsDict = { + 'lllyasviel/control_v11p_sd15_canny': { + type: 'lllyasviel/control_v11p_sd15_canny', + label: 'Canny', + defaultProcessor: 'canny_image_processor', + }, + 'lllyasviel/control_v11p_sd15_inpaint': { + type: 'lllyasviel/control_v11p_sd15_inpaint', + label: 'Inpaint', + defaultProcessor: 'none', + }, + 'lllyasviel/control_v11p_sd15_mlsd': { + type: 'lllyasviel/control_v11p_sd15_mlsd', + label: 'M-LSD', + defaultProcessor: 'mlsd_image_processor', + }, + 'lllyasviel/control_v11f1p_sd15_depth': { + type: 'lllyasviel/control_v11f1p_sd15_depth', + label: 'Depth', + defaultProcessor: 'midas_depth_image_processor', + }, + 'lllyasviel/control_v11p_sd15_normalbae': { + type: 'lllyasviel/control_v11p_sd15_normalbae', + label: 'Normal Map (BAE)', + defaultProcessor: 'normalbae_image_processor', + }, + 'lllyasviel/control_v11p_sd15_seg': { + type: 'lllyasviel/control_v11p_sd15_seg', + label: 'Segmentation', + defaultProcessor: 'none', + }, + 'lllyasviel/control_v11p_sd15_lineart': { + type: 'lllyasviel/control_v11p_sd15_lineart', + label: 'Lineart', + defaultProcessor: 'lineart_image_processor', + }, + 'lllyasviel/control_v11p_sd15s2_lineart_anime': { + type: 'lllyasviel/control_v11p_sd15s2_lineart_anime', + label: 'Lineart Anime', + defaultProcessor: 'lineart_anime_image_processor', + }, + 'lllyasviel/control_v11p_sd15_scribble': { + type: 'lllyasviel/control_v11p_sd15_scribble', + label: 'Scribble', + defaultProcessor: 'none', + }, + 'lllyasviel/control_v11p_sd15_softedge': { + type: 'lllyasviel/control_v11p_sd15_softedge', + label: 'Soft Edge', + defaultProcessor: 'hed_image_processor', + }, + 'lllyasviel/control_v11e_sd15_shuffle': { + type: 'lllyasviel/control_v11e_sd15_shuffle', + label: 'Content Shuffle', + defaultProcessor: 'content_shuffle_image_processor', + }, + 'lllyasviel/control_v11p_sd15_openpose': { + type: 'lllyasviel/control_v11p_sd15_openpose', + label: 'Openpose', + defaultProcessor: 'openpose_image_processor', + }, + 'lllyasviel/control_v11f1e_sd15_tile': { + type: 'lllyasviel/control_v11f1e_sd15_tile', + label: 'Tile (experimental)', + defaultProcessor: 'none', + }, + 'lllyasviel/control_v11e_sd15_ip2p': { + type: 'lllyasviel/control_v11e_sd15_ip2p', + label: 'Pix2Pix (experimental)', + defaultProcessor: 'none', + }, + 'CrucibleAI/ControlNetMediaPipeFace': { + type: 'CrucibleAI/ControlNetMediaPipeFace', + label: 'Mediapipe Face', + defaultProcessor: 'mediapipe_face_processor', + }, +}; + +export type ControlNetModelName = keyof typeof CONTROLNET_MODELS; diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetDenylist.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetDenylist.ts new file mode 100644 index 0000000000..eef6bfad85 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetDenylist.ts @@ -0,0 +1,8 @@ +import { ControlNetState } from './controlNetSlice'; + +/** + * ControlNet slice persist denylist + */ +export const controlNetDenylist: (keyof ControlNetState)[] = [ + 'pendingControlImages', +]; diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts new file mode 100644 index 0000000000..d1c69566e9 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -0,0 +1,334 @@ +import { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { ImageDTO } from 'services/api/types'; +import { + ControlNetProcessorType, + RequiredCannyImageProcessorInvocation, + RequiredControlNetProcessorNode, +} from './types'; +import { + CONTROLNET_MODELS, + CONTROLNET_PROCESSORS, + ControlNetModelName, +} from './constants'; +import { controlNetImageProcessed } from './actions'; +import { imageDeleted, imageUrlsReceived } from 'services/api/thunks/image'; +import { forEach } from 'lodash-es'; +import { isAnySessionRejected } from 'services/api/thunks/session'; +import { appSocketInvocationError } from 'services/events/actions'; + +export type ControlModes = + | 'balanced' + | 'more_prompt' + | 'more_control' + | 'unbalanced'; + +export const initialControlNet: Omit = { + isEnabled: true, + model: CONTROLNET_MODELS['lllyasviel/control_v11p_sd15_canny'].type, + weight: 1, + beginStepPct: 0, + endStepPct: 1, + controlMode: 'balanced', + controlImage: null, + processedControlImage: null, + processorType: 'canny_image_processor', + processorNode: CONTROLNET_PROCESSORS.canny_image_processor + .default as RequiredCannyImageProcessorInvocation, + shouldAutoConfig: true, +}; + +export type ControlNetConfig = { + controlNetId: string; + isEnabled: boolean; + model: ControlNetModelName; + weight: number; + beginStepPct: number; + endStepPct: number; + controlMode: ControlModes; + controlImage: string | null; + processedControlImage: string | null; + processorType: ControlNetProcessorType; + processorNode: RequiredControlNetProcessorNode; + shouldAutoConfig: boolean; +}; + +export type ControlNetState = { + controlNets: Record; + isEnabled: boolean; + pendingControlImages: string[]; +}; + +export const initialControlNetState: ControlNetState = { + controlNets: {}, + isEnabled: false, + pendingControlImages: [], +}; + +export const controlNetSlice = createSlice({ + name: 'controlNet', + initialState: initialControlNetState, + reducers: { + isControlNetEnabledToggled: (state) => { + state.isEnabled = !state.isEnabled; + }, + controlNetAdded: ( + state, + action: PayloadAction<{ + controlNetId: string; + controlNet?: ControlNetConfig; + }> + ) => { + const { controlNetId, controlNet } = action.payload; + state.controlNets[controlNetId] = { + ...(controlNet ?? initialControlNet), + controlNetId, + }; + }, + controlNetAddedFromImage: ( + state, + action: PayloadAction<{ controlNetId: string; controlImage: string }> + ) => { + const { controlNetId, controlImage } = action.payload; + state.controlNets[controlNetId] = { + ...initialControlNet, + controlNetId, + controlImage, + }; + }, + controlNetRemoved: ( + state, + action: PayloadAction<{ controlNetId: string }> + ) => { + const { controlNetId } = action.payload; + delete state.controlNets[controlNetId]; + }, + controlNetToggled: ( + state, + action: PayloadAction<{ controlNetId: string }> + ) => { + const { controlNetId } = action.payload; + state.controlNets[controlNetId].isEnabled = + !state.controlNets[controlNetId].isEnabled; + }, + controlNetImageChanged: ( + state, + action: PayloadAction<{ + controlNetId: string; + controlImage: string | null; + }> + ) => { + const { controlNetId, controlImage } = action.payload; + state.controlNets[controlNetId].controlImage = controlImage; + state.controlNets[controlNetId].processedControlImage = null; + if ( + controlImage !== null && + state.controlNets[controlNetId].processorType !== 'none' + ) { + state.pendingControlImages.push(controlNetId); + } + }, + controlNetProcessedImageChanged: ( + state, + action: PayloadAction<{ + controlNetId: string; + processedControlImage: string | null; + }> + ) => { + const { controlNetId, processedControlImage } = action.payload; + state.controlNets[controlNetId].processedControlImage = + processedControlImage; + state.pendingControlImages = state.pendingControlImages.filter( + (id) => id !== controlNetId + ); + }, + controlNetModelChanged: ( + state, + action: PayloadAction<{ + controlNetId: string; + model: ControlNetModelName; + }> + ) => { + const { controlNetId, model } = action.payload; + state.controlNets[controlNetId].model = model; + state.controlNets[controlNetId].processedControlImage = null; + + if (state.controlNets[controlNetId].shouldAutoConfig) { + const processorType = CONTROLNET_MODELS[model].defaultProcessor; + if (processorType) { + state.controlNets[controlNetId].processorType = processorType; + state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ + processorType + ].default as RequiredControlNetProcessorNode; + } else { + state.controlNets[controlNetId].processorType = 'none'; + state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS + .none.default as RequiredControlNetProcessorNode; + } + } + }, + controlNetWeightChanged: ( + state, + action: PayloadAction<{ controlNetId: string; weight: number }> + ) => { + const { controlNetId, weight } = action.payload; + state.controlNets[controlNetId].weight = weight; + }, + controlNetBeginStepPctChanged: ( + state, + action: PayloadAction<{ controlNetId: string; beginStepPct: number }> + ) => { + const { controlNetId, beginStepPct } = action.payload; + state.controlNets[controlNetId].beginStepPct = beginStepPct; + }, + controlNetEndStepPctChanged: ( + state, + action: PayloadAction<{ controlNetId: string; endStepPct: number }> + ) => { + const { controlNetId, endStepPct } = action.payload; + state.controlNets[controlNetId].endStepPct = endStepPct; + }, + controlNetControlModeChanged: ( + state, + action: PayloadAction<{ controlNetId: string; controlMode: ControlModes }> + ) => { + const { controlNetId, controlMode } = action.payload; + state.controlNets[controlNetId].controlMode = controlMode; + }, + controlNetProcessorParamsChanged: ( + state, + action: PayloadAction<{ + controlNetId: string; + changes: Omit< + Partial, + 'id' | 'type' | 'is_intermediate' + >; + }> + ) => { + const { controlNetId, changes } = action.payload; + const processorNode = state.controlNets[controlNetId].processorNode; + state.controlNets[controlNetId].processorNode = { + ...processorNode, + ...changes, + }; + state.controlNets[controlNetId].shouldAutoConfig = false; + }, + controlNetProcessorTypeChanged: ( + state, + action: PayloadAction<{ + controlNetId: string; + processorType: ControlNetProcessorType; + }> + ) => { + const { controlNetId, processorType } = action.payload; + state.controlNets[controlNetId].processedControlImage = null; + state.controlNets[controlNetId].processorType = processorType; + state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ + processorType + ].default as RequiredControlNetProcessorNode; + state.controlNets[controlNetId].shouldAutoConfig = false; + }, + controlNetAutoConfigToggled: ( + state, + action: PayloadAction<{ + controlNetId: string; + }> + ) => { + const { controlNetId } = action.payload; + const newShouldAutoConfig = + !state.controlNets[controlNetId].shouldAutoConfig; + + if (newShouldAutoConfig) { + // manage the processor for the user + const processorType = + CONTROLNET_MODELS[state.controlNets[controlNetId].model] + .defaultProcessor; + if (processorType) { + state.controlNets[controlNetId].processorType = processorType; + state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ + processorType + ].default as RequiredControlNetProcessorNode; + } else { + state.controlNets[controlNetId].processorType = 'none'; + state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS + .none.default as RequiredControlNetProcessorNode; + } + } + + state.controlNets[controlNetId].shouldAutoConfig = newShouldAutoConfig; + }, + controlNetReset: () => { + return { ...initialControlNetState }; + }, + }, + extraReducers: (builder) => { + builder.addCase(controlNetImageProcessed, (state, action) => { + if ( + state.controlNets[action.payload.controlNetId].controlImage !== null + ) { + state.pendingControlImages.push(action.payload.controlNetId); + } + }); + + builder.addCase(imageDeleted.pending, (state, action) => { + // Preemptively remove the image from the gallery + const { image_name } = action.meta.arg; + forEach(state.controlNets, (c) => { + if (c.controlImage === image_name) { + c.controlImage = null; + c.processedControlImage = null; + } + if (c.processedControlImage === image_name) { + c.processedControlImage = null; + } + }); + }); + + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; + + // forEach(state.controlNets, (c) => { + // if (c.controlImage?.image_name === image_name) { + // c.controlImage.image_url = image_url; + // c.controlImage.thumbnail_url = thumbnail_url; + // } + // if (c.processedControlImage?.image_name === image_name) { + // c.processedControlImage.image_url = image_url; + // c.processedControlImage.thumbnail_url = thumbnail_url; + // } + // }); + // }); + + builder.addCase(appSocketInvocationError, (state, action) => { + state.pendingControlImages = []; + }); + + builder.addMatcher(isAnySessionRejected, (state, action) => { + state.pendingControlImages = []; + }); + }, +}); + +export const { + isControlNetEnabledToggled, + controlNetAdded, + controlNetAddedFromImage, + controlNetRemoved, + controlNetImageChanged, + controlNetProcessedImageChanged, + controlNetToggled, + controlNetModelChanged, + controlNetWeightChanged, + controlNetBeginStepPctChanged, + controlNetEndStepPctChanged, + controlNetControlModeChanged, + controlNetProcessorParamsChanged, + controlNetProcessorTypeChanged, + controlNetReset, + controlNetAutoConfigToggled, +} = controlNetSlice.actions; + +export default controlNetSlice.reducer; + +export const controlNetSelector = (state: RootState) => state.controlNet; diff --git a/invokeai/frontend/web/src/features/controlNet/store/types.ts b/invokeai/frontend/web/src/features/controlNet/store/types.ts new file mode 100644 index 0000000000..2d028fd0bb --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/store/types.ts @@ -0,0 +1,329 @@ +import { isObject } from 'lodash-es'; +import { + CannyImageProcessorInvocation, + ContentShuffleImageProcessorInvocation, + HedImageProcessorInvocation, + LineartAnimeImageProcessorInvocation, + LineartImageProcessorInvocation, + MediapipeFaceProcessorInvocation, + MidasDepthImageProcessorInvocation, + MlsdImageProcessorInvocation, + NormalbaeImageProcessorInvocation, + OpenposeImageProcessorInvocation, + PidiImageProcessorInvocation, + ZoeDepthImageProcessorInvocation, +} from 'services/api/types'; +import { O } from 'ts-toolbelt'; + +/** + * Any ControlNet processor node + */ +export type ControlNetProcessorNode = + | CannyImageProcessorInvocation + | ContentShuffleImageProcessorInvocation + | HedImageProcessorInvocation + | LineartAnimeImageProcessorInvocation + | LineartImageProcessorInvocation + | MediapipeFaceProcessorInvocation + | MidasDepthImageProcessorInvocation + | MlsdImageProcessorInvocation + | NormalbaeImageProcessorInvocation + | OpenposeImageProcessorInvocation + | PidiImageProcessorInvocation + | ZoeDepthImageProcessorInvocation; + +/** + * Any ControlNet processor type + */ +export type ControlNetProcessorType = NonNullable< + ControlNetProcessorNode['type'] | 'none' +>; + +/** + * The Canny processor node, with parameters flagged as required + */ +export type RequiredCannyImageProcessorInvocation = O.Required< + CannyImageProcessorInvocation, + 'type' | 'low_threshold' | 'high_threshold' +>; + +/** + * The ContentShuffle processor node, with parameters flagged as required + */ +export type RequiredContentShuffleImageProcessorInvocation = O.Required< + ContentShuffleImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'w' | 'h' | 'f' +>; + +/** + * The HED processor node, with parameters flagged as required + */ +export type RequiredHedImageProcessorInvocation = O.Required< + HedImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'scribble' +>; + +/** + * The Lineart Anime processor node, with parameters flagged as required + */ +export type RequiredLineartAnimeImageProcessorInvocation = O.Required< + LineartAnimeImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' +>; + +/** + * The Lineart processor node, with parameters flagged as required + */ +export type RequiredLineartImageProcessorInvocation = O.Required< + LineartImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'coarse' +>; + +/** + * The MediapipeFace processor node, with parameters flagged as required + */ +export type RequiredMediapipeFaceProcessorInvocation = O.Required< + MediapipeFaceProcessorInvocation, + 'type' | 'max_faces' | 'min_confidence' +>; + +/** + * The MidasDepth processor node, with parameters flagged as required + */ +export type RequiredMidasDepthImageProcessorInvocation = O.Required< + MidasDepthImageProcessorInvocation, + 'type' | 'a_mult' | 'bg_th' +>; + +/** + * The MLSD processor node, with parameters flagged as required + */ +export type RequiredMlsdImageProcessorInvocation = O.Required< + MlsdImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'thr_v' | 'thr_d' +>; + +/** + * The NormalBae processor node, with parameters flagged as required + */ +export type RequiredNormalbaeImageProcessorInvocation = O.Required< + NormalbaeImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' +>; + +/** + * The Openpose processor node, with parameters flagged as required + */ +export type RequiredOpenposeImageProcessorInvocation = O.Required< + OpenposeImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'hand_and_face' +>; + +/** + * The Pidi processor node, with parameters flagged as required + */ +export type RequiredPidiImageProcessorInvocation = O.Required< + PidiImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'safe' | 'scribble' +>; + +/** + * The ZoeDepth processor node, with parameters flagged as required + */ +export type RequiredZoeDepthImageProcessorInvocation = O.Required< + ZoeDepthImageProcessorInvocation, + 'type' +>; + +/** + * Any ControlNet Processor node, with its parameters flagged as required + */ +export type RequiredControlNetProcessorNode = + | RequiredCannyImageProcessorInvocation + | RequiredContentShuffleImageProcessorInvocation + | RequiredHedImageProcessorInvocation + | RequiredLineartAnimeImageProcessorInvocation + | RequiredLineartImageProcessorInvocation + | RequiredMediapipeFaceProcessorInvocation + | RequiredMidasDepthImageProcessorInvocation + | RequiredMlsdImageProcessorInvocation + | RequiredNormalbaeImageProcessorInvocation + | RequiredOpenposeImageProcessorInvocation + | RequiredPidiImageProcessorInvocation + | RequiredZoeDepthImageProcessorInvocation; + +/** + * Type guard for CannyImageProcessorInvocation + */ +export const isCannyImageProcessorInvocation = ( + obj: unknown +): obj is CannyImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'canny_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for ContentShuffleImageProcessorInvocation + */ +export const isContentShuffleImageProcessorInvocation = ( + obj: unknown +): obj is ContentShuffleImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'content_shuffle_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for HedImageprocessorInvocation + */ +export const isHedImageprocessorInvocation = ( + obj: unknown +): obj is HedImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'hed_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for LineartAnimeImageProcessorInvocation + */ +export const isLineartAnimeImageProcessorInvocation = ( + obj: unknown +): obj is LineartAnimeImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'lineart_anime_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for LineartImageProcessorInvocation + */ +export const isLineartImageProcessorInvocation = ( + obj: unknown +): obj is LineartImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'lineart_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for MediapipeFaceProcessorInvocation + */ +export const isMediapipeFaceProcessorInvocation = ( + obj: unknown +): obj is MediapipeFaceProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'mediapipe_face_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for MidasDepthImageProcessorInvocation + */ +export const isMidasDepthImageProcessorInvocation = ( + obj: unknown +): obj is MidasDepthImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'midas_depth_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for MlsdImageProcessorInvocation + */ +export const isMlsdImageProcessorInvocation = ( + obj: unknown +): obj is MlsdImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'mlsd_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for NormalbaeImageProcessorInvocation + */ +export const isNormalbaeImageProcessorInvocation = ( + obj: unknown +): obj is NormalbaeImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'normalbae_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for OpenposeImageProcessorInvocation + */ +export const isOpenposeImageProcessorInvocation = ( + obj: unknown +): obj is OpenposeImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'openpose_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for PidiImageProcessorInvocation + */ +export const isPidiImageProcessorInvocation = ( + obj: unknown +): obj is PidiImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'pidi_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for ZoeDepthImageProcessorInvocation + */ +export const isZoeDepthImageProcessorInvocation = ( + obj: unknown +): obj is ZoeDepthImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'zoe_depth_image_processor' + ) { + return true; + } + return false; +}; diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx new file mode 100644 index 0000000000..1aefecf3e6 --- /dev/null +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx @@ -0,0 +1,45 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAICollapse from 'common/components/IAICollapse'; +import { useCallback } from 'react'; +import { isEnabledToggled } from '../store/slice'; +import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts'; +import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial'; +import { Flex } from '@chakra-ui/react'; + +const selector = createSelector( + stateSelector, + (state) => { + const { isEnabled } = state.dynamicPrompts; + + return { isEnabled }; + }, + defaultSelectorOptions +); + +const ParamDynamicPromptsCollapse = () => { + const dispatch = useAppDispatch(); + const { isEnabled } = useAppSelector(selector); + + const handleToggleIsEnabled = useCallback(() => { + dispatch(isEnabledToggled()); + }, [dispatch]); + + return ( + + + + + + + ); +}; + +export default ParamDynamicPromptsCollapse; diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx new file mode 100644 index 0000000000..30c2240c37 --- /dev/null +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx @@ -0,0 +1,36 @@ +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { combinatorialToggled } from '../store/slice'; +import { createSelector } from '@reduxjs/toolkit'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { useCallback } from 'react'; +import { stateSelector } from 'app/store/store'; +import IAISwitch from 'common/components/IAISwitch'; + +const selector = createSelector( + stateSelector, + (state) => { + const { combinatorial } = state.dynamicPrompts; + + return { combinatorial }; + }, + defaultSelectorOptions +); + +const ParamDynamicPromptsCombinatorial = () => { + const { combinatorial } = useAppSelector(selector); + const dispatch = useAppDispatch(); + + const handleChange = useCallback(() => { + dispatch(combinatorialToggled()); + }, [dispatch]); + + return ( + + ); +}; + +export default ParamDynamicPromptsCombinatorial; diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx new file mode 100644 index 0000000000..19f02ae3e5 --- /dev/null +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx @@ -0,0 +1,55 @@ +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { maxPromptsChanged, maxPromptsReset } from '../store/slice'; +import { createSelector } from '@reduxjs/toolkit'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { useCallback } from 'react'; +import { stateSelector } from 'app/store/store'; + +const selector = createSelector( + stateSelector, + (state) => { + const { maxPrompts, combinatorial } = state.dynamicPrompts; + const { min, sliderMax, inputMax } = + state.config.sd.dynamicPrompts.maxPrompts; + + return { maxPrompts, min, sliderMax, inputMax, combinatorial }; + }, + defaultSelectorOptions +); + +const ParamDynamicPromptsMaxPrompts = () => { + const { maxPrompts, min, sliderMax, inputMax, combinatorial } = + useAppSelector(selector); + const dispatch = useAppDispatch(); + + const handleChange = useCallback( + (v: number) => { + dispatch(maxPromptsChanged(v)); + }, + [dispatch] + ); + + const handleReset = useCallback(() => { + dispatch(maxPromptsReset()); + }, [dispatch]); + + return ( + + ); +}; + +export default ParamDynamicPromptsMaxPrompts; diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/store/selectors.ts b/invokeai/frontend/web/src/features/dynamicPrompts/store/selectors.ts new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/invokeai/frontend/web/src/features/dynamicPrompts/store/selectors.ts @@ -0,0 +1 @@ +// diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/store/slice.ts b/invokeai/frontend/web/src/features/dynamicPrompts/store/slice.ts new file mode 100644 index 0000000000..8c33feb20c --- /dev/null +++ b/invokeai/frontend/web/src/features/dynamicPrompts/store/slice.ts @@ -0,0 +1,50 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; + +export interface DynamicPromptsState { + isEnabled: boolean; + maxPrompts: number; + combinatorial: boolean; +} + +export const initialDynamicPromptsState: DynamicPromptsState = { + isEnabled: false, + maxPrompts: 100, + combinatorial: true, +}; + +const initialState: DynamicPromptsState = initialDynamicPromptsState; + +export const dynamicPromptsSlice = createSlice({ + name: 'dynamicPrompts', + initialState, + reducers: { + maxPromptsChanged: (state, action: PayloadAction) => { + state.maxPrompts = action.payload; + }, + maxPromptsReset: (state) => { + state.maxPrompts = initialDynamicPromptsState.maxPrompts; + }, + combinatorialToggled: (state) => { + state.combinatorial = !state.combinatorial; + }, + isEnabledToggled: (state) => { + state.isEnabled = !state.isEnabled; + }, + }, + extraReducers: (builder) => { + // + }, +}); + +export const { + isEnabledToggled, + maxPromptsChanged, + maxPromptsReset, + combinatorialToggled, +} = dynamicPromptsSlice.actions; + +export default dynamicPromptsSlice.reducer; + +export const dynamicPromptsSelector = (state: RootState) => + state.dynamicPrompts; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx new file mode 100644 index 0000000000..a08fdec07f --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -0,0 +1,27 @@ +import IAIButton from 'common/components/IAIButton'; +import { useCallback } from 'react'; +import { useCreateBoardMutation } from 'services/api/endpoints/boards'; + +const DEFAULT_BOARD_NAME = 'My Board'; + +const AddBoardButton = () => { + const [createBoard, { isLoading }] = useCreateBoardMutation(); + + const handleCreateBoard = useCallback(() => { + createBoard(DEFAULT_BOARD_NAME); + }, [createBoard]); + + return ( + + Add Board + + ); +}; + +export default AddBoardButton; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx new file mode 100644 index 0000000000..858329ead6 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx @@ -0,0 +1,97 @@ +import { Flex, Text, useColorMode } from '@chakra-ui/react'; +import { FaImages } from 'react-icons/fa'; +import { boardIdSelected } from '../../store/boardSlice'; +import { useDispatch } from 'react-redux'; +import { IAINoImageFallback } from 'common/components/IAIImageFallback'; +import { AnimatePresence } from 'framer-motion'; +import { SelectedItemOverlay } from '../SelectedItemOverlay'; +import { useCallback } from 'react'; +import { ImageDTO } from 'services/api/types'; +import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages'; +import { useDroppable } from '@dnd-kit/core'; +import IAIDropOverlay from 'common/components/IAIDropOverlay'; +import { mode } from 'theme/util/mode'; + +const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { + const dispatch = useDispatch(); + const { colorMode } = useColorMode(); + + const handleAllImagesBoardClick = () => { + dispatch(boardIdSelected()); + }; + + const [removeImageFromBoard, { isLoading }] = + useRemoveImageFromBoardMutation(); + + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (!droppedImage.board_id) { + return; + } + removeImageFromBoard({ + board_id: droppedImage.board_id, + image_name: droppedImage.image_name, + }); + }, + [removeImageFromBoard] + ); + + const { + isOver, + setNodeRef, + active: isDropActive, + } = useDroppable({ + id: `board_droppable_all_images`, + data: { + handleDrop, + }, + }); + + return ( + + + + + {isSelected && } + + + {isDropActive && } + + + + All Images + + + ); +}; + +export default AllImagesBoard; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx new file mode 100644 index 0000000000..fb095b9f42 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -0,0 +1,134 @@ +import { + Collapse, + Flex, + Grid, + IconButton, + Input, + InputGroup, + InputRightElement, +} from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { + boardsSelector, + setBoardSearchText, +} from 'features/gallery/store/boardSlice'; +import { memo, useState } from 'react'; +import HoverableBoard from './HoverableBoard'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import AddBoardButton from './AddBoardButton'; +import AllImagesBoard from './AllImagesBoard'; +import { CloseIcon } from '@chakra-ui/icons'; +import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; + +const selector = createSelector( + [boardsSelector], + (boardsState) => { + const { selectedBoardId, searchText } = boardsState; + return { selectedBoardId, searchText }; + }, + defaultSelectorOptions +); + +type Props = { + isOpen: boolean; +}; + +const BoardsList = (props: Props) => { + const { isOpen } = props; + const dispatch = useAppDispatch(); + const { selectedBoardId, searchText } = useAppSelector(selector); + + const { data: boards } = useListAllBoardsQuery(); + + const filteredBoards = searchText + ? boards?.filter((board) => + board.board_name.toLowerCase().includes(searchText.toLowerCase()) + ) + : boards; + + const [searchMode, setSearchMode] = useState(false); + + const handleBoardSearch = (searchTerm: string) => { + setSearchMode(searchTerm.length > 0); + dispatch(setBoardSearchText(searchTerm)); + }; + const clearBoardSearch = () => { + setSearchMode(false); + dispatch(setBoardSearchText('')); + }; + + return ( + + + + + { + handleBoardSearch(e.target.value); + }} + /> + {searchText && searchText.length && ( + + } + /> + + )} + + + + + + {!searchMode && } + {filteredBoards && + filteredBoards.map((board) => ( + + ))} + + + + + ); +}; + +export default memo(BoardsList); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardImagesModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardImagesModal.tsx new file mode 100644 index 0000000000..736d72f862 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardImagesModal.tsx @@ -0,0 +1,114 @@ +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Divider, + Flex, + ListItem, + Text, + UnorderedList, +} from '@chakra-ui/react'; +import IAIButton from 'common/components/IAIButton'; +import { memo, useContext, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext'; +import { some } from 'lodash-es'; +import { ImageUsage } from '../../../../app/contexts/DeleteImageContext'; + +const BoardImageInUseMessage = (props: { imagesUsage?: ImageUsage }) => { + const { imagesUsage } = props; + + if (!imagesUsage) { + return null; + } + + if (!some(imagesUsage)) { + return null; + } + + return ( + <> + + An image from this board is currently in use in the following features: + + + {imagesUsage.isInitialImage && Image to Image} + {imagesUsage.isCanvasImage && Unified Canvas} + {imagesUsage.isControlNetImage && ControlNet} + {imagesUsage.isNodesImage && Node Editor} + + + If you delete images from this board, those features will immediately be + reset. + + + ); +}; + +const DeleteBoardImagesModal = () => { + const { t } = useTranslation(); + + const { + isOpen, + onClose, + board, + handleDeleteBoardImages, + handleDeleteBoardOnly, + imagesUsage, + } = useContext(DeleteBoardImagesContext); + + const cancelRef = useRef(null); + + return ( + + + {board && ( + + + Delete Board + + + + + + + {t('common.areYouSure')} + + This board has {board.image_count} image(s) that will be + deleted. + + + + + + Cancel + + handleDeleteBoardOnly(board.board_id)} + > + Delete Board Only + + handleDeleteBoardImages(board.board_id)} + > + Delete Board and Images + + + + )} + + + ); +}; + +export default memo(DeleteBoardImagesModal); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx new file mode 100644 index 0000000000..118484f305 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -0,0 +1,217 @@ +import { + Badge, + Box, + Editable, + EditableInput, + EditablePreview, + Flex, + Image, + MenuItem, + MenuList, + useColorMode, +} from '@chakra-ui/react'; + +import { useAppDispatch } from 'app/store/storeHooks'; +import { memo, useCallback, useContext } from 'react'; +import { FaFolder, FaTrash } from 'react-icons/fa'; +import { ContextMenu } from 'chakra-ui-contextmenu'; +import { BoardDTO, ImageDTO } from 'services/api/types'; +import { IAINoImageFallback } from 'common/components/IAIImageFallback'; +import { boardIdSelected } from 'features/gallery/store/boardSlice'; +import { useAddImageToBoardMutation } from 'services/api/endpoints/boardImages'; +import { + useDeleteBoardMutation, + useUpdateBoardMutation, +} from 'services/api/endpoints/boards'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; + +import { skipToken } from '@reduxjs/toolkit/dist/query'; +import { useDroppable } from '@dnd-kit/core'; +import { AnimatePresence } from 'framer-motion'; +import IAIDropOverlay from 'common/components/IAIDropOverlay'; +import { SelectedItemOverlay } from '../SelectedItemOverlay'; +import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext'; +import { mode } from 'theme/util/mode'; + +interface HoverableBoardProps { + board: BoardDTO; + isSelected: boolean; +} + +const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { + const dispatch = useAppDispatch(); + + const { currentData: coverImage } = useGetImageDTOQuery( + board.cover_image_name ?? skipToken + ); + + const { colorMode } = useColorMode(); + + const { board_name, board_id } = board; + + const { onClickDeleteBoardImages } = useContext(DeleteBoardImagesContext); + + const handleSelectBoard = useCallback(() => { + dispatch(boardIdSelected(board_id)); + }, [board_id, dispatch]); + + const [updateBoard, { isLoading: isUpdateBoardLoading }] = + useUpdateBoardMutation(); + + const [deleteBoard, { isLoading: isDeleteBoardLoading }] = + useDeleteBoardMutation(); + + const [addImageToBoard, { isLoading: isAddImageToBoardLoading }] = + useAddImageToBoardMutation(); + + const handleUpdateBoardName = (newBoardName: string) => { + updateBoard({ board_id, changes: { board_name: newBoardName } }); + }; + + const handleDeleteBoard = useCallback(() => { + deleteBoard(board_id); + }, [board_id, deleteBoard]); + + const handleDeleteBoardAndImages = useCallback(() => { + console.log({ board }); + onClickDeleteBoardImages(board); + }, [board, onClickDeleteBoardImages]); + + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (droppedImage.board_id === board_id) { + return; + } + addImageToBoard({ board_id, image_name: droppedImage.image_name }); + }, + [addImageToBoard, board_id] + ); + + const { + isOver, + setNodeRef, + active: isDropActive, + } = useDroppable({ + id: `board_droppable_${board_id}`, + data: { + handleDrop, + }, + }); + + return ( + + + menuProps={{ size: 'sm', isLazy: true }} + renderMenu={() => ( + + {board.image_count > 0 && ( + } + onClickCapture={handleDeleteBoardAndImages} + > + Delete Board and Images + + )} + } + onClickCapture={handleDeleteBoard} + > + Delete Board + + + )} + > + {(ref) => ( + + + {board.cover_image_name && coverImage?.image_url && ( + + )} + {!(board.cover_image_name && coverImage?.image_url) && ( + + )} + + {board.image_count} + + + {isSelected && } + + + {isDropActive && } + + + + + { + handleUpdateBoardName(nextValue); + }} + > + + + + + + )} + + + ); +}); + +HoverableBoard.displayName = 'HoverableBoard'; + +export default HoverableBoard; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx new file mode 100644 index 0000000000..a98e8dca34 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx @@ -0,0 +1,93 @@ +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Box, + Flex, + Spinner, + Text, +} from '@chakra-ui/react'; +import IAIButton from 'common/components/IAIButton'; + +import { memo, useContext, useRef, useState } from 'react'; +import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; + +const UpdateImageBoardModal = () => { + // const boards = useSelector(selectBoardsAll); + const { data: boards, isFetching } = useListAllBoardsQuery(); + const { isOpen, onClose, handleAddToBoard, image } = useContext( + AddImageToBoardContext + ); + const [selectedBoard, setSelectedBoard] = useState(); + + const cancelRef = useRef(null); + + const currentBoard = boards?.find( + (board) => board.board_id === image?.board_id + ); + + return ( + + + + + {currentBoard ? 'Move Image to Board' : 'Add Image to Board'} + + + + + + {currentBoard && ( + + Moving this image from{' '} + {currentBoard.board_name} to + + )} + {isFetching ? ( + + ) : ( + setSelectedBoard(v)} + value={selectedBoard} + data={(boards ?? []).map((board) => ({ + label: board.board_name, + value: board.board_id, + }))} + /> + )} + + + + + Cancel + { + if (selectedBoard) { + handleAddToBoard(selectedBoard); + } + }} + ml={3} + > + {currentBoard ? 'Move' : 'Add'} + + + + + + ); +}; + +export default memo(UpdateImageBoardModal); diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index c19a404a37..169a965be0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -1,13 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; -import { isEqual, isString } from 'lodash-es'; +import { isEqual } from 'lodash-es'; -import { - ButtonGroup, - Flex, - FlexProps, - Link, - useDisclosure, -} from '@chakra-ui/react'; +import { ButtonGroup, Flex, FlexProps, Link } from '@chakra-ui/react'; // import { runESRGAN, runFacetool } from 'app/socketio/actions'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; @@ -25,8 +19,8 @@ import { } from 'features/ui/store/uiSelectors'; import { setActiveTab, - setShouldHidePreview, setShouldShowImageDetails, + setShouldShowProgressInViewer, } from 'features/ui/store/uiSlice'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -37,38 +31,32 @@ import { FaDownload, FaExpand, FaExpandArrowsAlt, - FaEye, - FaEyeSlash, FaGrinStars, + FaHourglassHalf, FaQuoteRight, FaSeedling, FaShare, FaShareAlt, - FaTrash, - FaWrench, } from 'react-icons/fa'; import { gallerySelector } from '../store/gallerySelectors'; -import DeleteImageModal from './DeleteImageModal'; -import { useCallback } from 'react'; +import { useCallback, useContext } from 'react'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import { useGetUrl } from 'common/util/getUrl'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { useParameters } from 'features/parameters/hooks/useParameters'; +import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { initialImageSelected } from 'features/parameters/store/actions'; -import { - requestedImageDeletion, - sentImageToCanvas, - sentImageToImg2Img, -} from '../store/actions'; +import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions'; import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings'; import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings'; -import { allParametersSet } from 'features/parameters/store/generationSlice'; -import DeleteImageButton from './ImageActionButtons/DeleteImageButton'; import { useAppToaster } from 'app/components/Toaster'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; +import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; +import { DeleteImageButton } from './DeleteImageModal'; +import { selectImagesById } from '../store/imagesSlice'; +import { RootState } from 'app/store/store'; const currentImageButtonsSelector = createSelector( [ + (state: RootState) => state, systemSelector, gallerySelector, postprocessingSelector, @@ -76,7 +64,7 @@ const currentImageButtonsSelector = createSelector( lightboxSelector, activeTabNameSelector, ], - (system, gallery, postprocessing, ui, lightbox, activeTabName) => { + (state, system, gallery, postprocessing, ui, lightbox, activeTabName) => { const { isProcessing, isConnected, @@ -90,7 +78,13 @@ const currentImageButtonsSelector = createSelector( const { isLightboxOpen } = lightbox; - const { shouldShowImageDetails, shouldHidePreview } = ui; + const { + shouldShowImageDetails, + shouldHidePreview, + shouldShowProgressInViewer, + } = ui; + + const imageDTO = selectImagesById(state, gallery.selectedImage ?? ''); const { selectedImage } = gallery; @@ -108,10 +102,11 @@ const currentImageButtonsSelector = createSelector( activeTabName, isLightboxOpen, shouldHidePreview, - image: selectedImage, - seed: selectedImage?.metadata?.seed, - prompt: selectedImage?.metadata?.positive_conditioning, - negativePrompt: selectedImage?.metadata?.negative_conditioning, + image: imageDTO, + seed: imageDTO?.metadata?.seed, + prompt: imageDTO?.metadata?.positive_conditioning, + negativePrompt: imageDTO?.metadata?.negative_conditioning, + shouldShowProgressInViewer, }; }, { @@ -123,10 +118,6 @@ const currentImageButtonsSelector = createSelector( type CurrentImageButtonsProps = FlexProps; -/** - * Row of buttons for common actions: - * Use as init image, use all params, use seed, upscale, fix faces, details, delete. - */ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { const dispatch = useAppDispatch(); const { @@ -138,13 +129,11 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { facetoolStrength, shouldDisableToolbarButtons, shouldShowImageDetails, - // currentImage, isLightboxOpen, activeTabName, shouldHidePreview, image, - canDeleteImage, - shouldConfirmOnDelete, + shouldShowProgressInViewer, } = useAppSelector(currentImageButtonsSelector); const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled; @@ -152,18 +141,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled; const isFaceRestoreEnabled = useFeatureStatus('faceRestore').isFeatureEnabled; - const { getUrl, shouldTransformUrls } = useGetUrl(); - - const { - isOpen: isDeleteDialogOpen, - onOpen: onDeleteDialogOpen, - onClose: onDeleteDialogClose, - } = useDisclosure(); - const toaster = useAppToaster(); const { t } = useTranslation(); - const { recallPrompt, recallSeed, recallAllParameters } = useParameters(); + const { recallBothPrompts, recallSeed, recallAllParameters } = + useRecallParameters(); + + const { onDelete } = useContext(DeleteImageContext); // const handleCopyImage = useCallback(async () => { // if (!image?.url) { @@ -195,10 +179,6 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { return; } - if (shouldTransformUrls) { - return getUrl(image.image_url); - } - if (image.image_url.startsWith('http')) { return image.image_url; } @@ -227,11 +207,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { isClosable: true, }); }); - }, [toaster, shouldTransformUrls, getUrl, t, image]); - - const handlePreviewVisibility = useCallback(() => { - dispatch(setShouldHidePreview(!shouldHidePreview)); - }, [dispatch, shouldHidePreview]); + }, [toaster, t, image]); const handleClickUseAllParameters = useCallback(() => { recallAllParameters(image); @@ -252,11 +228,11 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { useHotkeys('s', handleUseSeed, [image]); const handleUsePrompt = useCallback(() => { - recallPrompt( + recallBothPrompts( image?.metadata?.positive_conditioning, image?.metadata?.negative_conditioning ); - }, [image, recallPrompt]); + }, [image, recallBothPrompts]); useHotkeys('p', handleUsePrompt, [image]); @@ -271,6 +247,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { // selectedImage && dispatch(runESRGAN(selectedImage)); }, []); + const handleDelete = useCallback(() => { + onDelete(image); + }, [image, onDelete]); + useHotkeys( 'Shift+U', () => { @@ -372,26 +352,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { [image, shouldShowImageDetails, toaster] ); - const handleDelete = useCallback(() => { - if (canDeleteImage && image) { - dispatch(requestedImageDeletion(image)); - } - }, [image, canDeleteImage, dispatch]); - - const handleInitiateDelete = useCallback(() => { - if (shouldConfirmOnDelete) { - onDeleteDialogOpen(); - } else { - handleDelete(); - } - }, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]); - - useHotkeys('delete', handleInitiateDelete, [ - image, - shouldConfirmOnDelete, - isConnected, - isProcessing, - ]); + const handleClickProgressImagesToggle = useCallback(() => { + dispatch(setShouldShowProgressInViewer(!shouldShowProgressInViewer)); + }, [dispatch, shouldShowProgressInViewer]); const handleLightBox = useCallback(() => { dispatch(setIsLightboxOpen(!isLightboxOpen)); @@ -412,8 +375,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { } /> } @@ -458,28 +422,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { {t('parameters.copyImageToLink')} - + } size="sm" w="100%"> {t('parameters.downloadImage')} - {/* : } - tooltip={ - !shouldHidePreview - ? t('parameters.hidePreview') - : t('parameters.showPreview') - } - aria-label={ - !shouldHidePreview - ? t('parameters.hidePreview') - : t('parameters.showPreview') - } - isChecked={shouldHidePreview} - onClick={handlePreviewVisibility} - /> */} {isLightboxEnabled && ( } @@ -605,7 +554,17 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { - + } + isChecked={shouldShowProgressInViewer} + onClick={handleClickProgressImagesToggle} + /> + + + + diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx index 5810c599c1..2da5185fe5 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx @@ -1,13 +1,12 @@ -import { Flex, Icon } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { systemSelector } from 'features/system/store/systemSelectors'; -import { isEqual } from 'lodash-es'; import { gallerySelector } from '../store/gallerySelectors'; import CurrentImageButtons from './CurrentImageButtons'; import CurrentImagePreview from './CurrentImagePreview'; -import { FaImage } from 'react-icons/fa'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; export const currentImageDisplaySelector = createSelector( [systemSelector, gallerySelector], @@ -15,21 +14,15 @@ export const currentImageDisplaySelector = createSelector( const { progressImage } = system; return { - hasAnImageToDisplay: gallery.selectedImage || progressImage, + hasSelectedImage: Boolean(gallery.selectedImage), + hasProgressImage: Boolean(progressImage), }; }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } + defaultSelectorOptions ); -/** - * Displays the current image if there is one, plus associated actions. - */ const CurrentImageDisplay = () => { - const { hasAnImageToDisplay } = useAppSelector(currentImageDisplaySelector); + const { hasSelectedImage } = useAppSelector(currentImageDisplaySelector); return ( { height: '100%', width: '100%', rowGap: 4, - borderRadius: 'base', alignItems: 'center', justifyContent: 'center', }} > - - {hasAnImageToDisplay ? ( - <> - - - - ) : ( - - )} - + {hasSelectedImage && } + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 4562e3458d..fac19b347e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -1,20 +1,20 @@ import { Box, Flex, Image } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { useGetUrl } from 'common/util/getUrl'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; import { gallerySelector } from '../store/gallerySelectors'; import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import NextPrevImageButtons from './NextPrevImageButtons'; -import { DragEvent, memo, useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { systemSelector } from 'features/system/store/systemSelectors'; -import ImageFallbackSpinner from './ImageFallbackSpinner'; -import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; -import { configSelector } from '../../system/store/configSelectors'; -import { useAppToaster } from 'app/components/Toaster'; import { imageSelected } from '../store/gallerySlice'; +import IAIDndImage from 'common/components/IAIDndImage'; +import { ImageDTO } from 'services/api/types'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; export const imagesSelector = createSelector( [uiSelector, gallerySelector, systemSelector], @@ -29,7 +29,7 @@ export const imagesSelector = createSelector( return { shouldShowImageDetails, shouldHidePreview, - image: selectedImage, + selectedImage, progressImage, shouldShowProgressInViewer, shouldAntialiasProgressImage, @@ -45,48 +45,39 @@ export const imagesSelector = createSelector( const CurrentImagePreview = () => { const { shouldShowImageDetails, - image, - shouldHidePreview, + selectedImage, progressImage, shouldShowProgressInViewer, shouldAntialiasProgressImage, } = useAppSelector(imagesSelector); - const { shouldFetchImages } = useAppSelector(configSelector); - const { getUrl } = useGetUrl(); - const toaster = useAppToaster(); + + const { + currentData: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(selectedImage ?? skipToken); + const dispatch = useAppDispatch(); - const handleDragStart = useCallback( - (e: DragEvent) => { - if (!image) { + const handleDrop = useCallback( + (droppedImage: ImageDTO) => { + if (droppedImage.image_name === image?.image_name) { return; } - e.dataTransfer.setData('invokeai/imageName', image.image_name); - e.dataTransfer.setData('invokeai/imageType', image.image_type); - e.dataTransfer.effectAllowed = 'move'; + dispatch(imageSelected(droppedImage.image_name)); }, - [image] + [dispatch, image?.image_name] ); - const handleError = useCallback(() => { - dispatch(imageSelected()); - if (shouldFetchImages) { - toaster({ - title: 'Something went wrong, please refresh', - status: 'error', - isClosable: true, - }); - } - }, [dispatch, toaster, shouldFetchImages]); - return ( {progressImage && shouldShowProgressInViewer ? ( @@ -94,10 +85,11 @@ const CurrentImagePreview = () => { src={progressImage.dataURL} width={progressImage.width} height={progressImage.height} + draggable={false} sx={{ objectFit: 'contain', - maxWidth: '100%', - maxHeight: '100%', + maxWidth: 'full', + maxHeight: 'full', height: 'auto', position: 'absolute', borderRadius: 'base', @@ -105,34 +97,21 @@ const CurrentImagePreview = () => { }} /> ) : ( - image && ( - <> - } - onDragStart={handleDragStart} - sx={{ - objectFit: 'contain', - maxWidth: '100%', - maxHeight: '100%', - height: 'auto', - position: 'absolute', - borderRadius: 'base', - }} - onError={handleError} - /> - - - ) + } + isUploadDisabled={true} + fitContainer + /> )} - {shouldShowImageDetails && image && 'metadata' in image && ( + {shouldShowImageDetails && image && ( { )} - {!shouldShowImageDetails && } + {!shouldShowImageDetails && image && ( + + + + )} ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx b/invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx index 12038f4179..0ce7bb3666 100644 --- a/invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx @@ -5,51 +5,81 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, + Divider, Flex, + ListItem, Text, + UnorderedList, } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; +import { + DeleteImageContext, + ImageUsage, +} from 'app/contexts/DeleteImageContext'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIButton from 'common/components/IAIButton'; +import IAIIconButton from 'common/components/IAIIconButton'; import IAISwitch from 'common/components/IAISwitch'; import { configSelector } from 'features/system/store/configSelectors'; import { systemSelector } from 'features/system/store/systemSelectors'; import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice'; -import { isEqual } from 'lodash-es'; +import { some } from 'lodash-es'; -import { ChangeEvent, memo, useCallback, useRef } from 'react'; +import { ChangeEvent, memo, useCallback, useContext, useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { FaTrash } from 'react-icons/fa'; const selector = createSelector( [systemSelector, configSelector], (system, config) => { const { shouldConfirmOnDelete } = system; const { canRestoreDeletedImagesFromBin } = config; - return { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin }; + + return { + shouldConfirmOnDelete, + canRestoreDeletedImagesFromBin, + }; }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } + defaultSelectorOptions ); -interface DeleteImageModalProps { - isOpen: boolean; - onClose: () => void; - handleDelete: () => void; -} +const ImageInUseMessage = (props: { imageUsage?: ImageUsage }) => { + const { imageUsage } = props; -const DeleteImageModal = ({ - isOpen, - onClose, - handleDelete, -}: DeleteImageModalProps) => { + if (!imageUsage) { + return null; + } + + if (!some(imageUsage)) { + return null; + } + + return ( + <> + This image is currently in use in the following features: + + {imageUsage.isInitialImage && Image to Image} + {imageUsage.isCanvasImage && Unified Canvas} + {imageUsage.isControlNetImage && ControlNet} + {imageUsage.isNodesImage && Node Editor} + + + If you delete this image, those features will immediately be reset. + + + ); +}; + +const DeleteImageModal = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); + + const { isOpen, onClose, onImmediatelyDelete, image, imageUsage } = + useContext(DeleteImageContext); + const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } = useAppSelector(selector); - const cancelRef = useRef(null); const handleChangeShouldConfirmOnDelete = useCallback( (e: ChangeEvent) => @@ -57,10 +87,7 @@ const DeleteImageModal = ({ [dispatch] ); - const handleClickDelete = useCallback(() => { - handleDelete(); - onClose(); - }, [handleDelete, onClose]); + const cancelRef = useRef(null); return ( - - - {t('common.areYouSure')} - - {canRestoreDeletedImagesFromBin - ? t('gallery.deleteImageBin') - : t('gallery.deleteImagePermanent')} - - + + + + + {canRestoreDeletedImagesFromBin + ? t('gallery.deleteImageBin') + : t('gallery.deleteImagePermanent')} + + {t('common.areYouSure')} Cancel - + Delete @@ -107,3 +134,33 @@ const DeleteImageModal = ({ }; export default memo(DeleteImageModal); + +const deleteImageButtonsSelector = createSelector( + [systemSelector], + (system) => { + const { isProcessing, isConnected } = system; + + return isConnected && !isProcessing; + } +); + +type DeleteImageButtonProps = { + onClick: () => void; +}; + +export const DeleteImageButton = (props: DeleteImageButtonProps) => { + const { onClick } = props; + const { t } = useTranslation(); + const canDeleteImage = useAppSelector(deleteImageButtonsSelector); + + return ( + } + tooltip={`${t('gallery.deleteImage')} (Del)`} + aria-label={`${t('gallery.deleteImage')} (Del)`} + isDisabled={!canDeleteImage} + colorScheme="error" + /> + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx index f4bfee3ae1..9ab8ccb5c9 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx @@ -120,7 +120,7 @@ const GalleryDrawer = () => { isResizable={true} isOpen={shouldShowGallery} onClose={handleCloseGallery} - minWidth={200} + minWidth={337} > diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index ed427f4984..91648d8df0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -1,17 +1,15 @@ -import { - Box, - Flex, - Icon, - Image, - MenuItem, - MenuList, - useDisclosure, -} from '@chakra-ui/react'; +import { Box, Flex, Icon, Image, MenuItem, MenuList } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { imageSelected } from 'features/gallery/store/gallerySlice'; -import { DragEvent, MouseEvent, memo, useCallback, useState } from 'react'; -import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa'; -import DeleteImageModal from './DeleteImageModal'; +import { memo, useCallback, useContext, useState } from 'react'; +import { + FaCheck, + FaExpand, + FaFolder, + FaImage, + FaShare, + FaTrash, +} from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { resizeAndScaleCanvas, @@ -21,7 +19,6 @@ import { gallerySelector } from 'features/gallery/store/gallerySelectors'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { useTranslation } from 'react-i18next'; import IAIIconButton from 'common/components/IAIIconButton'; -import { useGetUrl } from 'common/util/getUrl'; import { ExternalLinkIcon } from '@chakra-ui/icons'; import { IoArrowUndoCircleOutline } from 'react-icons/io5'; import { createSelector } from '@reduxjs/toolkit'; @@ -30,15 +27,15 @@ import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { isEqual } from 'lodash-es'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { useParameters } from 'features/parameters/hooks/useParameters'; +import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { initialImageSelected } from 'features/parameters/store/actions'; -import { - requestedImageDeletion, - sentImageToCanvas, - sentImageToImg2Img, -} from '../store/actions'; +import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions'; import { useAppToaster } from 'app/components/Toaster'; -import { ImageDTO } from 'services/api'; +import { ImageDTO } from 'services/api/types'; +import { useDraggable } from '@dnd-kit/core'; +import { DeleteImageContext } from 'app/contexts/DeleteImageContext'; +import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; +import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages'; export const selector = createSelector( [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], @@ -74,17 +71,10 @@ interface HoverableImageProps { isSelected: boolean; } -const memoEqualityCheck = ( - prev: HoverableImageProps, - next: HoverableImageProps -) => - prev.image.image_name === next.image.image_name && - prev.isSelected === next.isSelected; - /** * Gallery image component with delete/use all/use seed buttons on hover. */ -const HoverableImage = memo((props: HoverableImageProps) => { +const HoverableImage = (props: HoverableImageProps) => { const dispatch = useAppDispatch(); const { activeTabName, @@ -92,74 +82,53 @@ const HoverableImage = memo((props: HoverableImageProps) => { galleryImageMinimumWidth, canDeleteImage, shouldUseSingleGalleryColumn, - shouldConfirmOnDelete, } = useAppSelector(selector); - const { - isOpen: isDeleteDialogOpen, - onOpen: onDeleteDialogOpen, - onClose: onDeleteDialogClose, - } = useDisclosure(); - const { image, isSelected } = props; const { image_url, thumbnail_url, image_name } = image; - const { getUrl } = useGetUrl(); const [isHovered, setIsHovered] = useState(false); - const toaster = useAppToaster(); const { t } = useTranslation(); - const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled; const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; - const { recallSeed, recallPrompt, recallInitialImage, recallAllParameters } = - useParameters(); + const { onDelete } = useContext(DeleteImageContext); + const { onClickAddToBoard } = useContext(AddImageToBoardContext); + const handleDelete = useCallback(() => { + onDelete(image); + }, [image, onDelete]); + const { recallBothPrompts, recallSeed, recallAllParameters } = + useRecallParameters(); + + const { attributes, listeners, setNodeRef } = useDraggable({ + id: `galleryImage_${image_name}`, + data: { + image, + }, + }); + + const [removeFromBoard] = useRemoveImageFromBoardMutation(); const handleMouseOver = () => setIsHovered(true); const handleMouseOut = () => setIsHovered(false); - // Immediately deletes an image - const handleDelete = useCallback(() => { - if (canDeleteImage && image) { - dispatch(requestedImageDeletion(image)); - } - }, [dispatch, image, canDeleteImage]); - - // Opens the alert dialog to check if user is sure they want to delete - const handleInitiateDelete = useCallback( - (e: MouseEvent) => { - e.stopPropagation(); - if (shouldConfirmOnDelete) { - onDeleteDialogOpen(); - } else { - handleDelete(); - } - }, - [handleDelete, onDeleteDialogOpen, shouldConfirmOnDelete] - ); - const handleSelectImage = useCallback(() => { - dispatch(imageSelected(image)); + dispatch(imageSelected(image.image_name)); }, [image, dispatch]); - const handleDragStart = useCallback( - (e: DragEvent) => { - e.dataTransfer.setData('invokeai/imageName', image.image_name); - e.dataTransfer.setData('invokeai/imageType', image.image_type); - e.dataTransfer.effectAllowed = 'move'; - }, - [image] - ); - // Recall parameters handlers const handleRecallPrompt = useCallback(() => { - recallPrompt( + recallBothPrompts( image.metadata?.positive_conditioning, image.metadata?.negative_conditioning ); - }, [image, recallPrompt]); + }, [ + image.metadata?.negative_conditioning, + image.metadata?.positive_conditioning, + recallBothPrompts, + ]); const handleRecallSeed = useCallback(() => { recallSeed(image.metadata?.seed); @@ -204,12 +173,28 @@ const HoverableImage = memo((props: HoverableImageProps) => { // dispatch(setIsLightboxOpen(true)); }; + const handleAddToBoard = useCallback(() => { + onClickAddToBoard(image); + }, [image, onClickAddToBoard]); + + const handleRemoveFromBoard = useCallback(() => { + if (!image.board_id) { + return; + } + removeFromBoard({ board_id: image.board_id, image_name: image.image_name }); + }, [image.board_id, image.image_name, removeFromBoard]); + const handleOpenInNewTab = () => { - window.open(getUrl(image.image_url), '_blank'); + window.open(image.image_url, '_blank'); }; return ( - <> + menuProps={{ size: 'sm', isLazy: true }} renderMenu={() => ( @@ -275,7 +260,22 @@ const HoverableImage = memo((props: HoverableImageProps) => { {t('parameters.sendToUnifiedCanvas')} )} - } onClickCapture={onDeleteDialogOpen}> + } onClickCapture={handleAddToBoard}> + {image.board_id ? 'Change Board' : 'Add to Board'} + + {image.board_id && ( + } + onClickCapture={handleRemoveFromBoard} + > + Remove from Board + + )} + } + onClickCapture={handleDelete} + > {t('gallery.deleteImage')} @@ -288,8 +288,6 @@ const HoverableImage = memo((props: HoverableImageProps) => { onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} userSelect="none" - draggable={true} - onDragStart={handleDragStart} onClick={handleSelectImage} ref={ref} sx={{ @@ -308,8 +306,9 @@ const HoverableImage = memo((props: HoverableImageProps) => { objectFit={ shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit } + draggable={false} rounded="md" - src={getUrl(thumbnail_url || image_url)} + src={thumbnail_url || image_url} fallback={} sx={{ width: '100%', @@ -353,7 +352,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { }} > } size="xs" @@ -365,15 +364,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { )} - - + ); -}, memoEqualityCheck); +}; -HoverableImage.displayName = 'HoverableImage'; - -export default HoverableImage; +export default memo(HoverableImage); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx deleted file mode 100644 index 4b0f6e60dd..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; - -import { useDisclosure } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { systemSelector } from 'features/system/store/systemSelectors'; - -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaTrash } from 'react-icons/fa'; -import { memo, useCallback } from 'react'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import DeleteImageModal from '../DeleteImageModal'; -import { requestedImageDeletion } from 'features/gallery/store/actions'; -import { ImageDTO } from 'services/api'; - -const selector = createSelector( - [systemSelector], - (system) => { - const { isProcessing, isConnected, shouldConfirmOnDelete } = system; - - return { - canDeleteImage: isConnected && !isProcessing, - shouldConfirmOnDelete, - isProcessing, - isConnected, - }; - }, - defaultSelectorOptions -); - -type DeleteImageButtonProps = { - image: ImageDTO | undefined; -}; - -const DeleteImageButton = (props: DeleteImageButtonProps) => { - const { image } = props; - const dispatch = useAppDispatch(); - const { isProcessing, isConnected, canDeleteImage, shouldConfirmOnDelete } = - useAppSelector(selector); - - const { - isOpen: isDeleteDialogOpen, - onOpen: onDeleteDialogOpen, - onClose: onDeleteDialogClose, - } = useDisclosure(); - - const { t } = useTranslation(); - - const handleDelete = useCallback(() => { - if (canDeleteImage && image) { - dispatch(requestedImageDeletion(image)); - } - }, [image, canDeleteImage, dispatch]); - - const handleInitiateDelete = useCallback(() => { - if (shouldConfirmOnDelete) { - onDeleteDialogOpen(); - } else { - handleDelete(); - } - }, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]); - - useHotkeys('delete', handleInitiateDelete, [ - image, - shouldConfirmOnDelete, - isConnected, - isProcessing, - ]); - - return ( - <> - } - tooltip={`${t('gallery.deleteImage')} (Del)`} - aria-label={`${t('gallery.deleteImage')} (Del)`} - isDisabled={!image || !isConnected} - colorScheme="error" - /> - {image && ( - - )} - - ); -}; - -export default memo(DeleteImageButton); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx index 394ff9db15..fd603d3756 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx @@ -14,6 +14,8 @@ const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => { justifyContent: 'center', position: 'absolute', color: 'base.400', + minH: 36, + minW: 36, }} > diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 468dfd694f..a22eb6d20f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -1,26 +1,30 @@ import { Box, + Button, ButtonGroup, Flex, FlexProps, Grid, Icon, Text, + VStack, forwardRef, + useColorMode, + useDisclosure, } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import IAISlider from 'common/components/IAISlider'; import { gallerySelector } from 'features/gallery/store/gallerySelectors'; import { - setCurrentCategory, setGalleryImageMinimumWidth, setGalleryImageObjectFit, setShouldAutoSwitchToNewImages, setShouldUseSingleGalleryColumn, + setGalleryView, } from 'features/gallery/store/gallerySlice'; import { togglePinGalleryPanel } from 'features/ui/store/uiSlice'; import { useOverlayScrollbars } from 'overlayscrollbars-react'; @@ -31,86 +35,84 @@ import { memo, useCallback, useEffect, + useMemo, useRef, useState, } from 'react'; import { useTranslation } from 'react-i18next'; import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs'; -import { FaImage, FaUser, FaWrench } from 'react-icons/fa'; +import { FaImage, FaServer, FaWrench } from 'react-icons/fa'; import { MdPhotoLibrary } from 'react-icons/md'; import HoverableImage from './HoverableImage'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import { resultsAdapter } from '../store/resultsSlice'; -import { - receivedResultImagesPage, - receivedUploadImagesPage, -} from 'services/thunks/gallery'; -import { uploadsAdapter } from '../store/uploadsSlice'; import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { Virtuoso, VirtuosoGrid } from 'react-virtuoso'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import GalleryProgressImage from './GalleryProgressImage'; import { uiSelector } from 'features/ui/store/uiSelectors'; -import { ImageDTO } from 'services/api'; +import { + ASSETS_CATEGORIES, + IMAGE_CATEGORIES, + imageCategoriesChanged, + selectImagesAll, +} from '../store/imagesSlice'; +import { receivedPageOfImages } from 'services/api/thunks/image'; +import BoardsList from './Boards/BoardsList'; +import { boardsSelector } from '../store/boardSlice'; +import { ChevronUpIcon } from '@chakra-ui/icons'; +import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; +import { mode } from 'theme/util/mode'; -const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290; -const PROGRESS_IMAGE_PLACEHOLDER = 'PROGRESS_IMAGE_PLACEHOLDER'; - -const categorySelector = createSelector( +const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { results, uploads, system, gallery } = state; - const { currentCategory } = gallery; + const { categories, total: allImagesTotal, isLoading } = state.images; + const { selectedBoardId } = state.boards; - if (currentCategory === 'results') { - const tempImages: (ImageDTO | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = []; + const allImages = selectImagesAll(state); - if (system.progressImage) { - tempImages.push(PROGRESS_IMAGE_PLACEHOLDER); - } - - return { - images: tempImages.concat( - resultsAdapter.getSelectors().selectAll(results) - ), - isLoading: results.isLoading, - areMoreImagesAvailable: results.page < results.pages - 1, - }; - } + const images = allImages.filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = selectedBoardId + ? i.board_id === selectedBoardId + : true; + return isInCategory && isInSelectedBoard; + }); return { - images: uploadsAdapter.getSelectors().selectAll(uploads), - isLoading: uploads.isLoading, - areMoreImagesAvailable: uploads.page < uploads.pages - 1, + images, + allImagesTotal, + isLoading, + categories, + selectedBoardId, }; }, defaultSelectorOptions ); const mainSelector = createSelector( - [gallerySelector, uiSelector], - (gallery, ui) => { + [gallerySelector, uiSelector, boardsSelector], + (gallery, ui, boards) => { const { - currentCategory, galleryImageMinimumWidth, galleryImageObjectFit, shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, } = gallery; const { shouldPinGallery } = ui; - return { - currentCategory, shouldPinGallery, galleryImageMinimumWidth, galleryImageObjectFit, shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, + selectedBoardId: boards.selectedBoardId, }; }, defaultSelectorOptions @@ -120,7 +122,6 @@ const ImageGalleryContent = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const resizeObserverRef = useRef(null); - const [shouldShouldIconButtons, setShouldShouldIconButtons] = useState(true); const rootRef = useRef(null); const [scroller, setScroller] = useState(null); const [initialize, osInstance] = useOverlayScrollbars({ @@ -136,28 +137,54 @@ const ImageGalleryContent = () => { }, }); + const { colorMode } = useColorMode(); + const { - currentCategory, shouldPinGallery, galleryImageMinimumWidth, galleryImageObjectFit, shouldAutoSwitchToNewImages, shouldUseSingleGalleryColumn, selectedImage, + galleryView, } = useAppSelector(mainSelector); - const { images, areMoreImagesAvailable, isLoading } = - useAppSelector(categorySelector); + const { images, isLoading, allImagesTotal, categories, selectedBoardId } = + useAppSelector(itemSelector); - const handleClickLoadMore = () => { - if (currentCategory === 'results') { - dispatch(receivedResultImagesPage()); - } + const { selectedBoard } = useListAllBoardsQuery(undefined, { + selectFromResult: ({ data }) => ({ + selectedBoard: data?.find((b) => b.board_id === selectedBoardId), + }), + }); - if (currentCategory === 'uploads') { - dispatch(receivedUploadImagesPage()); + const filteredImagesTotal = useMemo( + () => selectedBoard?.image_count ?? allImagesTotal, + [allImagesTotal, selectedBoard?.image_count] + ); + + const areMoreAvailable = useMemo(() => { + return images.length < filteredImagesTotal; + }, [images.length, filteredImagesTotal]); + + const handleLoadMoreImages = useCallback(() => { + dispatch( + receivedPageOfImages({ + categories, + board_id: selectedBoardId, + is_intermediate: false, + }) + ); + }, [categories, dispatch, selectedBoardId]); + + const handleEndReached = useMemo(() => { + if (areMoreAvailable && !isLoading) { + return handleLoadMoreImages; } - }; + return undefined; + }, [areMoreAvailable, handleLoadMoreImages, isLoading]); + + const { isOpen: isBoardListOpen, onToggle } = useDisclosure(); const handleChangeGalleryImageMinimumWidth = (v: number) => { dispatch(setGalleryImageMinimumWidth(v)); @@ -168,28 +195,6 @@ const ImageGalleryContent = () => { dispatch(requestCanvasRescale()); }; - useEffect(() => { - if (!resizeObserverRef.current) { - return; - } - const resizeObserver = new ResizeObserver(() => { - if (!resizeObserverRef.current) { - return; - } - - if ( - resizeObserverRef.current.clientWidth < GALLERY_SHOW_BUTTONS_MIN_WIDTH - ) { - setShouldShouldIconButtons(true); - return; - } - - setShouldShouldIconButtons(false); - }); - resizeObserver.observe(resizeObserverRef.current); - return () => resizeObserver.disconnect(); // clean up - }, []); - useEffect(() => { const { current: root } = rootRef; if (scroller && root) { @@ -209,82 +214,91 @@ const ImageGalleryContent = () => { } }, []); - const handleEndReached = useCallback(() => { - if (currentCategory === 'results') { - dispatch(receivedResultImagesPage()); - } else if (currentCategory === 'uploads') { - dispatch(receivedUploadImagesPage()); - } - }, [dispatch, currentCategory]); + const handleClickImagesCategory = useCallback(() => { + dispatch(imageCategoriesChanged(IMAGE_CATEGORIES)); + dispatch(setGalleryView('images')); + }, [dispatch]); + + const handleClickAssetsCategory = useCallback(() => { + dispatch(imageCategoriesChanged(ASSETS_CATEGORIES)); + dispatch(setGalleryView('assets')); + }, [dispatch]); return ( - - - + - {shouldShouldIconButtons ? ( - <> - } - onClick={() => dispatch(setCurrentCategory('results'))} - /> - } - onClick={() => dispatch(setCurrentCategory('uploads'))} - /> - - ) : ( - <> - dispatch(setCurrentCategory('results'))} - flexGrow={1} - > - {t('gallery.generations')} - - dispatch(setCurrentCategory('uploads'))} - flexGrow={1} - > - {t('gallery.uploads')} - - - )} - - - + + } + /> + } + /> + + + + {selectedBoard ? selectedBoard.board_name : 'All Images'} + + + } /> } @@ -300,7 +314,7 @@ const ImageGalleryContent = () => { withReset handleReset={() => dispatch(setGalleryImageMinimumWidth(64))} /> - @@ -311,14 +325,14 @@ const ImageGalleryContent = () => { ) } /> - ) => dispatch(setShouldAutoSwitchToNewImages(e.target.checked)) } /> - ) => @@ -336,9 +350,12 @@ const ImageGalleryContent = () => { icon={shouldPinGallery ? : } /> - - - {images.length || areMoreImagesAvailable ? ( + + + + + + {images.length || areMoreAvailable ? ( <> {shouldUseSingleGalleryColumn ? ( @@ -347,28 +364,15 @@ const ImageGalleryContent = () => { data={images} endReached={handleEndReached} scrollerRef={(ref) => setScrollerRef(ref)} - itemContent={(index, image) => { - const isSelected = - image === PROGRESS_IMAGE_PLACEHOLDER - ? false - : selectedImage?.image_name === image?.image_name; - - return ( - - {image === PROGRESS_IMAGE_PLACEHOLDER ? ( - - ) : ( - - )} - - ); - }} + itemContent={(index, item) => ( + + + + )} /> ) : ( { List: ListContainer, }} scrollerRef={setScroller} - itemContent={(index, image) => { - const isSelected = - image === PROGRESS_IMAGE_PLACEHOLDER - ? false - : selectedImage?.image_name === image?.image_name; - - return image === PROGRESS_IMAGE_PLACEHOLDER ? ( - - ) : ( - - ); - }} + itemContent={(index, item) => ( + + )} /> )} - {areMoreImagesAvailable + {areMoreAvailable ? t('gallery.loadMore') : t('gallery.allImagesLoaded')} @@ -435,7 +430,7 @@ const ImageGalleryContent = () => { )} - + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx index b01191105e..e150cea883 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx @@ -9,28 +9,15 @@ import { Tooltip, } from '@chakra-ui/react'; import { useAppDispatch } from 'app/store/storeHooks'; -import { useGetUrl } from 'common/util/getUrl'; -import promptToString from 'common/util/promptToString'; -import { - setCfgScale, - setHeight, - setImg2imgStrength, - setNegativePrompt, - setPositivePrompt, - setScheduler, - setSeed, - setSteps, - setWidth, -} from 'features/parameters/store/generationSlice'; +import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { setShouldShowImageDetails } from 'features/ui/store/uiSlice'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { memo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { FaCopy } from 'react-icons/fa'; import { IoArrowUndoCircleOutline } from 'react-icons/io5'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import { ImageDTO } from 'services/api'; -import { Scheduler } from 'app/constants'; +import { ImageDTO } from 'services/api/types'; type MetadataItemProps = { isLink?: boolean; @@ -53,6 +40,11 @@ const MetadataItem = ({ withCopy = false, }: MetadataItemProps) => { const { t } = useTranslation(); + + if (!value) { + return null; + } + return ( {onClick && ( @@ -101,20 +93,27 @@ type ImageMetadataViewerProps = { image: ImageDTO; }; -// TODO: I don't know if this is needed. -const memoEqualityCheck = ( - prev: ImageMetadataViewerProps, - next: ImageMetadataViewerProps -) => prev.image.image_name === next.image.image_name; - -// TODO: Show more interesting information in this component. - /** * Image metadata viewer overlays currently selected image and provides * access to any of its metadata for use in processing. */ -const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { +const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { const dispatch = useAppDispatch(); + const { + recallBothPrompts, + recallPositivePrompt, + recallNegativePrompt, + recallSeed, + recallInitialImage, + recallCfgScale, + recallModel, + recallScheduler, + recallSteps, + recallWidth, + recallHeight, + recallStrength, + recallAllParameters, + } = useRecallParameters(); useHotkeys('esc', () => { dispatch(setShouldShowImageDetails(false)); @@ -125,7 +124,6 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { const metadata = image?.metadata; const { t } = useTranslation(); - const { getUrl } = useGetUrl(); const metadataJSON = JSON.stringify(image, null, 2); @@ -147,11 +145,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { > File: - + {image.image_name} @@ -161,52 +155,53 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { {metadata.type && ( )} - {metadata.width && ( - dispatch(setWidth(Number(metadata.width)))} - /> - )} - {metadata.height && ( - dispatch(setHeight(Number(metadata.height)))} - /> - )} - {metadata.model && ( - - )} + {sessionId && } {metadata.positive_conditioning && ( + recallPositivePrompt(metadata.positive_conditioning) } - onClick={() => setPositivePrompt(metadata.positive_conditioning!)} /> )} {metadata.negative_conditioning && ( + recallNegativePrompt(metadata.negative_conditioning) } - onClick={() => setNegativePrompt(metadata.negative_conditioning!)} /> )} {metadata.seed !== undefined && ( dispatch(setSeed(Number(metadata.seed)))} + onClick={() => recallSeed(metadata.seed)} + /> + )} + {metadata.model !== undefined && ( + recallModel(metadata.model)} + /> + )} + {metadata.width && ( + recallWidth(metadata.width)} + /> + )} + {metadata.height && ( + recallHeight(metadata.height)} /> )} {/* {metadata.threshold !== undefined && ( @@ -227,23 +222,21 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { - dispatch(setScheduler(metadata.scheduler as Scheduler)) - } + onClick={() => recallScheduler(metadata.scheduler)} /> )} {metadata.steps && ( dispatch(setSteps(Number(metadata.steps)))} + onClick={() => recallSteps(metadata.steps)} /> )} {metadata.cfg_scale !== undefined && ( dispatch(setCfgScale(Number(metadata.cfg_scale)))} + onClick={() => recallCfgScale(metadata.cfg_scale)} /> )} {/* {metadata.variations && metadata.variations.length > 0 && ( @@ -284,9 +277,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { - dispatch(setImg2imgStrength(Number(metadata.strength))) - } + onClick={() => recallStrength(metadata.strength)} /> )} {/* {metadata.fit && ( @@ -325,7 +316,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { borderRadius: 'base', bg: 'whiteAlpha.500', _dark: { bg: 'blackAlpha.500' }, - w: 'max-content', + w: 'full', }} >

{metadataJSON}
@@ -334,8 +325,6 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => { ); -}, memoEqualityCheck); +}; -ImageMetadataViewer.displayName = 'ImageMetadataViewer'; - -export default ImageMetadataViewer; +export default memo(ImageMetadataViewer); diff --git a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx index fcf8359187..b1f06ad433 100644 --- a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx @@ -9,6 +9,10 @@ import { gallerySelector } from '../store/gallerySelectors'; import { RootState } from 'app/store/store'; import { imageSelected } from '../store/gallerySlice'; import { useHotkeys } from 'react-hotkeys-hook'; +import { + selectFilteredImagesAsObject, + selectFilteredImagesIds, +} from '../store/imagesSlice'; const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = { height: '100%', @@ -21,9 +25,14 @@ const nextPrevButtonStyles: ChakraProps['sx'] = { }; export const nextPrevImageButtonsSelector = createSelector( - [(state: RootState) => state, gallerySelector], - (state, gallery) => { - const { selectedImage, currentCategory } = gallery; + [ + (state: RootState) => state, + gallerySelector, + selectFilteredImagesAsObject, + selectFilteredImagesIds, + ], + (state, gallery, filteredImagesAsObject, filteredImageIds) => { + const { selectedImage } = gallery; if (!selectedImage) { return { @@ -32,29 +41,29 @@ export const nextPrevImageButtonsSelector = createSelector( }; } - const currentImageIndex = state[currentCategory].ids.findIndex( - (i) => i === selectedImage.image_name + const currentImageIndex = filteredImageIds.findIndex( + (i) => i === selectedImage ); const nextImageIndex = clamp( currentImageIndex + 1, 0, - state[currentCategory].ids.length - 1 + filteredImageIds.length - 1 ); const prevImageIndex = clamp( currentImageIndex - 1, 0, - state[currentCategory].ids.length - 1 + filteredImageIds.length - 1 ); - const nextImageId = state[currentCategory].ids[nextImageIndex]; - const prevImageId = state[currentCategory].ids[prevImageIndex]; + const nextImageId = filteredImageIds[nextImageIndex]; + const prevImageId = filteredImageIds[prevImageIndex]; - const nextImage = state[currentCategory].entities[nextImageId]; - const prevImage = state[currentCategory].entities[prevImageId]; + const nextImage = filteredImagesAsObject[nextImageId]; + const prevImage = filteredImagesAsObject[prevImageId]; - const imagesLength = state[currentCategory].ids.length; + const imagesLength = filteredImageIds.length; return { isOnFirstImage: currentImageIndex === 0, @@ -62,6 +71,8 @@ export const nextPrevImageButtonsSelector = createSelector( !isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1, nextImage, prevImage, + nextImageId, + prevImageId, }; }, { @@ -75,7 +86,7 @@ const NextPrevImageButtons = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { isOnFirstImage, isOnLastImage, nextImage, prevImage } = + const { isOnFirstImage, isOnLastImage, nextImageId, prevImageId } = useAppSelector(nextPrevImageButtonsSelector); const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = @@ -90,19 +101,19 @@ const NextPrevImageButtons = () => { }, []); const handlePrevImage = useCallback(() => { - dispatch(imageSelected(prevImage)); - }, [dispatch, prevImage]); + dispatch(imageSelected(prevImageId)); + }, [dispatch, prevImageId]); const handleNextImage = useCallback(() => { - dispatch(imageSelected(nextImage)); - }, [dispatch, nextImage]); + dispatch(imageSelected(nextImageId)); + }, [dispatch, nextImageId]); useHotkeys( 'left', () => { handlePrevImage(); }, - [prevImage] + [prevImageId] ); useHotkeys( @@ -110,7 +121,7 @@ const NextPrevImageButtons = () => { () => { handleNextImage(); }, - [nextImage] + [nextImageId] ); return ( diff --git a/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx b/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx new file mode 100644 index 0000000000..3fabe706d6 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/SelectedItemOverlay.tsx @@ -0,0 +1,40 @@ +import { useColorMode, useToken } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { mode } from 'theme/util/mode'; + +export const SelectedItemOverlay = () => { + const [accent400, accent500] = useToken('colors', [ + 'accent.400', + 'accent.500', + ]); + + const { colorMode } = useColorMode(); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useGetImageByName.ts b/invokeai/frontend/web/src/features/gallery/hooks/useGetImageByName.ts index ad0870e7a4..89709b322a 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useGetImageByName.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useGetImageByName.ts @@ -1,33 +1,18 @@ -import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { ImageType } from 'services/api'; -import { selectResultsEntities } from '../store/resultsSlice'; -import { selectUploadsEntities } from '../store/uploadsSlice'; +import { selectImagesEntities } from '../store/imagesSlice'; +import { useCallback } from 'react'; -const useGetImageByNameSelector = createSelector( - [selectResultsEntities, selectUploadsEntities], - (allResults, allUploads) => { - return { allResults, allUploads }; - } -); - -const useGetImageByNameAndType = () => { - const { allResults, allUploads } = useAppSelector(useGetImageByNameSelector); - return (name: string, type: ImageType) => { - if (type === 'results') { - const resultImagesResult = allResults[name]; - if (resultImagesResult) { - return resultImagesResult; +const useGetImageByName = () => { + const images = useAppSelector(selectImagesEntities); + return useCallback( + (name: string | undefined) => { + if (!name) { + return; } - } - - if (type === 'uploads') { - const userImagesResult = allUploads[name]; - if (userImagesResult) { - return userImagesResult; - } - } - }; + return images[name]; + }, + [images] + ); }; -export default useGetImageByNameAndType; +export default useGetImageByName; diff --git a/invokeai/frontend/web/src/features/gallery/store/actions.ts b/invokeai/frontend/web/src/features/gallery/store/actions.ts index 7e071f279d..4234778120 100644 --- a/invokeai/frontend/web/src/features/gallery/store/actions.ts +++ b/invokeai/frontend/web/src/features/gallery/store/actions.ts @@ -1,10 +1,25 @@ import { createAction } from '@reduxjs/toolkit'; -import { ImageNameAndType } from 'features/parameters/store/actions'; -import { ImageDTO } from 'services/api'; +import { ImageUsage } from 'app/contexts/DeleteImageContext'; +import { ImageDTO, BoardDTO } from 'services/api/types'; -export const requestedImageDeletion = createAction< - ImageDTO | ImageNameAndType | undefined ->('gallery/requestedImageDeletion'); +export type RequestedImageDeletionArg = { + image: ImageDTO; + imageUsage: ImageUsage; +}; + +export const requestedImageDeletion = createAction( + 'gallery/requestedImageDeletion' +); + +export type RequestedBoardImagesDeletionArg = { + board: BoardDTO; + imagesUsage: ImageUsage; +}; + +export const requestedBoardImagesDeletion = + createAction( + 'gallery/requestedBoardImagesDeletion' + ); export const sentImageToCanvas = createAction('gallery/sentImageToCanvas'); diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts new file mode 100644 index 0000000000..3dac2b6e50 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/boardSelectors.ts @@ -0,0 +1,23 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { selectBoardsAll } from './boardSlice'; + +export const boardSelector = (state: RootState) => state.boards.entities; + +export const searchBoardsSelector = createSelector( + (state: RootState) => state, + (state) => { + const { + boards: { searchText }, + } = state; + + if (!searchText) { + // If no search text provided, return all entities + return selectBoardsAll(state); + } + + return selectBoardsAll(state).filter((i) => + i.board_name.toLowerCase().includes(searchText.toLowerCase()) + ); + } +); diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts new file mode 100644 index 0000000000..7ec74dc4bf --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -0,0 +1,47 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { boardsApi } from 'services/api/endpoints/boards'; + +type BoardsState = { + searchText: string; + selectedBoardId?: string; + updateBoardModalOpen: boolean; +}; + +export const initialBoardsState: BoardsState = { + updateBoardModalOpen: false, + searchText: '', +}; + +const boardsSlice = createSlice({ + name: 'boards', + initialState: initialBoardsState, + reducers: { + boardIdSelected: (state, action: PayloadAction) => { + state.selectedBoardId = action.payload; + }, + setBoardSearchText: (state, action: PayloadAction) => { + state.searchText = action.payload; + }, + setUpdateBoardModalOpen: (state, action: PayloadAction) => { + state.updateBoardModalOpen = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addMatcher( + boardsApi.endpoints.deleteBoard.matchFulfilled, + (state, action) => { + if (action.meta.arg.originalArgs === state.selectedBoardId) { + state.selectedBoardId = undefined; + } + } + ); + }, +}); + +export const { boardIdSelected, setBoardSearchText, setUpdateBoardModalOpen } = + boardsSlice.actions; + +export const boardsSelector = (state: RootState) => state.boards; + +export default boardsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts index 49f51d5a80..44e03f9f71 100644 --- a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts @@ -4,6 +4,5 @@ import { GalleryState } from './gallerySlice'; * Gallery slice persist denylist */ export const galleryPersistDenylist: (keyof GalleryState)[] = [ - 'currentCategory', 'shouldAutoSwitchToNewImages', ]; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 9d6f5ece60..b7fc0809a6 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -1,20 +1,16 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { - receivedResultImagesPage, - receivedUploadImagesPage, -} from '../../../services/thunks/gallery'; -import { ImageDTO } from 'services/api'; +import { imageUpserted } from './imagesSlice'; type GalleryImageObjectFitType = 'contain' | 'cover'; export interface GalleryState { - selectedImage?: ImageDTO; + selectedImage?: string; galleryImageMinimumWidth: number; galleryImageObjectFit: GalleryImageObjectFitType; shouldAutoSwitchToNewImages: boolean; shouldUseSingleGalleryColumn: boolean; - currentCategory: 'results' | 'uploads'; + galleryView: 'images' | 'assets' | 'boards'; } export const initialGalleryState: GalleryState = { @@ -22,14 +18,14 @@ export const initialGalleryState: GalleryState = { galleryImageObjectFit: 'cover', shouldAutoSwitchToNewImages: true, shouldUseSingleGalleryColumn: false, - currentCategory: 'results', + galleryView: 'images', }; export const gallerySlice = createSlice({ name: 'gallery', initialState: initialGalleryState, reducers: { - imageSelected: (state, action: PayloadAction) => { + imageSelected: (state, action: PayloadAction) => { state.selectedImage = action.payload; // TODO: if the user selects an image, disable the auto switch? // state.shouldAutoSwitchToNewImages = false; @@ -46,52 +42,36 @@ export const gallerySlice = createSlice({ setShouldAutoSwitchToNewImages: (state, action: PayloadAction) => { state.shouldAutoSwitchToNewImages = action.payload; }, - setCurrentCategory: ( - state, - action: PayloadAction<'results' | 'uploads'> - ) => { - state.currentCategory = action.payload; - }, setShouldUseSingleGalleryColumn: ( state, action: PayloadAction ) => { state.shouldUseSingleGalleryColumn = action.payload; }, + setGalleryView: ( + state, + action: PayloadAction<'images' | 'assets' | 'boards'> + ) => { + state.galleryView = action.payload; + }, }, - extraReducers(builder) { - builder.addCase(receivedResultImagesPage.fulfilled, (state, action) => { - // rehydrate selectedImage URL when results list comes in - // solves case when outdated URL is in local storage - const selectedImage = state.selectedImage; - if (selectedImage) { - const selectedImageInResults = action.payload.items.find( - (image) => image.image_name === selectedImage.image_name - ); - - if (selectedImageInResults) { - selectedImage.image_url = selectedImageInResults.image_url; - selectedImage.thumbnail_url = selectedImageInResults.thumbnail_url; - state.selectedImage = selectedImage; - } + extraReducers: (builder) => { + builder.addCase(imageUpserted, (state, action) => { + if ( + state.shouldAutoSwitchToNewImages && + action.payload.image_category === 'general' + ) { + state.selectedImage = action.payload.image_name; } }); - builder.addCase(receivedUploadImagesPage.fulfilled, (state, action) => { - // rehydrate selectedImage URL when results list comes in - // solves case when outdated URL is in local storage - const selectedImage = state.selectedImage; - if (selectedImage) { - const selectedImageInResults = action.payload.items.find( - (image) => image.image_name === selectedImage.image_name - ); + // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + // const { image_name, image_url, thumbnail_url } = action.payload; - if (selectedImageInResults) { - selectedImage.image_url = selectedImageInResults.image_url; - selectedImage.thumbnail_url = selectedImageInResults.thumbnail_url; - state.selectedImage = selectedImage; - } - } - }); + // if (state.selectedImage?.image_name === image_name) { + // state.selectedImage.image_url = image_url; + // state.selectedImage.thumbnail_url = thumbnail_url; + // } + // }); }, }); @@ -101,7 +81,7 @@ export const { setGalleryImageObjectFit, setShouldAutoSwitchToNewImages, setShouldUseSingleGalleryColumn, - setCurrentCategory, + setGalleryView, } = gallerySlice.actions; export default gallerySlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts new file mode 100644 index 0000000000..8041ffd5c5 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts @@ -0,0 +1,182 @@ +import { + PayloadAction, + Update, + createEntityAdapter, + createSelector, + createSlice, +} from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { ImageCategory, ImageDTO } from 'services/api/types'; +import { dateComparator } from 'common/util/dateComparator'; +import { keyBy } from 'lodash-es'; +import { + imageDeleted, + imageUrlsReceived, + receivedPageOfImages, +} from 'services/api/thunks/image'; + +export const imagesAdapter = createEntityAdapter({ + selectId: (image) => image.image_name, + sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at), +}); + +export const IMAGE_CATEGORIES: ImageCategory[] = ['general']; +export const ASSETS_CATEGORIES: ImageCategory[] = [ + 'control', + 'mask', + 'user', + 'other', +]; + +type AdditionaImagesState = { + offset: number; + limit: number; + total: number; + isLoading: boolean; + categories: ImageCategory[]; +}; + +export const initialImagesState = + imagesAdapter.getInitialState({ + offset: 0, + limit: 0, + total: 0, + isLoading: false, + categories: IMAGE_CATEGORIES, + }); + +export type ImagesState = typeof initialImagesState; + +const imagesSlice = createSlice({ + name: 'images', + initialState: initialImagesState, + reducers: { + imageUpserted: (state, action: PayloadAction) => { + imagesAdapter.upsertOne(state, action.payload); + }, + imageUpdatedOne: (state, action: PayloadAction>) => { + imagesAdapter.updateOne(state, action.payload); + }, + imageRemoved: (state, action: PayloadAction) => { + imagesAdapter.removeOne(state, action.payload); + }, + imagesRemoved: (state, action: PayloadAction) => { + imagesAdapter.removeMany(state, action.payload); + }, + imageCategoriesChanged: (state, action: PayloadAction) => { + state.categories = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(receivedPageOfImages.pending, (state) => { + state.isLoading = true; + }); + builder.addCase(receivedPageOfImages.rejected, (state) => { + state.isLoading = false; + }); + builder.addCase(receivedPageOfImages.fulfilled, (state, action) => { + state.isLoading = false; + const { board_id, categories, image_origin, is_intermediate } = + action.meta.arg; + + const { items, offset, limit, total } = action.payload; + imagesAdapter.upsertMany(state, items); + + if (!categories?.includes('general') || board_id) { + // need to skip updating the total images count if the images recieved were for a specific board + // TODO: this doesn't work when on the Asset tab/category... + return; + } + + state.offset = offset; + state.limit = limit; + state.total = total; + }); + builder.addCase(imageDeleted.pending, (state, action) => { + // Image deleted + const { image_name } = action.meta.arg; + imagesAdapter.removeOne(state, image_name); + }); + builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { + const { image_name, image_url, thumbnail_url } = action.payload; + + imagesAdapter.updateOne(state, { + id: image_name, + changes: { image_url, thumbnail_url }, + }); + }); + }, +}); + +export const { + selectAll: selectImagesAll, + selectById: selectImagesById, + selectEntities: selectImagesEntities, + selectIds: selectImagesIds, + selectTotal: selectImagesTotal, +} = imagesAdapter.getSelectors((state) => state.images); + +export const { + imageUpserted, + imageUpdatedOne, + imageRemoved, + imagesRemoved, + imageCategoriesChanged, +} = imagesSlice.actions; + +export default imagesSlice.reducer; + +export const selectFilteredImagesAsArray = createSelector( + (state: RootState) => state, + (state) => { + const { + images: { categories }, + } = state; + + return selectImagesAll(state).filter((i) => + categories.includes(i.image_category) + ); + } +); + +export const selectFilteredImagesAsObject = createSelector( + (state: RootState) => state, + (state) => { + const { + images: { categories }, + } = state; + + return keyBy( + selectImagesAll(state).filter((i) => + categories.includes(i.image_category) + ), + 'image_name' + ); + } +); + +export const selectFilteredImagesIds = createSelector( + (state: RootState) => state, + (state) => { + const { + images: { categories }, + } = state; + + return selectImagesAll(state) + .filter((i) => categories.includes(i.image_category)) + .map((i) => i.image_name); + } +); + +// export const selectImageById = createSelector( +// (state: RootState, imageId) => state, +// (state) => { +// const { +// images: { categories }, +// } = state; + +// return selectImagesAll(state) +// .filter((i) => categories.includes(i.image_category)) +// .map((i) => i.image_name); +// } +// ); diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts deleted file mode 100644 index 1c3d8aaaec..0000000000 --- a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ResultsState } from './resultsSlice'; - -/** - * Results slice persist denylist - * - * Currently denylisting results slice entirely, see `serialize.ts` - */ -export const resultsPersistDenylist: (keyof ResultsState)[] = []; diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts deleted file mode 100644 index 36f4c49401..0000000000 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - PayloadAction, - createEntityAdapter, - createSlice, -} from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { - receivedResultImagesPage, - IMAGES_PER_PAGE, -} from 'services/thunks/gallery'; -import { ImageDTO } from 'services/api'; -import { dateComparator } from 'common/util/dateComparator'; - -export type ResultsImageDTO = Omit & { - image_type: 'results'; -}; - -export const resultsAdapter = createEntityAdapter({ - selectId: (image) => image.image_name, - sortComparer: (a, b) => dateComparator(b.created_at, a.created_at), -}); - -type AdditionalResultsState = { - page: number; - pages: number; - isLoading: boolean; - nextPage: number; - upsertedImageCount: number; -}; - -export const initialResultsState = - resultsAdapter.getInitialState({ - page: 0, - pages: 0, - isLoading: false, - nextPage: 0, - upsertedImageCount: 0, - }); - -export type ResultsState = typeof initialResultsState; - -const resultsSlice = createSlice({ - name: 'results', - initialState: initialResultsState, - reducers: { - resultUpserted: (state, action: PayloadAction) => { - resultsAdapter.upsertOne(state, action.payload); - state.upsertedImageCount += 1; - }, - }, - extraReducers: (builder) => { - /** - * Received Result Images Page - PENDING - */ - builder.addCase(receivedResultImagesPage.pending, (state) => { - state.isLoading = true; - }); - - /** - * Received Result Images Page - FULFILLED - */ - builder.addCase(receivedResultImagesPage.fulfilled, (state, action) => { - const { page, pages } = action.payload; - - // We know these will all be of the results type, but it's not represented in the API types - const items = action.payload.items as ResultsImageDTO[]; - - resultsAdapter.setMany(state, items); - - state.page = page; - state.pages = pages; - state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1; - state.isLoading = false; - }); - }, -}); - -export const { - selectAll: selectResultsAll, - selectById: selectResultsById, - selectEntities: selectResultsEntities, - selectIds: selectResultsIds, - selectTotal: selectResultsTotal, -} = resultsAdapter.getSelectors((state) => state.results); - -export const { resultUpserted } = resultsSlice.actions; - -export default resultsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts deleted file mode 100644 index 296e8b2057..0000000000 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UploadsState } from './uploadsSlice'; - -/** - * Uploads slice persist denylist - * - * Currently denylisting uploads slice entirely, see `serialize.ts` - */ -export const uploadsPersistDenylist: (keyof UploadsState)[] = []; diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts deleted file mode 100644 index 3058e82673..0000000000 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - PayloadAction, - createEntityAdapter, - createSlice, -} from '@reduxjs/toolkit'; - -import { RootState } from 'app/store/store'; -import { - receivedUploadImagesPage, - IMAGES_PER_PAGE, -} from 'services/thunks/gallery'; -import { ImageDTO } from 'services/api'; -import { dateComparator } from 'common/util/dateComparator'; - -export type UploadsImageDTO = Omit & { - image_type: 'uploads'; -}; - -export const uploadsAdapter = createEntityAdapter({ - selectId: (image) => image.image_name, - sortComparer: (a, b) => dateComparator(b.created_at, a.created_at), -}); - -type AdditionalUploadsState = { - page: number; - pages: number; - isLoading: boolean; - nextPage: number; - upsertedImageCount: number; -}; - -export const initialUploadsState = - uploadsAdapter.getInitialState({ - page: 0, - pages: 0, - nextPage: 0, - isLoading: false, - upsertedImageCount: 0, - }); - -export type UploadsState = typeof initialUploadsState; - -const uploadsSlice = createSlice({ - name: 'uploads', - initialState: initialUploadsState, - reducers: { - uploadUpserted: (state, action: PayloadAction) => { - uploadsAdapter.upsertOne(state, action.payload); - state.upsertedImageCount += 1; - }, - }, - extraReducers: (builder) => { - /** - * Received Upload Images Page - PENDING - */ - builder.addCase(receivedUploadImagesPage.pending, (state) => { - state.isLoading = true; - }); - - /** - * Received Upload Images Page - FULFILLED - */ - builder.addCase(receivedUploadImagesPage.fulfilled, (state, action) => { - const { page, pages } = action.payload; - - // We know these will all be of the uploads type, but it's not represented in the API types - const items = action.payload.items as UploadsImageDTO[]; - - uploadsAdapter.setMany(state, items); - - state.page = page; - state.pages = pages; - state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1; - state.isLoading = false; - }); - }, -}); - -export const { - selectAll: selectUploadsAll, - selectById: selectUploadsById, - selectEntities: selectUploadsEntities, - selectIds: selectUploadsIds, - selectTotal: selectUploadsTotal, -} = uploadsAdapter.getSelectors((state) => state.uploads); - -export const { uploadUpserted } = uploadsSlice.actions; - -export default uploadsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx b/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx index b1e822c309..73e7144163 100644 --- a/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx +++ b/invokeai/frontend/web/src/features/lightbox/components/ReactPanZoomImage.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch'; -import { useGetUrl } from 'common/util/getUrl'; -import { ImageDTO } from 'services/api'; +import { ImageDTO } from 'services/api/types'; type ReactPanZoomProps = { image: ImageDTO; @@ -23,7 +22,6 @@ export default function ReactPanZoomImage({ scaleY, }: ReactPanZoomProps) { const { centerView } = useTransformContext(); - const { getUrl } = useGetUrl(); return ( { + const data: NodeTemplate[] = map(nodes.invocationTemplates, (template) => { + return { + label: template.title, + value: template.type, + description: template.description, + }; + }); + + return { data }; + }, + defaultSelectorOptions +); const AddNodeMenu = () => { const dispatch = useAppDispatch(); - - const invocationTemplates = useAppSelector( - (state: RootState) => state.nodes.invocationTemplates - ); + const { data } = useAppSelector(selector); const buildInvocation = useBuildInvocation(); @@ -46,23 +59,52 @@ const AddNodeMenu = () => { ); return ( - - } + + + item.label.toLowerCase().includes(value.toLowerCase().trim()) || + item.value.toLowerCase().includes(value.toLowerCase().trim()) || + item.description.toLowerCase().includes(value.toLowerCase().trim()) + } + onChange={(v) => { + v[0] && addNode(v[0] as AnyInvocationType); + }} + sx={{ + width: '18rem', + }} /> - - {map(invocationTemplates, ({ title, description, type }, key) => { - return ( - - addNode(type)}>{title} - - ); - })} - - + ); }; -export default memo(AddNodeMenu); +interface ItemProps extends React.ComponentPropsWithoutRef<'div'> { + value: string; + label: string; + description: string; +} + +const SelectItem = forwardRef( + ({ label, description, ...others }: ItemProps, ref) => { + return ( +
+
+ {label} + + {description} + +
+
+ ); + } +); + +SelectItem.displayName = 'SelectItem'; + +export default AddNodeMenu; diff --git a/invokeai/frontend/web/src/features/nodes/components/FieldTypeLegend.tsx b/invokeai/frontend/web/src/features/nodes/components/FieldTypeLegend.tsx index c14c7ebccf..78316cc694 100644 --- a/invokeai/frontend/web/src/features/nodes/components/FieldTypeLegend.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/FieldTypeLegend.tsx @@ -6,7 +6,7 @@ import { memo } from 'react'; const FieldTypeLegend = () => { return ( - + {map(FIELDS, ({ title, description, color }, key) => ( { const { nodeId, template } = props; return ( - + {template.title} @@ -30,7 +40,16 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => { hasArrow shouldWrapChildren > - + ); diff --git a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx index 346261fbff..65b7cfa560 100644 --- a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx @@ -7,6 +7,9 @@ import EnumInputFieldComponent from './fields/EnumInputFieldComponent'; import ImageInputFieldComponent from './fields/ImageInputFieldComponent'; import LatentsInputFieldComponent from './fields/LatentsInputFieldComponent'; import ConditioningInputFieldComponent from './fields/ConditioningInputFieldComponent'; +import UNetInputFieldComponent from './fields/UNetInputFieldComponent'; +import ClipInputFieldComponent from './fields/ClipInputFieldComponent'; +import VaeInputFieldComponent from './fields/VaeInputFieldComponent'; import ControlInputFieldComponent from './fields/ControlInputFieldComponent'; import ModelInputFieldComponent from './fields/ModelInputFieldComponent'; import NumberInputFieldComponent from './fields/NumberInputFieldComponent'; @@ -98,6 +101,36 @@ const InputFieldComponent = (props: InputFieldComponentProps) => { ); } + if (type === 'unet' && template.type === 'unet') { + return ( + + ); + } + + if (type === 'clip' && template.type === 'clip') { + return ( + + ); + } + + if (type === 'vae' && template.type === 'vae') { + return ( + + ); + } + if (type === 'control' && template.type === 'control') { return ( ) => { return ( - + @@ -86,8 +93,9 @@ export const InvocationComponent = memo((props: NodeProps) => { sx={{ flexDirection: 'column', borderBottomRadius: 'md', - bg: 'base.800', py: 2, + bg: 'base.200', + _dark: { bg: 'base.800' }, }} > diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx index 0afd4ce6c5..2be98b1cb9 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx @@ -8,12 +8,12 @@ import { memo } from 'react'; const NodeEditor = () => { return ( diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx index e66f75792b..1d498f19f5 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx @@ -11,16 +11,20 @@ const NodeGraphOverlay = () => { return ( {JSON.stringify(graph, null, 2)} diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ClipInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ClipInputFieldComponent.tsx new file mode 100644 index 0000000000..86359dc9b5 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ClipInputFieldComponent.tsx @@ -0,0 +1,16 @@ +import { + ClipInputFieldTemplate, + ClipInputFieldValue, +} from 'features/nodes/types/types'; +import { memo } from 'react'; +import { FieldComponentProps } from './types'; + +const ClipInputFieldComponent = ( + props: FieldComponentProps +) => { + const { nodeId, field } = props; + + return null; +}; + +export default memo(ClipInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx index 18be021625..8d83e8353f 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx @@ -1,61 +1,81 @@ -import { Box, Image } from '@chakra-ui/react'; import { useAppDispatch } from 'app/store/storeHooks'; -import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder'; -import { useGetUrl } from 'common/util/getUrl'; -import useGetImageByNameAndType from 'features/gallery/hooks/useGetImageByName'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ImageInputFieldTemplate, ImageInputFieldValue, } from 'features/nodes/types/types'; -import { DragEvent, memo, useCallback, useState } from 'react'; +import { memo, useCallback } from 'react'; -import { ImageType } from 'services/api'; import { FieldComponentProps } from './types'; +import IAIDndImage from 'common/components/IAIDndImage'; +import { ImageDTO } from 'services/api/types'; +import { Flex } from '@chakra-ui/react'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; const ImageInputFieldComponent = ( props: FieldComponentProps ) => { const { nodeId, field } = props; - const getImageByNameAndType = useGetImageByNameAndType(); const dispatch = useAppDispatch(); - const [url, setUrl] = useState(field.value?.image_url); - const { getUrl } = useGetUrl(); + + const { + currentData: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(field.value?.image_name ?? skipToken); const handleDrop = useCallback( - (e: DragEvent) => { - const name = e.dataTransfer.getData('invokeai/imageName'); - const type = e.dataTransfer.getData('invokeai/imageType') as ImageType; - - if (!name || !type) { + ({ image_name }: ImageDTO) => { + if (field.value?.image_name === image_name) { return; } - const image = getImageByNameAndType(name, type); - - if (!image) { - return; - } - - setUrl(image.image_url); - dispatch( fieldValueChanged({ nodeId, fieldName: field.name, - value: image, + value: { image_name }, }) ); }, - [getImageByNameAndType, dispatch, field.name, nodeId] + [dispatch, field.name, field.value, nodeId] ); + const handleReset = useCallback(() => { + dispatch( + fieldValueChanged({ + nodeId, + fieldName: field.name, + value: undefined, + }) + ); + }, [dispatch, field.name, nodeId]); + return ( - - } /> - + + + ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx index a1ef69de01..741662655f 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ModelInputFieldComponent.tsx @@ -1,28 +1,18 @@ -import { Select } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { SelectItem } from '@mantine/core'; +import { useAppDispatch } from 'app/store/storeHooks'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ModelInputFieldTemplate, ModelInputFieldValue, } from 'features/nodes/types/types'; -import { selectModelsIds } from 'features/system/store/modelSlice'; -import { isEqual } from 'lodash-es'; -import { ChangeEvent, memo } from 'react'; -import { FieldComponentProps } from './types'; -const availableModelsSelector = createSelector( - [selectModelsIds], - (allModelNames) => { - return { allModelNames }; - // return map(modelList, (_, name) => name); - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); +import { memo, useCallback, useEffect, useMemo } from 'react'; +import { FieldComponentProps } from './types'; +import { forEach, isString } from 'lodash-es'; +import { MODEL_TYPE_MAP as BASE_MODEL_NAME_MAP } from 'features/system/components/ModelSelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { useTranslation } from 'react-i18next'; +import { useListModelsQuery } from 'services/api/endpoints/models'; const ModelInputFieldComponent = ( props: FieldComponentProps @@ -30,28 +20,82 @@ const ModelInputFieldComponent = ( const { nodeId, field } = props; const dispatch = useAppDispatch(); + const { t } = useTranslation(); - const { allModelNames } = useAppSelector(availableModelsSelector); + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'main', + }); - const handleValueChanged = (e: ChangeEvent) => { - dispatch( - fieldValueChanged({ - nodeId, - fieldName: field.name, - value: e.target.value, - }) - ); - }; + const data = useMemo(() => { + if (!pipelineModels) { + return []; + } + + const data: SelectItem[] = []; + + forEach(pipelineModels.entities, (model, id) => { + if (!model) { + return; + } + + data.push({ + value: id, + label: model.name, + group: BASE_MODEL_NAME_MAP[model.base_model], + }); + }); + + return data; + }, [pipelineModels]); + + const selectedModel = useMemo( + () => pipelineModels?.entities[field.value ?? pipelineModels.ids[0]], + [pipelineModels?.entities, pipelineModels?.ids, field.value] + ); + + const handleValueChanged = useCallback( + (v: string | null) => { + if (!v) { + return; + } + + dispatch( + fieldValueChanged({ + nodeId, + fieldName: field.name, + value: v, + }) + ); + }, + [dispatch, field.name, nodeId] + ); + + useEffect(() => { + if (field.value && pipelineModels?.ids.includes(field.value)) { + return; + } + + const firstModel = pipelineModels?.ids[0]; + + if (!isString(firstModel)) { + return; + } + + handleValueChanged(firstModel); + }, [field.value, handleValueChanged, pipelineModels?.ids]); return ( - + /> ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/UNetInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/UNetInputFieldComponent.tsx new file mode 100644 index 0000000000..5926bf113a --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/fields/UNetInputFieldComponent.tsx @@ -0,0 +1,16 @@ +import { + UNetInputFieldTemplate, + UNetInputFieldValue, +} from 'features/nodes/types/types'; +import { memo } from 'react'; +import { FieldComponentProps } from './types'; + +const UNetInputFieldComponent = ( + props: FieldComponentProps +) => { + const { nodeId, field } = props; + + return null; +}; + +export default memo(UNetInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/VaeInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/VaeInputFieldComponent.tsx new file mode 100644 index 0000000000..0fa11ae34e --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/fields/VaeInputFieldComponent.tsx @@ -0,0 +1,16 @@ +import { + VaeInputFieldTemplate, + VaeInputFieldValue, +} from 'features/nodes/types/types'; +import { memo } from 'react'; +import { FieldComponentProps } from './types'; + +const VaeInputFieldComponent = ( + props: FieldComponentProps +) => { + const { nodeId, field } = props; + + return null; +}; + +export default memo(VaeInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/MinimapPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/MinimapPanel.tsx index 2d76e2911f..1ad4cc78ce 100644 --- a/invokeai/frontend/web/src/features/nodes/components/panels/MinimapPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/panels/MinimapPanel.tsx @@ -1,15 +1,25 @@ -import { RootState } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; -import { CSSProperties, memo } from 'react'; +import { useColorModeValue } from '@chakra-ui/react'; +import { memo } from 'react'; import { MiniMap } from 'reactflow'; -const MinimapStyle: CSSProperties = { - background: 'var(--invokeai-colors-base-500)', -}; - const MinimapPanel = () => { - const currentTheme = useAppSelector( - (state: RootState) => state.ui.currentTheme + const miniMapStyle = useColorModeValue( + { + background: 'var(--invokeai-colors-base-200)', + }, + { + background: 'var(--invokeai-colors-base-500)', + } + ); + + const nodeColor = useColorModeValue( + 'var(--invokeai-colors-accent-300)', + 'var(--invokeai-colors-accent-700)' + ); + + const maskColor = useColorModeValue( + 'var(--invokeai-colors-blackAlpha-300)', + 'var(--invokeai-colors-blackAlpha-600)' ); return ( @@ -18,15 +28,9 @@ const MinimapPanel = () => { pannable zoomable nodeBorderRadius={30} - style={MinimapStyle} - nodeColor={ - currentTheme === 'light' - ? 'var(--invokeai-colors-accent-700)' - : currentTheme === 'green' - ? 'var(--invokeai-colors-accent-600)' - : 'var(--invokeai-colors-accent-700)' - } - maskColor="var(--invokeai-colors-base-700)" + style={miniMapStyle} + nodeColor={nodeColor} + maskColor={maskColor} /> ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx index b97bf423e1..17920af501 100644 --- a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx @@ -1,18 +1,14 @@ import { HStack } from '@chakra-ui/react'; -import { userInvoked } from 'app/store/actions'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import { memo, useCallback } from 'react'; import { Panel } from 'reactflow'; -import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { receivedOpenAPISchema } from 'services/api/thunks/schema'; +import NodeInvokeButton from '../ui/NodeInvokeButton'; const TopCenterPanel = () => { const dispatch = useAppDispatch(); - const handleInvoke = useCallback(() => { - dispatch(userInvoked('nodes')); - }, [dispatch]); - const handleReloadSchema = useCallback(() => { dispatch(receivedOpenAPISchema()); }, [dispatch]); @@ -20,9 +16,7 @@ const TopCenterPanel = () => { return ( - - Will it blend? - + Reload Schema diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx index 3fe72225eb..2b89db000a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopLeftPanel.tsx @@ -1,10 +1,10 @@ import { memo } from 'react'; import { Panel } from 'reactflow'; -import NodeSearch from '../search/NodeSearch'; +import AddNodeMenu from '../AddNodeMenu'; const TopLeftPanel = () => ( - + ); diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/NodeInvokeButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/NodeInvokeButton.tsx new file mode 100644 index 0000000000..be5e5a943e --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/ui/NodeInvokeButton.tsx @@ -0,0 +1,95 @@ +import { Box } from '@chakra-ui/react'; +import { userInvoked } from 'app/store/actions'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIButton, { IAIButtonProps } from 'common/components/IAIButton'; +import IAIIconButton, { + IAIIconButtonProps, +} from 'common/components/IAIIconButton'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; +import ProgressBar from 'features/system/components/ProgressBar'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { useCallback } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { useTranslation } from 'react-i18next'; +import { FaPlay } from 'react-icons/fa'; + +interface InvokeButton + extends Omit { + iconButton?: boolean; +} + +export default function NodeInvokeButton(props: InvokeButton) { + const { iconButton = false, ...rest } = props; + const dispatch = useAppDispatch(); + const activeTabName = useAppSelector(activeTabNameSelector); + const isReady = useIsReadyToInvoke(); + const handleInvoke = useCallback(() => { + dispatch(userInvoked('nodes')); + }, [dispatch]); + + const { t } = useTranslation(); + + useHotkeys( + ['ctrl+enter', 'meta+enter'], + handleInvoke, + { + enabled: () => isReady, + preventDefault: true, + enableOnFormTags: ['input', 'textarea', 'select'], + }, + [isReady, activeTabName] + ); + + return ( + + + {!isReady && ( + + + + )} + {iconButton ? ( + } + isDisabled={!isReady} + onClick={handleInvoke} + flexGrow={1} + w="100%" + tooltip={t('parameters.invoke')} + tooltipProps={{ placement: 'bottom' }} + colorScheme="accent" + id="invoke-button" + {...rest} + /> + ) : ( + + Invoke + + )} + + + ); +} diff --git a/invokeai/frontend/web/src/features/nodes/store/actions.ts b/invokeai/frontend/web/src/features/nodes/store/actions.ts index eda753b9dc..2463a1e945 100644 --- a/invokeai/frontend/web/src/features/nodes/store/actions.ts +++ b/invokeai/frontend/web/src/features/nodes/store/actions.ts @@ -1,5 +1,5 @@ import { createAction, isAnyOf } from '@reduxjs/toolkit'; -import { Graph } from 'services/api'; +import { Graph } from 'services/api/types'; export const textToImageGraphBuilt = createAction( 'nodes/textToImageGraphBuilt' diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index 3c93be7ac5..ba217fff5f 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -11,14 +11,11 @@ import { NodeChange, OnConnectStartParams, } from 'reactflow'; -import { ImageDTO } from 'services/api'; -import { receivedOpenAPISchema } from 'services/thunks/schema'; +import { ImageField } from 'services/api/types'; +import { receivedOpenAPISchema } from 'services/api/thunks/schema'; import { InvocationTemplate, InvocationValue } from '../types/types'; -import { parseSchema } from '../util/parseSchema'; -import { log } from 'app/logging/useLogger'; -import { size } from 'lodash-es'; -import { isAnyGraphBuilt } from './actions'; import { RgbaColor } from 'react-colorful'; +import { RootState } from 'app/store/store'; export type NodesState = { nodes: Node[]; @@ -65,7 +62,7 @@ const nodesSlice = createSlice({ action: PayloadAction<{ nodeId: string; fieldName: string; - value: string | number | boolean | ImageDTO | RgbaColor | undefined; + value: string | number | boolean | ImageField | RgbaColor | undefined; }> ) => { const { nodeId, fieldName, value } = action.payload; @@ -78,30 +75,20 @@ const nodesSlice = createSlice({ shouldShowGraphOverlayChanged: (state, action: PayloadAction) => { state.shouldShowGraphOverlay = action.payload; }, - parsedOpenAPISchema: (state, action: PayloadAction) => { - try { - const parsedSchema = parseSchema(action.payload); - - // TODO: Achtung! Side effect in a reducer! - log.info( - { namespace: 'schema', nodes: parsedSchema }, - `Parsed ${size(parsedSchema)} nodes` - ); - state.invocationTemplates = parsedSchema; - } catch (err) { - console.error(err); - } + nodeTemplatesBuilt: ( + state, + action: PayloadAction> + ) => { + state.invocationTemplates = action.payload; + }, + nodeEditorReset: () => { + return { ...initialNodesState }; }, }, - extraReducers(builder) { + extraReducers: (builder) => { builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => { state.schema = action.payload; }); - - builder.addMatcher(isAnyGraphBuilt, (state, action) => { - // TODO: Achtung! Side effect in a reducer! - log.info({ namespace: 'nodes', data: action.payload }, 'Graph built'); - }); }, }); @@ -114,7 +101,10 @@ export const { connectionStarted, connectionEnded, shouldShowGraphOverlayChanged, - parsedOpenAPISchema, + nodeTemplatesBuilt, + nodeEditorReset, } = nodesSlice.actions; export default nodesSlice.reducer; + +export const nodesSelector = (state: RootState) => state.nodes; diff --git a/invokeai/frontend/web/src/features/nodes/types/constants.ts b/invokeai/frontend/web/src/features/nodes/types/constants.ts index a9ae209178..83fadb6bcb 100644 --- a/invokeai/frontend/web/src/features/nodes/types/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/types/constants.ts @@ -12,12 +12,17 @@ export const FIELD_TYPE_MAP: Record = { ImageField: 'image', LatentsField: 'latents', ConditioningField: 'conditioning', + UNetField: 'unet', + ClipField: 'clip', + VaeField: 'vae', model: 'model', array: 'array', item: 'item', ColorField: 'color', ControlField: 'control', control: 'control', + cfg_scale: 'float', + control_weight: 'float', }; const COLOR_TOKEN_VALUE = 500; @@ -77,6 +82,24 @@ export const FIELDS: Record = { title: 'Conditioning', description: 'Conditioning may be passed between nodes.', }, + unet: { + color: 'red', + colorCssVar: getColorTokenCssVariable('red'), + title: 'UNet', + description: 'UNet submodel.', + }, + clip: { + color: 'green', + colorCssVar: getColorTokenCssVariable('green'), + title: 'Clip', + description: 'Tokenizer and text_encoder submodels.', + }, + vae: { + color: 'blue', + colorCssVar: getColorTokenCssVariable('blue'), + title: 'Vae', + description: 'Vae submodel.', + }, control: { color: 'cyan', colorCssVar: getColorTokenCssVariable('cyan'), // TODO: no free color left diff --git a/invokeai/frontend/web/src/features/nodes/types/types.ts b/invokeai/frontend/web/src/features/nodes/types/types.ts index 745584f244..3faf2f9653 100644 --- a/invokeai/frontend/web/src/features/nodes/types/types.ts +++ b/invokeai/frontend/web/src/features/nodes/types/types.ts @@ -1,6 +1,6 @@ import { OpenAPIV3 } from 'openapi-types'; import { RgbaColor } from 'react-colorful'; -import { Graph, ImageDTO } from 'services/api'; +import { Graph, ImageDTO, ImageField } from 'services/api/types'; import { AnyInvocationType } from 'services/events/types'; import { O } from 'ts-toolbelt'; @@ -34,12 +34,10 @@ export type InvocationTemplate = { * Array of invocation inputs */ inputs: Record; - // inputs: InputField[]; /** * Array of the invocation outputs */ outputs: Record; - // outputs: OutputField[]; }; export type FieldUIConfig = { @@ -61,6 +59,9 @@ export type FieldType = | 'image' | 'latents' | 'conditioning' + | 'unet' + | 'clip' + | 'vae' | 'control' | 'model' | 'array' @@ -83,6 +84,9 @@ export type InputFieldValue = | ImageInputFieldValue | LatentsInputFieldValue | ConditioningInputFieldValue + | UNetInputFieldValue + | ClipInputFieldValue + | VaeInputFieldValue | ControlInputFieldValue | EnumInputFieldValue | ModelInputFieldValue @@ -104,6 +108,9 @@ export type InputFieldTemplate = | ImageInputFieldTemplate | LatentsInputFieldTemplate | ConditioningInputFieldTemplate + | UNetInputFieldTemplate + | ClipInputFieldTemplate + | VaeInputFieldTemplate | ControlInputFieldTemplate | EnumInputFieldTemplate | ModelInputFieldTemplate @@ -188,9 +195,24 @@ export type ControlInputFieldValue = FieldValueBase & { value?: undefined; }; +export type UNetInputFieldValue = FieldValueBase & { + type: 'unet'; + value?: undefined; +}; + +export type ClipInputFieldValue = FieldValueBase & { + type: 'clip'; + value?: undefined; +}; + +export type VaeInputFieldValue = FieldValueBase & { + type: 'vae'; + value?: undefined; +}; + export type ImageInputFieldValue = FieldValueBase & { type: 'image'; - value?: ImageDTO; + value?: ImageField; }; export type ModelInputFieldValue = FieldValueBase & { diff --git a/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts new file mode 100644 index 0000000000..11ceb23763 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts @@ -0,0 +1,100 @@ +import { RootState } from 'app/store/store'; +import { filter } from 'lodash-es'; +import { CollectInvocation, ControlNetInvocation } from 'services/api/types'; +import { NonNullableGraph } from '../types/types'; +import { CONTROL_NET_COLLECT } from './graphBuilders/constants'; + +export const addControlNetToLinearGraph = ( + graph: NonNullableGraph, + baseNodeId: string, + state: RootState +): void => { + const { isEnabled: isControlNetEnabled, controlNets } = state.controlNet; + + const validControlNets = filter( + controlNets, + (c) => + c.isEnabled && + (Boolean(c.processedControlImage) || + (c.processorType === 'none' && Boolean(c.controlImage))) + ); + + if (isControlNetEnabled && Boolean(validControlNets.length)) { + if (validControlNets.length > 1) { + // We have multiple controlnets, add ControlNet collector + const controlNetIterateNode: CollectInvocation = { + id: CONTROL_NET_COLLECT, + type: 'collect', + }; + graph.nodes[controlNetIterateNode.id] = controlNetIterateNode; + graph.edges.push({ + source: { node_id: controlNetIterateNode.id, field: 'collection' }, + destination: { + node_id: baseNodeId, + field: 'control', + }, + }); + } + + validControlNets.forEach((controlNet) => { + const { + controlNetId, + controlImage, + processedControlImage, + beginStepPct, + endStepPct, + controlMode, + model, + processorType, + weight, + } = controlNet; + + const controlNetNode: ControlNetInvocation = { + id: `control_net_${controlNetId}`, + type: 'controlnet', + begin_step_percent: beginStepPct, + end_step_percent: endStepPct, + control_mode: controlMode, + control_model: model as ControlNetInvocation['control_model'], + control_weight: weight, + }; + + if (processedControlImage && processorType !== 'none') { + // We've already processed the image in the app, so we can just use the processed image + controlNetNode.image = { + image_name: processedControlImage, + }; + } else if (controlImage) { + // The control image is preprocessed + controlNetNode.image = { + image_name: controlImage, + }; + } else { + // Skip ControlNets without an unprocessed image - should never happen if everything is working correctly + return; + } + + graph.nodes[controlNetNode.id] = controlNetNode; + + if (validControlNets.length > 1) { + // if we have multiple controlnets, link to the collector + graph.edges.push({ + source: { node_id: controlNetNode.id, field: 'control' }, + destination: { + node_id: CONTROL_NET_COLLECT, + field: 'item', + }, + }); + } else { + // otherwise, link directly to the base node + graph.edges.push({ + source: { node_id: controlNetNode.id, field: 'control' }, + destination: { + node_id: baseNodeId, + field: 'control', + }, + }); + } + }); + } +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/edgeBuilders/buildEdges.ts b/invokeai/frontend/web/src/features/nodes/util/edgeBuilders/buildEdges.ts index a1e9837647..1c3d2c909e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/edgeBuilders/buildEdges.ts +++ b/invokeai/frontend/web/src/features/nodes/util/edgeBuilders/buildEdges.ts @@ -6,7 +6,7 @@ import { RandomRangeInvocation, RangeInvocation, TextToImageInvocation, -} from 'services/api'; +} from 'services/api/types'; export const buildEdges = ( baseNode: TextToImageInvocation | ImageToImageInvocation | InpaintInvocation, diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts index e1f65e8826..f1ad731d32 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts @@ -10,6 +10,9 @@ import { IntegerInputFieldTemplate, LatentsInputFieldTemplate, ConditioningInputFieldTemplate, + UNetInputFieldTemplate, + ClipInputFieldTemplate, + VaeInputFieldTemplate, ControlInputFieldTemplate, StringInputFieldTemplate, ModelInputFieldTemplate, @@ -216,6 +219,51 @@ const buildConditioningInputFieldTemplate = ({ return template; }; +const buildUNetInputFieldTemplate = ({ + schemaObject, + baseField, +}: BuildInputFieldArg): UNetInputFieldTemplate => { + const template: UNetInputFieldTemplate = { + ...baseField, + type: 'unet', + inputRequirement: 'always', + inputKind: 'connection', + default: schemaObject.default ?? undefined, + }; + + return template; +}; + +const buildClipInputFieldTemplate = ({ + schemaObject, + baseField, +}: BuildInputFieldArg): ClipInputFieldTemplate => { + const template: ClipInputFieldTemplate = { + ...baseField, + type: 'clip', + inputRequirement: 'always', + inputKind: 'connection', + default: schemaObject.default ?? undefined, + }; + + return template; +}; + +const buildVaeInputFieldTemplate = ({ + schemaObject, + baseField, +}: BuildInputFieldArg): VaeInputFieldTemplate => { + const template: VaeInputFieldTemplate = { + ...baseField, + type: 'vae', + inputRequirement: 'always', + inputKind: 'connection', + default: schemaObject.default ?? undefined, + }; + + return template; +}; + const buildControlInputFieldTemplate = ({ schemaObject, baseField, @@ -358,6 +406,15 @@ export const buildInputFieldTemplate = ( if (['conditioning'].includes(fieldType)) { return buildConditioningInputFieldTemplate({ schemaObject, baseField }); } + if (['unet'].includes(fieldType)) { + return buildUNetInputFieldTemplate({ schemaObject, baseField }); + } + if (['clip'].includes(fieldType)) { + return buildClipInputFieldTemplate({ schemaObject, baseField }); + } + if (['vae'].includes(fieldType)) { + return buildVaeInputFieldTemplate({ schemaObject, baseField }); + } if (['control'].includes(fieldType)) { return buildControlInputFieldTemplate({ schemaObject, baseField }); } diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts index 0b10a3e464..1703c45331 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts @@ -52,6 +52,18 @@ export const buildInputFieldValue = ( fieldValue.value = undefined; } + if (template.type === 'unet') { + fieldValue.value = undefined; + } + + if (template.type === 'clip') { + fieldValue.value = undefined; + } + + if (template.type === 'vae') { + fieldValue.value = undefined; + } + if (template.type === 'control') { fieldValue.value = undefined; } diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addDynamicPromptsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addDynamicPromptsToGraph.ts new file mode 100644 index 0000000000..264dac2e14 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addDynamicPromptsToGraph.ts @@ -0,0 +1,153 @@ +import { RootState } from 'app/store/store'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { + DynamicPromptInvocation, + IterateInvocation, + NoiseInvocation, + RandomIntInvocation, + RangeOfSizeInvocation, +} from 'services/api/types'; +import { + DYNAMIC_PROMPT, + ITERATE, + NOISE, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, +} from './constants'; +import { unset } from 'lodash-es'; + +export const addDynamicPromptsToGraph = ( + graph: NonNullableGraph, + state: RootState +): void => { + const { positivePrompt, iterations, seed, shouldRandomizeSeed } = + state.generation; + + const { + combinatorial, + isEnabled: isDynamicPromptsEnabled, + maxPrompts, + } = state.dynamicPrompts; + + if (isDynamicPromptsEnabled) { + // iteration is handled via dynamic prompts + unset(graph.nodes[POSITIVE_CONDITIONING], 'prompt'); + + const dynamicPromptNode: DynamicPromptInvocation = { + id: DYNAMIC_PROMPT, + type: 'dynamic_prompt', + max_prompts: combinatorial ? maxPrompts : iterations, + combinatorial, + prompt: positivePrompt, + }; + + const iterateNode: IterateInvocation = { + id: ITERATE, + type: 'iterate', + }; + + graph.nodes[DYNAMIC_PROMPT] = dynamicPromptNode; + graph.nodes[ITERATE] = iterateNode; + + // connect dynamic prompts to compel nodes + graph.edges.push( + { + source: { + node_id: DYNAMIC_PROMPT, + field: 'prompt_collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }, + { + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'prompt', + }, + } + ); + + if (shouldRandomizeSeed) { + // Random int node to generate the starting seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + + // Connect random int to the start of the range of size so the range starts on the random first seed + graph.edges.push({ + source: { node_id: RANDOM_INT, field: 'a' }, + destination: { node_id: NOISE, field: 'seed' }, + }); + } else { + // User specified seed, so set the start of the range of size to the seed + (graph.nodes[NOISE] as NoiseInvocation).seed = seed; + } + } else { + const rangeOfSizeNode: RangeOfSizeInvocation = { + id: RANGE_OF_SIZE, + type: 'range_of_size', + size: iterations, + step: 1, + }; + + const iterateNode: IterateInvocation = { + id: ITERATE, + type: 'iterate', + }; + + graph.nodes[ITERATE] = iterateNode; + graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; + + graph.edges.push({ + source: { + node_id: RANGE_OF_SIZE, + field: 'collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }); + + graph.edges.push({ + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: NOISE, + field: 'seed', + }, + }); + + // handle seed + if (shouldRandomizeSeed) { + // Random int node to generate the starting seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + + // Connect random int to the start of the range of size so the range starts on the random first seed + graph.edges.push({ + source: { node_id: RANDOM_INT, field: 'a' }, + destination: { node_id: RANGE_OF_SIZE, field: 'start' }, + }); + } else { + // User specified seed, so set the start of the range of size to the seed + rangeOfSizeNode.start = seed; + } + } +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts index 3615f7d298..3b2816844b 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasGraph.ts @@ -1,108 +1,39 @@ import { RootState } from 'app/store/store'; -import { - Edge, - ImageToImageInvocation, - InpaintInvocation, - IterateInvocation, - RandomRangeInvocation, - RangeInvocation, - TextToImageInvocation, -} from 'services/api'; -import { buildImg2ImgNode } from '../nodeBuilders/buildImageToImageNode'; -import { buildTxt2ImgNode } from '../nodeBuilders/buildTextToImageNode'; -import { buildRangeNode } from '../nodeBuilders/buildRangeNode'; -import { buildIterateNode } from '../nodeBuilders/buildIterateNode'; -import { buildEdges } from '../edgeBuilders/buildEdges'; +import { ImageDTO } from 'services/api/types'; import { log } from 'app/logging/useLogger'; -import { buildInpaintNode } from '../nodeBuilders/buildInpaintNode'; +import { forEach } from 'lodash-es'; +import { buildCanvasInpaintGraph } from './buildCanvasInpaintGraph'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { buildCanvasImageToImageGraph } from './buildCanvasImageToImageGraph'; +import { buildCanvasTextToImageGraph } from './buildCanvasTextToImageGraph'; -const moduleLog = log.child({ namespace: 'buildCanvasGraph' }); +const moduleLog = log.child({ namespace: 'nodes' }); -const buildBaseNode = ( - nodeType: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint', - state: RootState -): - | TextToImageInvocation - | ImageToImageInvocation - | InpaintInvocation - | undefined => { - const dimensionsOverride = state.canvas.boundingBoxDimensions; - - if (nodeType === 'txt2img') { - return buildTxt2ImgNode(state, dimensionsOverride); - } - - if (nodeType === 'img2img') { - return buildImg2ImgNode(state, dimensionsOverride); - } - - if (nodeType === 'inpaint' || nodeType === 'outpaint') { - return buildInpaintNode(state, dimensionsOverride); - } -}; - -/** - * Builds the Canvas workflow graph and image blobs. - */ -export const buildCanvasGraphComponents = async ( +export const buildCanvasGraph = ( state: RootState, - generationMode: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint' -): Promise< - | { - rangeNode: RangeInvocation | RandomRangeInvocation; - iterateNode: IterateInvocation; - baseNode: - | TextToImageInvocation - | ImageToImageInvocation - | InpaintInvocation; - edges: Edge[]; + generationMode: 'txt2img' | 'img2img' | 'inpaint' | 'outpaint', + canvasInitImage: ImageDTO | undefined, + canvasMaskImage: ImageDTO | undefined +) => { + let graph: NonNullableGraph; + + if (generationMode === 'txt2img') { + graph = buildCanvasTextToImageGraph(state); + } else if (generationMode === 'img2img') { + if (!canvasInitImage) { + throw new Error('Missing canvas init image'); } - | undefined -> => { - // The base node is a txt2img, img2img or inpaint node - const baseNode = buildBaseNode(generationMode, state); - - if (!baseNode) { - moduleLog.error('Problem building base node'); - return; + graph = buildCanvasImageToImageGraph(state, canvasInitImage); + } else { + if (!canvasInitImage || !canvasMaskImage) { + throw new Error('Missing canvas init and mask images'); + } + graph = buildCanvasInpaintGraph(state, canvasInitImage, canvasMaskImage); } - if (baseNode.type === 'inpaint') { - const { - seamSize, - seamBlur, - seamSteps, - seamStrength, - tileSize, - infillMethod, - } = state.generation; + forEach(graph.nodes, (node) => { + graph.nodes[node.id].is_intermediate = true; + }); - // generationParameters.invert_mask = shouldPreserveMaskedArea; - // if (boundingBoxScale !== 'none') { - // generationParameters.inpaint_width = scaledBoundingBoxDimensions.width; - // generationParameters.inpaint_height = scaledBoundingBoxDimensions.height; - // } - baseNode.seam_size = seamSize; - baseNode.seam_blur = seamBlur; - baseNode.seam_strength = seamStrength; - baseNode.seam_steps = seamSteps; - baseNode.tile_size = tileSize; - baseNode.infill_method = infillMethod as InpaintInvocation['infill_method']; - // baseNode.force_outpaint = false; - } - - // We always range and iterate nodes, no matter the iteration count - // This is required to provide the correct seeds to the backend engine - const rangeNode = buildRangeNode(state); - const iterateNode = buildIterateNode(); - - // Build the edges for the nodes selected. - const edges = buildEdges(baseNode, rangeNode, iterateNode); - - return { - rangeNode, - iterateNode, - baseNode, - edges, - }; + return graph; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts new file mode 100644 index 0000000000..49bab291f7 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasImageToImageGraph.ts @@ -0,0 +1,287 @@ +import { RootState } from 'app/store/store'; +import { + ImageDTO, + ImageResizeInvocation, + ImageToLatentsInvocation, + RandomIntInvocation, + RangeOfSizeInvocation, +} from 'services/api/types'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { log } from 'app/logging/useLogger'; +import { + ITERATE, + LATENTS_TO_IMAGE, + PIPELINE_MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + IMAGE_TO_IMAGE_GRAPH, + IMAGE_TO_LATENTS, + LATENTS_TO_LATENTS, + RESIZE, +} from './constants'; +import { set } from 'lodash-es'; +import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; +import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; + +const moduleLog = log.child({ namespace: 'nodes' }); + +/** + * Builds the Canvas tab's Image to Image graph. + */ +export const buildCanvasImageToImageGraph = ( + state: RootState, + initialImage: ImageDTO +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: modelId, + cfgScale: cfg_scale, + scheduler, + steps, + img2imgStrength: strength, + iterations, + seed, + shouldRandomizeSeed, + } = state.generation; + + // The bounding box determines width and height, not the width and height params + const { width, height } = state.canvas.boundingBoxDimensions; + + const model = modelIdToPipelineModelField(modelId); + + /** + * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the + * full graph here as a template. Then use the parameters from app state and set friendlier node + * ids. + * + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. + */ + + // copy-pasted graph from node editor, filled in with state values & friendly node ids + const graph: NonNullableGraph = { + id: IMAGE_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + }, + [PIPELINE_MODEL_LOADER]: { + type: 'pipeline_model_loader', + id: PIPELINE_MODEL_LOADER, + model, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + [LATENTS_TO_LATENTS]: { + type: 'l2l', + id: LATENTS_TO_LATENTS, + cfg_scale, + scheduler, + steps, + strength, + }, + [IMAGE_TO_LATENTS]: { + type: 'i2l', + id: IMAGE_TO_LATENTS, + // must be set manually later, bc `fit` parameter may require a resize node inserted + // image: { + // image_name: initialImage.image_name, + // }, + }, + }, + edges: [ + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: IMAGE_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'vae', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + ], + }; + + // handle `fit` + if (initialImage.width !== width || initialImage.height !== height) { + // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` + + // Create a resize node, explicitly setting its image + const resizeNode: ImageResizeInvocation = { + id: RESIZE, + type: 'img_resize', + image: { + image_name: initialImage.image_name, + }, + is_intermediate: true, + width, + height, + }; + + graph.nodes[RESIZE] = resizeNode; + + // The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS` + graph.edges.push({ + source: { node_id: RESIZE, field: 'image' }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'image', + }, + }); + + // The `RESIZE` node also passes its width and height to `NOISE` + graph.edges.push({ + source: { node_id: RESIZE, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + + graph.edges.push({ + source: { node_id: RESIZE, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } else { + // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly + (graph.nodes[IMAGE_TO_LATENTS] as ImageToLatentsInvocation).image = { + image_name: initialImage.image_name, + }; + + // Pass the image's dimensions to the `NOISE` node + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } + + // add dynamic prompts, mutating `graph` + addDynamicPromptsToGraph(graph, state); + + // add controlnet, mutating `graph` + addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts new file mode 100644 index 0000000000..74bd12a742 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasInpaintGraph.ts @@ -0,0 +1,227 @@ +import { RootState } from 'app/store/store'; +import { + ImageDTO, + InpaintInvocation, + RandomIntInvocation, + RangeOfSizeInvocation, +} from 'services/api/types'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { log } from 'app/logging/useLogger'; +import { + ITERATE, + PIPELINE_MODEL_LOADER, + NEGATIVE_CONDITIONING, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + INPAINT_GRAPH, + INPAINT, +} from './constants'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; + +const moduleLog = log.child({ namespace: 'nodes' }); + +/** + * Builds the Canvas tab's Inpaint graph. + */ +export const buildCanvasInpaintGraph = ( + state: RootState, + canvasInitImage: ImageDTO, + canvasMaskImage: ImageDTO +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: modelId, + cfgScale: cfg_scale, + scheduler, + steps, + img2imgStrength: strength, + shouldFitToWidthHeight, + iterations, + seed, + shouldRandomizeSeed, + seamSize, + seamBlur, + seamSteps, + seamStrength, + tileSize, + infillMethod, + } = state.generation; + + // The bounding box determines width and height, not the width and height params + const { width, height } = state.canvas.boundingBoxDimensions; + + // We may need to set the inpaint width and height to scale the image + const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas; + + const model = modelIdToPipelineModelField(modelId); + + const graph: NonNullableGraph = { + id: INPAINT_GRAPH, + nodes: { + [INPAINT]: { + type: 'inpaint', + id: INPAINT, + steps, + width, + height, + cfg_scale, + scheduler, + image: { + image_name: canvasInitImage.image_name, + }, + strength, + fit: shouldFitToWidthHeight, + mask: { + image_name: canvasMaskImage.image_name, + }, + seam_size: seamSize, + seam_blur: seamBlur, + seam_strength: seamStrength, + seam_steps: seamSteps, + tile_size: infillMethod === 'tile' ? tileSize : undefined, + infill_method: infillMethod as InpaintInvocation['infill_method'], + inpaint_width: + boundingBoxScaleMethod !== 'none' + ? scaledBoundingBoxDimensions.width + : undefined, + inpaint_height: + boundingBoxScaleMethod !== 'none' + ? scaledBoundingBoxDimensions.height + : undefined, + }, + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [PIPELINE_MODEL_LOADER]: { + type: 'pipeline_model_loader', + id: PIPELINE_MODEL_LOADER, + model, + }, + [RANGE_OF_SIZE]: { + type: 'range_of_size', + id: RANGE_OF_SIZE, + // seed - must be connected manually + // start: 0, + size: iterations, + step: 1, + }, + [ITERATE]: { + type: 'iterate', + id: ITERATE, + }, + }, + edges: [ + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: INPAINT, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: INPAINT, + field: 'positive_conditioning', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: INPAINT, + field: 'unet', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: INPAINT, + field: 'vae', + }, + }, + { + source: { + node_id: RANGE_OF_SIZE, + field: 'collection', + }, + destination: { + node_id: ITERATE, + field: 'collection', + }, + }, + { + source: { + node_id: ITERATE, + field: 'item', + }, + destination: { + node_id: INPAINT, + field: 'seed', + }, + }, + ], + }; + + // handle seed + if (shouldRandomizeSeed) { + // Random int node to generate the starting seed + const randomIntNode: RandomIntInvocation = { + id: RANDOM_INT, + type: 'rand_int', + }; + + graph.nodes[RANDOM_INT] = randomIntNode; + + // Connect random int to the start of the range of size so the range starts on the random first seed + graph.edges.push({ + source: { node_id: RANDOM_INT, field: 'a' }, + destination: { node_id: RANGE_OF_SIZE, field: 'start' }, + }); + } else { + // User specified seed, so set the start of the range of size to the seed + (graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed; + } + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts new file mode 100644 index 0000000000..b15b2cd192 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildCanvasTextToImageGraph.ts @@ -0,0 +1,180 @@ +import { RootState } from 'app/store/store'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { RandomIntInvocation, RangeOfSizeInvocation } from 'services/api/types'; +import { + ITERATE, + LATENTS_TO_IMAGE, + PIPELINE_MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + RANDOM_INT, + RANGE_OF_SIZE, + TEXT_TO_IMAGE_GRAPH, + TEXT_TO_LATENTS, +} from './constants'; +import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; +import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; + +/** + * Builds the Canvas tab's Text to Image graph. + */ +export const buildCanvasTextToImageGraph = ( + state: RootState +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: modelId, + cfgScale: cfg_scale, + scheduler, + steps, + iterations, + seed, + shouldRandomizeSeed, + } = state.generation; + + // The bounding box determines width and height, not the width and height params + const { width, height } = state.canvas.boundingBoxDimensions; + + const model = modelIdToPipelineModelField(modelId); + + /** + * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the + * full graph here as a template. Then use the parameters from app state and set friendlier node + * ids. + * + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. + */ + + // copy-pasted graph from node editor, filled in with state values & friendly node ids + const graph: NonNullableGraph = { + id: TEXT_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + width, + height, + }, + [TEXT_TO_LATENTS]: { + type: 't2l', + id: TEXT_TO_LATENTS, + cfg_scale, + scheduler, + steps, + }, + [PIPELINE_MODEL_LOADER]: { + type: 'pipeline_model_loader', + id: PIPELINE_MODEL_LOADER, + model, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + }, + edges: [ + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: TEXT_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'noise', + }, + }, + ], + }; + + // add dynamic prompts, mutating `graph` + addDynamicPromptsToGraph(graph, state); + + // add controlnet, mutating `graph` + addControlNetToLinearGraph(graph, TEXT_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts deleted file mode 100644 index d9eb80d654..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildImageToImageGraph.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { RootState } from 'app/store/store'; -import { - CompelInvocation, - Graph, - ImageToLatentsInvocation, - LatentsToImageInvocation, - LatentsToLatentsInvocation, -} from 'services/api'; -import { NonNullableGraph } from 'features/nodes/types/types'; -import { addNoiseNodes } from '../nodeBuilders/addNoiseNodes'; -import { log } from 'app/logging/useLogger'; - -const moduleLog = log.child({ namespace: 'buildImageToImageGraph' }); - -const POSITIVE_CONDITIONING = 'positive_conditioning'; -const NEGATIVE_CONDITIONING = 'negative_conditioning'; -const IMAGE_TO_LATENTS = 'image_to_latents'; -const LATENTS_TO_LATENTS = 'latents_to_latents'; -const LATENTS_TO_IMAGE = 'latents_to_image'; - -/** - * Builds the Image to Image tab graph. - */ -export const buildImageToImageGraph = (state: RootState): Graph => { - const { - positivePrompt, - negativePrompt, - model, - cfgScale: cfg_scale, - scheduler, - steps, - initialImage, - img2imgStrength: strength, - } = state.generation; - - if (!initialImage) { - moduleLog.error('No initial image found in state'); - throw new Error('No initial image found in state'); - } - - let graph: NonNullableGraph = { - nodes: {}, - edges: [], - }; - - // Create the conditioning, t2l and l2i nodes - const positiveConditioningNode: CompelInvocation = { - id: POSITIVE_CONDITIONING, - type: 'compel', - prompt: positivePrompt, - model, - }; - - const negativeConditioningNode: CompelInvocation = { - id: NEGATIVE_CONDITIONING, - type: 'compel', - prompt: negativePrompt, - model, - }; - - const imageToLatentsNode: ImageToLatentsInvocation = { - id: IMAGE_TO_LATENTS, - type: 'i2l', - model, - image: { - image_name: initialImage?.image_name, - image_type: initialImage?.image_type, - }, - }; - - const latentsToLatentsNode: LatentsToLatentsInvocation = { - id: LATENTS_TO_LATENTS, - type: 'l2l', - cfg_scale, - model, - scheduler, - steps, - strength, - }; - - const latentsToImageNode: LatentsToImageInvocation = { - id: LATENTS_TO_IMAGE, - type: 'l2i', - model, - }; - - // Add to the graph - graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode; - graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode; - graph.nodes[IMAGE_TO_LATENTS] = imageToLatentsNode; - graph.nodes[LATENTS_TO_LATENTS] = latentsToLatentsNode; - graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode; - - // Connect them - graph.edges.push({ - source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'positive_conditioning', - }, - }); - - graph.edges.push({ - source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'negative_conditioning', - }, - }); - - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'latents' }, - destination: { - node_id: LATENTS_TO_LATENTS, - field: 'latents', - }, - }); - - graph.edges.push({ - source: { node_id: LATENTS_TO_LATENTS, field: 'latents' }, - destination: { - node_id: LATENTS_TO_IMAGE, - field: 'latents', - }, - }); - - // Create and add the noise nodes - graph = addNoiseNodes(graph, latentsToLatentsNode.id, state); - - return graph; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts new file mode 100644 index 0000000000..15d5a431a2 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearImageToImageGraph.ts @@ -0,0 +1,285 @@ +import { RootState } from 'app/store/store'; +import { + ImageResizeInvocation, + ImageToLatentsInvocation, +} from 'services/api/types'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { log } from 'app/logging/useLogger'; +import { + LATENTS_TO_IMAGE, + PIPELINE_MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + IMAGE_TO_IMAGE_GRAPH, + IMAGE_TO_LATENTS, + LATENTS_TO_LATENTS, + RESIZE, +} from './constants'; +import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; +import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; + +const moduleLog = log.child({ namespace: 'nodes' }); + +/** + * Builds the Image to Image tab graph. + */ +export const buildLinearImageToImageGraph = ( + state: RootState +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: modelId, + cfgScale: cfg_scale, + scheduler, + steps, + initialImage, + img2imgStrength: strength, + shouldFitToWidthHeight, + width, + height, + } = state.generation; + + /** + * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the + * full graph here as a template. Then use the parameters from app state and set friendlier node + * ids. + * + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. + */ + + if (!initialImage) { + moduleLog.error('No initial image found in state'); + throw new Error('No initial image found in state'); + } + + const model = modelIdToPipelineModelField(modelId); + + // copy-pasted graph from node editor, filled in with state values & friendly node ids + const graph: NonNullableGraph = { + id: IMAGE_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + }, + [PIPELINE_MODEL_LOADER]: { + type: 'pipeline_model_loader', + id: PIPELINE_MODEL_LOADER, + model, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + [LATENTS_TO_LATENTS]: { + type: 'l2l', + id: LATENTS_TO_LATENTS, + cfg_scale, + scheduler, + steps, + strength, + }, + [IMAGE_TO_LATENTS]: { + type: 'i2l', + id: IMAGE_TO_LATENTS, + // must be set manually later, bc `fit` parameter may require a resize node inserted + // image: { + // image_name: initialImage.image_name, + // }, + }, + }, + edges: [ + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: IMAGE_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'latents', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'noise', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'vae', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: LATENTS_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + ], + }; + + // handle `fit` + if ( + shouldFitToWidthHeight && + (initialImage.width !== width || initialImage.height !== height) + ) { + // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` + + // Create a resize node, explicitly setting its image + const resizeNode: ImageResizeInvocation = { + id: RESIZE, + type: 'img_resize', + image: { + image_name: initialImage.imageName, + }, + is_intermediate: true, + width, + height, + }; + + graph.nodes[RESIZE] = resizeNode; + + // The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS` + graph.edges.push({ + source: { node_id: RESIZE, field: 'image' }, + destination: { + node_id: IMAGE_TO_LATENTS, + field: 'image', + }, + }); + + // The `RESIZE` node also passes its width and height to `NOISE` + graph.edges.push({ + source: { node_id: RESIZE, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + + graph.edges.push({ + source: { node_id: RESIZE, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } else { + // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly + (graph.nodes[IMAGE_TO_LATENTS] as ImageToLatentsInvocation).image = { + image_name: initialImage.imageName, + }; + + // Pass the image's dimensions to the `NOISE` node + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'width' }, + destination: { + node_id: NOISE, + field: 'width', + }, + }); + graph.edges.push({ + source: { node_id: IMAGE_TO_LATENTS, field: 'height' }, + destination: { + node_id: NOISE, + field: 'height', + }, + }); + } + + // add dynamic prompts, mutating `graph` + addDynamicPromptsToGraph(graph, state); + + // add controlnet, mutating `graph` + addControlNetToLinearGraph(graph, LATENTS_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts new file mode 100644 index 0000000000..216c5c8c67 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts @@ -0,0 +1,169 @@ +import { RootState } from 'app/store/store'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { + LATENTS_TO_IMAGE, + PIPELINE_MODEL_LOADER, + NEGATIVE_CONDITIONING, + NOISE, + POSITIVE_CONDITIONING, + TEXT_TO_IMAGE_GRAPH, + TEXT_TO_LATENTS, +} from './constants'; +import { addControlNetToLinearGraph } from '../addControlNetToLinearGraph'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; +import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; + +export const buildLinearTextToImageGraph = ( + state: RootState +): NonNullableGraph => { + const { + positivePrompt, + negativePrompt, + model: modelId, + cfgScale: cfg_scale, + scheduler, + steps, + width, + height, + } = state.generation; + + const model = modelIdToPipelineModelField(modelId); + + /** + * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the + * full graph here as a template. Then use the parameters from app state and set friendlier node + * ids. + * + * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, + * the `fit` param. These are added to the graph at the end. + */ + + // copy-pasted graph from node editor, filled in with state values & friendly node ids + const graph: NonNullableGraph = { + id: TEXT_TO_IMAGE_GRAPH, + nodes: { + [POSITIVE_CONDITIONING]: { + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }, + [NEGATIVE_CONDITIONING]: { + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }, + [NOISE]: { + type: 'noise', + id: NOISE, + width, + height, + }, + [TEXT_TO_LATENTS]: { + type: 't2l', + id: TEXT_TO_LATENTS, + cfg_scale, + scheduler, + steps, + }, + [PIPELINE_MODEL_LOADER]: { + type: 'pipeline_model_loader', + id: PIPELINE_MODEL_LOADER, + model, + }, + [LATENTS_TO_IMAGE]: { + type: 'l2i', + id: LATENTS_TO_IMAGE, + }, + }, + edges: [ + { + source: { + node_id: NEGATIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'negative_conditioning', + }, + }, + { + source: { + node_id: POSITIVE_CONDITIONING, + field: 'conditioning', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'positive_conditioning', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'unet', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'unet', + }, + }, + { + source: { + node_id: TEXT_TO_LATENTS, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'latents', + }, + }, + { + source: { + node_id: PIPELINE_MODEL_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE, + field: 'vae', + }, + }, + { + source: { + node_id: NOISE, + field: 'noise', + }, + destination: { + node_id: TEXT_TO_LATENTS, + field: 'noise', + }, + }, + ], + }; + + // add dynamic prompts, mutating `graph` + addDynamicPromptsToGraph(graph, state); + + // add controlnet, mutating `graph` + addControlNetToLinearGraph(graph, TEXT_TO_LATENTS, state); + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts index eef7379624..091899a21a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts @@ -1,8 +1,10 @@ -import { Graph } from 'services/api'; +import { Graph } from 'services/api/types'; import { v4 as uuidv4 } from 'uuid'; -import { cloneDeep, reduce } from 'lodash-es'; +import { cloneDeep, omit, reduce } from 'lodash-es'; import { RootState } from 'app/store/store'; import { InputFieldValue } from 'features/nodes/types/types'; +import { AnyInvocation } from 'services/events/types'; +import { modelIdToPipelineModelField } from '../modelIdToPipelineModelField'; /** * We need to do special handling for some fields @@ -23,6 +25,12 @@ export const parseFieldValue = (field: InputFieldValue) => { } } + if (field.type === 'model') { + if (field.value) { + return modelIdToPipelineModelField(field.value); + } + } + return field.value; }; @@ -89,6 +97,24 @@ export const buildNodesGraph = (state: RootState): Graph => { [] ); + /** + * Omit all inputs that have edges connected. + * + * Fixes edge case where the user has connected an input, but also provided an invalid explicit, + * value. + * + * In this edge case, pydantic will invalidate the node based on the invalid explicit value, + * even though the actual value that will be used comes from the connection. + */ + parsedEdges.forEach((edge) => { + const destination_node = parsedNodes[edge.destination.node_id]; + const field = edge.destination.field; + parsedNodes[edge.destination.node_id] = omit( + destination_node, + field + ) as AnyInvocation; + }); + // Assemble! const graph = { id: uuidv4(), diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts deleted file mode 100644 index 51f89e8f74..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { RootState } from 'app/store/store'; -import { - CompelInvocation, - Graph, - LatentsToImageInvocation, - TextToLatentsInvocation, -} from 'services/api'; -import { NonNullableGraph } from 'features/nodes/types/types'; -import { addNoiseNodes } from '../nodeBuilders/addNoiseNodes'; - -const POSITIVE_CONDITIONING = 'positive_conditioning'; -const NEGATIVE_CONDITIONING = 'negative_conditioning'; -const TEXT_TO_LATENTS = 'text_to_latents'; -const LATENTS_TO_IMAGE = 'latents_to_image'; - -/** - * Builds the Text to Image tab graph. - */ -export const buildTextToImageGraph = (state: RootState): Graph => { - const { - positivePrompt, - negativePrompt, - model, - cfgScale: cfg_scale, - scheduler, - steps, - } = state.generation; - - let graph: NonNullableGraph = { - nodes: {}, - edges: [], - }; - - // Create the conditioning, t2l and l2i nodes - const positiveConditioningNode: CompelInvocation = { - id: POSITIVE_CONDITIONING, - type: 'compel', - prompt: positivePrompt, - model, - }; - - const negativeConditioningNode: CompelInvocation = { - id: NEGATIVE_CONDITIONING, - type: 'compel', - prompt: negativePrompt, - model, - }; - - const textToLatentsNode: TextToLatentsInvocation = { - id: TEXT_TO_LATENTS, - type: 't2l', - cfg_scale, - model, - scheduler, - steps, - }; - - const latentsToImageNode: LatentsToImageInvocation = { - id: LATENTS_TO_IMAGE, - type: 'l2i', - model, - }; - - // Add to the graph - graph.nodes[POSITIVE_CONDITIONING] = positiveConditioningNode; - graph.nodes[NEGATIVE_CONDITIONING] = negativeConditioningNode; - graph.nodes[TEXT_TO_LATENTS] = textToLatentsNode; - graph.nodes[LATENTS_TO_IMAGE] = latentsToImageNode; - - // Connect them - graph.edges.push({ - source: { node_id: POSITIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: TEXT_TO_LATENTS, - field: 'positive_conditioning', - }, - }); - - graph.edges.push({ - source: { node_id: NEGATIVE_CONDITIONING, field: 'conditioning' }, - destination: { - node_id: TEXT_TO_LATENTS, - field: 'negative_conditioning', - }, - }); - - graph.edges.push({ - source: { node_id: TEXT_TO_LATENTS, field: 'latents' }, - destination: { - node_id: LATENTS_TO_IMAGE, - field: 'latents', - }, - }); - - // Create and add the noise nodes - graph = addNoiseNodes(graph, TEXT_TO_LATENTS, state); - - return graph; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts new file mode 100644 index 0000000000..d6ab33a6ea --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts @@ -0,0 +1,21 @@ +// friendly node ids +export const POSITIVE_CONDITIONING = 'positive_conditioning'; +export const NEGATIVE_CONDITIONING = 'negative_conditioning'; +export const TEXT_TO_LATENTS = 'text_to_latents'; +export const LATENTS_TO_IMAGE = 'latents_to_image'; +export const NOISE = 'noise'; +export const RANDOM_INT = 'rand_int'; +export const RANGE_OF_SIZE = 'range_of_size'; +export const ITERATE = 'iterate'; +export const PIPELINE_MODEL_LOADER = 'pipeline_model_loader'; +export const IMAGE_TO_LATENTS = 'image_to_latents'; +export const LATENTS_TO_LATENTS = 'latents_to_latents'; +export const RESIZE = 'resize_image'; +export const INPAINT = 'inpaint'; +export const CONTROL_NET_COLLECT = 'control_net_collect'; +export const DYNAMIC_PROMPT = 'dynamic_prompt'; + +// friendly graph ids +export const TEXT_TO_IMAGE_GRAPH = 'text_to_image_graph'; +export const IMAGE_TO_IMAGE_GRAPH = 'image_to_image_graph'; +export const INPAINT_GRAPH = 'inpaint_graph'; diff --git a/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts b/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts new file mode 100644 index 0000000000..0941255181 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/modelIdToPipelineModelField.ts @@ -0,0 +1,18 @@ +import { BaseModelType, PipelineModelField } from 'services/api/types'; + +/** + * Crudely converts a model id to a pipeline model field + * TODO: Make better + */ +export const modelIdToPipelineModelField = ( + modelId: string +): PipelineModelField => { + const [base_model, model_type, model_name] = modelId.split('/'); + + const field: PipelineModelField = { + base_model: base_model as BaseModelType, + model_name, + }; + + return field; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/addNoiseNodes.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/addNoiseNodes.ts deleted file mode 100644 index ba3d4d8168..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/addNoiseNodes.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { RootState } from 'app/store/store'; -import { - IterateInvocation, - NoiseInvocation, - RandomIntInvocation, - RangeOfSizeInvocation, -} from 'services/api'; -import { NonNullableGraph } from 'features/nodes/types/types'; -import { cloneDeep } from 'lodash-es'; - -const NOISE = 'noise'; -const RANDOM_INT = 'rand_int'; -const RANGE_OF_SIZE = 'range_of_size'; -const ITERATE = 'iterate'; -/** - * Adds the appropriate noise nodes to a linear UI t2l or l2l graph. - * - * @param graph The graph to add the noise nodes to. - * @param baseNodeId The id of the base node to connect the noise nodes to. - * @param state The app state.. - */ -export const addNoiseNodes = ( - graph: NonNullableGraph, - baseNodeId: string, - state: RootState -): NonNullableGraph => { - const graphClone = cloneDeep(graph); - - // Create and add the noise nodes - const { width, height, seed, iterations, shouldRandomizeSeed } = - state.generation; - - // Single iteration, explicit seed - if (!shouldRandomizeSeed && iterations === 1) { - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - seed: seed, - width, - height, - }; - - graphClone.nodes[NOISE] = noiseNode; - - // Connect them - graphClone.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: baseNodeId, - field: 'noise', - }, - }); - } - - // Single iteration, random seed - if (shouldRandomizeSeed && iterations === 1) { - // TODO: This assumes the `high` value is the max seed value - const randomIntNode: RandomIntInvocation = { - id: RANDOM_INT, - type: 'rand_int', - }; - - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - width, - height, - }; - - graphClone.nodes[RANDOM_INT] = randomIntNode; - graphClone.nodes[NOISE] = noiseNode; - - graphClone.edges.push({ - source: { node_id: RANDOM_INT, field: 'a' }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - graphClone.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: baseNodeId, - field: 'noise', - }, - }); - } - - // Multiple iterations, explicit seed - if (!shouldRandomizeSeed && iterations > 1) { - const rangeOfSizeNode: RangeOfSizeInvocation = { - id: RANGE_OF_SIZE, - type: 'range_of_size', - start: seed, - size: iterations, - }; - - const iterateNode: IterateInvocation = { - id: ITERATE, - type: 'iterate', - }; - - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - width, - height, - }; - - graphClone.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; - graphClone.nodes[ITERATE] = iterateNode; - graphClone.nodes[NOISE] = noiseNode; - - graphClone.edges.push({ - source: { node_id: RANGE_OF_SIZE, field: 'collection' }, - destination: { - node_id: ITERATE, - field: 'collection', - }, - }); - - graphClone.edges.push({ - source: { - node_id: ITERATE, - field: 'item', - }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - graphClone.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: baseNodeId, - field: 'noise', - }, - }); - } - - // Multiple iterations, random seed - if (shouldRandomizeSeed && iterations > 1) { - // TODO: This assumes the `high` value is the max seed value - const randomIntNode: RandomIntInvocation = { - id: RANDOM_INT, - type: 'rand_int', - }; - - const rangeOfSizeNode: RangeOfSizeInvocation = { - id: RANGE_OF_SIZE, - type: 'range_of_size', - size: iterations, - }; - - const iterateNode: IterateInvocation = { - id: ITERATE, - type: 'iterate', - }; - - const noiseNode: NoiseInvocation = { - id: NOISE, - type: 'noise', - width, - height, - }; - - graphClone.nodes[RANDOM_INT] = randomIntNode; - graphClone.nodes[RANGE_OF_SIZE] = rangeOfSizeNode; - graphClone.nodes[ITERATE] = iterateNode; - graphClone.nodes[NOISE] = noiseNode; - - graphClone.edges.push({ - source: { node_id: RANDOM_INT, field: 'a' }, - destination: { node_id: RANGE_OF_SIZE, field: 'start' }, - }); - - graphClone.edges.push({ - source: { node_id: RANGE_OF_SIZE, field: 'collection' }, - destination: { - node_id: ITERATE, - field: 'collection', - }, - }); - - graphClone.edges.push({ - source: { - node_id: ITERATE, - field: 'item', - }, - destination: { - node_id: NOISE, - field: 'seed', - }, - }); - - graphClone.edges.push({ - source: { node_id: NOISE, field: 'noise' }, - destination: { - node_id: baseNodeId, - field: 'noise', - }, - }); - } - - return graphClone; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildCompelNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildCompelNode.ts deleted file mode 100644 index 02ac148181..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildCompelNode.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { RootState } from 'app/store/store'; -import { CompelInvocation } from 'services/api'; -import { O } from 'ts-toolbelt'; - -export const buildCompelNode = ( - prompt: string, - state: RootState, - overrides: O.Partial = {} -): CompelInvocation => { - const nodeId = uuidv4(); - const { generation } = state; - - const { model } = generation; - - const compelNode: CompelInvocation = { - id: nodeId, - type: 'compel', - prompt, - model, - }; - - Object.assign(compelNode, overrides); - - return compelNode; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts deleted file mode 100644 index 5f00d12a23..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildImageToImageNode.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { RootState } from 'app/store/store'; -import { - Edge, - ImageToImageInvocation, - TextToImageInvocation, -} from 'services/api'; -import { O } from 'ts-toolbelt'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; - -export const buildImg2ImgNode = ( - state: RootState, - overrides: O.Partial = {} -): ImageToImageInvocation => { - const nodeId = uuidv4(); - const { generation } = state; - - const activeTabName = activeTabNameSelector(state); - - const { - positivePrompt: prompt, - negativePrompt: negativePrompt, - seed, - steps, - width, - height, - cfgScale, - scheduler, - model, - img2imgStrength: strength, - shouldFitToWidthHeight: fit, - shouldRandomizeSeed, - initialImage, - } = generation; - - // const initialImage = initialImageSelector(state); - - const imageToImageNode: ImageToImageInvocation = { - id: nodeId, - type: 'img2img', - prompt: `${prompt} [${negativePrompt}]`, - steps, - width, - height, - cfg_scale: cfgScale, - scheduler, - model, - strength, - fit, - }; - - // on Canvas tab, we do not manually specific init image - if (activeTabName !== 'unifiedCanvas') { - if (!initialImage) { - // TODO: handle this more better - throw 'no initial image'; - } - - imageToImageNode.image = { - image_name: initialImage.name, - image_type: initialImage.type, - }; - } - - if (!shouldRandomizeSeed) { - imageToImageNode.seed = seed; - } - - Object.assign(imageToImageNode, overrides); - - return imageToImageNode; -}; - -type hiresReturnType = { - node: Record; - edge: Edge; -}; - -export const buildHiResNode = ( - baseNode: Record, - strength?: number -): hiresReturnType => { - const nodeId = uuidv4(); - const baseNodeId = Object.keys(baseNode)[0]; - const baseNodeValues = Object.values(baseNode)[0]; - - return { - node: { - [nodeId]: { - ...baseNodeValues, - id: nodeId, - type: 'img2img', - strength, - fit: true, - }, - }, - edge: { - source: { - field: 'image', - node_id: baseNodeId, - }, - destination: { - field: 'image', - node_id: nodeId, - }, - }, - }; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts deleted file mode 100644 index b3f6cca933..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildInpaintNode.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { RootState } from 'app/store/store'; -import { InpaintInvocation } from 'services/api'; -import { O } from 'ts-toolbelt'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; - -export const buildInpaintNode = ( - state: RootState, - overrides: O.Partial = {} -): InpaintInvocation => { - const nodeId = uuidv4(); - const { generation } = state; - const activeTabName = activeTabNameSelector(state); - - const { - positivePrompt: prompt, - negativePrompt: negativePrompt, - seed, - steps, - width, - height, - cfgScale, - scheduler, - model, - img2imgStrength: strength, - shouldFitToWidthHeight: fit, - shouldRandomizeSeed, - initialImage, - } = generation; - - const inpaintNode: InpaintInvocation = { - id: nodeId, - type: 'inpaint', - prompt: `${prompt} [${negativePrompt}]`, - steps, - width, - height, - cfg_scale: cfgScale, - scheduler, - model, - strength, - fit, - }; - - // on Canvas tab, we do not manually specific init image - if (activeTabName !== 'unifiedCanvas') { - if (!initialImage) { - // TODO: handle this more better - throw 'no initial image'; - } - - inpaintNode.image = { - image_name: initialImage.name, - image_type: initialImage.type, - }; - } - - if (!shouldRandomizeSeed) { - inpaintNode.seed = seed; - } - - Object.assign(inpaintNode, overrides); - - return inpaintNode; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildIterateNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildIterateNode.ts deleted file mode 100644 index 4f1b8d8930..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildIterateNode.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; - -import { IterateInvocation } from 'services/api'; - -export const buildIterateNode = (): IterateInvocation => { - const nodeId = uuidv4(); - return { - id: nodeId, - type: 'iterate', - // collection: [], - // index: 0, - }; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildRangeNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildRangeNode.ts deleted file mode 100644 index 735c0ef726..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildRangeNode.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; - -import { RootState } from 'app/store/store'; -import { RandomRangeInvocation, RangeInvocation } from 'services/api'; - -export const buildRangeNode = ( - state: RootState -): RangeInvocation | RandomRangeInvocation => { - const nodeId = uuidv4(); - const { shouldRandomizeSeed, iterations, seed } = state.generation; - - if (shouldRandomizeSeed) { - return { - id: nodeId, - type: 'random_range', - size: iterations, - }; - } - - return { - id: nodeId, - type: 'range', - start: seed, - stop: seed + iterations, - }; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts deleted file mode 100644 index 64e7aaa831..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/nodeBuilders/buildTextToImageNode.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { RootState } from 'app/store/store'; -import { TextToImageInvocation } from 'services/api'; -import { O } from 'ts-toolbelt'; - -export const buildTxt2ImgNode = ( - state: RootState, - overrides: O.Partial = {} -): TextToImageInvocation => { - const nodeId = uuidv4(); - const { generation } = state; - - const { - positivePrompt: prompt, - negativePrompt: negativePrompt, - seed, - steps, - width, - height, - cfgScale: cfg_scale, - scheduler, - shouldRandomizeSeed, - model, - } = generation; - - const textToImageNode: NonNullable = { - id: nodeId, - type: 'txt2img', - prompt: `${prompt} [${negativePrompt}]`, - steps, - width, - height, - cfg_scale, - scheduler, - model, - }; - - if (!shouldRandomizeSeed) { - textToImageNode.seed = seed; - } - - Object.assign(textToImageNode, overrides); - - return textToImageNode; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts b/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts index 631552414d..c77fdeca5e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts +++ b/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts @@ -13,7 +13,7 @@ import { buildOutputFieldTemplates, } from './fieldTemplateBuilders'; -const RESERVED_FIELD_NAMES = ['id', 'type', 'meta']; +const RESERVED_FIELD_NAMES = ['id', 'type', 'is_intermediate']; const invocationDenylist = ['Graph', 'InvocationMeta']; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx index 75ec70f257..dc83ba8907 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx @@ -2,18 +2,22 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; +import { + canvasSelector, + isStagingSelector, +} from 'features/canvas/store/canvasSelectors'; import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - canvasSelector, - (canvas) => { + [canvasSelector, isStagingSelector], + (canvas, isStaging) => { const { boundingBoxDimensions } = canvas; return { boundingBoxDimensions, + isStaging, }; }, defaultSelectorOptions @@ -21,7 +25,7 @@ const selector = createSelector( const ParamBoundingBoxWidth = () => { const dispatch = useAppDispatch(); - const { boundingBoxDimensions } = useAppSelector(selector); + const { boundingBoxDimensions, isStaging } = useAppSelector(selector); const { t } = useTranslation(); @@ -45,12 +49,13 @@ const ParamBoundingBoxWidth = () => { return ( { + [canvasSelector, isStagingSelector], + (canvas, isStaging) => { const { boundingBoxDimensions } = canvas; return { boundingBoxDimensions, + isStaging, }; }, defaultSelectorOptions @@ -21,7 +25,7 @@ const selector = createSelector( const ParamBoundingBoxWidth = () => { const dispatch = useAppDispatch(); - const { boundingBoxDimensions } = useAppSelector(selector); + const { boundingBoxDimensions, isStaging } = useAppSelector(selector); const { t } = useTranslation(); @@ -45,12 +49,13 @@ const ParamBoundingBoxWidth = () => { return ( { return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx index 00812f458a..d0f8545e14 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillMethod.tsx @@ -1,12 +1,12 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import IAISelect from 'common/components/IAISelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setInfillMethod } from 'features/parameters/store/generationSlice'; import { systemSelector } from 'features/system/store/systemSelectors'; -import { ChangeEvent, memo, useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( @@ -30,17 +30,17 @@ const ParamInfillMethod = () => { const { t } = useTranslation(); const handleChange = useCallback( - (e: ChangeEvent) => { - dispatch(setInfillMethod(e.target.value)); + (v: string) => { + dispatch(setInfillMethod(v)); }, [dispatch] ); return ( - ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx index 8164371b56..827fe77e0d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx @@ -1,15 +1,15 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import IAISelect from 'common/components/IAISelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice'; import { - BoundingBoxScale, BOUNDING_BOX_SCALES_DICT, + BoundingBoxScale, } from 'features/canvas/store/canvasTypes'; -import { ChangeEvent, memo } from 'react'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( @@ -30,16 +30,14 @@ const ParamScaleBeforeProcessing = () => { const { t } = useTranslation(); - const handleChangeBoundingBoxScaleMethod = ( - e: ChangeEvent - ) => { - dispatch(setBoundingBoxScaleMethod(e.target.value as BoundingBoxScale)); + const handleChangeBoundingBoxScaleMethod = (v: string) => { + dispatch(setBoundingBoxScaleMethod(v as BoundingBoxScale)); }; return ( - diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx new file mode 100644 index 0000000000..06c6108dcb --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx @@ -0,0 +1,69 @@ +import { Divider, Flex } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; +import IAICollapse from 'common/components/IAICollapse'; +import { Fragment, memo, useCallback } from 'react'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { createSelector } from '@reduxjs/toolkit'; +import { + controlNetAdded, + controlNetSelector, + isControlNetEnabledToggled, +} from 'features/controlNet/store/controlNetSlice'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { map } from 'lodash-es'; +import { v4 as uuidv4 } from 'uuid'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import IAIButton from 'common/components/IAIButton'; +import ControlNet from 'features/controlNet/components/ControlNet'; + +const selector = createSelector( + controlNetSelector, + (controlNet) => { + const { controlNets, isEnabled } = controlNet; + + return { controlNetsArray: map(controlNets), isEnabled }; + }, + defaultSelectorOptions +); + +const ParamControlNetCollapse = () => { + const { t } = useTranslation(); + const { controlNetsArray, isEnabled } = useAppSelector(selector); + const isControlNetDisabled = useFeatureStatus('controlNet').isFeatureDisabled; + const dispatch = useAppDispatch(); + + const handleClickControlNetToggle = useCallback(() => { + dispatch(isControlNetEnabledToggled()); + }, [dispatch]); + + const handleClickedAddControlNet = useCallback(() => { + dispatch(controlNetAdded({ controlNetId: uuidv4() })); + }, [dispatch]); + + if (isControlNetDisabled) { + return null; + } + + return ( + + + {controlNetsArray.map((c, i) => ( + + {i > 0 && } + + + ))} + + Add ControlNet + + + + ); +}; + +export default memo(ParamControlNetCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamIterations.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamIterations.tsx index 5a5b782c04..a8cdabc8c9 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamIterations.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamIterations.tsx @@ -1,4 +1,5 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAINumberInput from 'common/components/IAINumberInput'; import IAISlider from 'common/components/IAISlider'; @@ -10,27 +11,27 @@ import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -const selector = createSelector( - [generationSelector, configSelector, uiSelector, hotkeysSelector], - (generation, config, ui, hotkeys) => { - const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = - config.sd.iterations; - const { iterations } = generation; - const { shouldUseSliders } = ui; +const selector = createSelector([stateSelector], (state) => { + const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = + state.config.sd.iterations; + const { iterations } = state.generation; + const { shouldUseSliders } = state.ui; + const isDisabled = + state.dynamicPrompts.isEnabled && state.dynamicPrompts.combinatorial; - const step = hotkeys.shift ? fineStep : coarseStep; + const step = state.hotkeys.shift ? fineStep : coarseStep; - return { - iterations, - initial, - min, - sliderMax, - inputMax, - step, - shouldUseSliders, - }; - } -); + return { + iterations, + initial, + min, + sliderMax, + inputMax, + step, + shouldUseSliders, + isDisabled, + }; +}); const ParamIterations = () => { const { @@ -41,6 +42,7 @@ const ParamIterations = () => { inputMax, step, shouldUseSliders, + isDisabled, } = useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -58,6 +60,7 @@ const ParamIterations = () => { return shouldUseSliders ? ( { /> ) : ( { name="negativePrompt" value={negativePrompt} onChange={(e) => dispatch(setNegativePrompt(e.target.value))} - placeholder={t('parameters.negativePrompts')} - _focusVisible={{ - borderColor: 'error.600', - }} + placeholder={t('parameters.negativePromptPlaceholder')} fontSize="sm" + minH={16} /> ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamPositiveConditioning.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamPositiveConditioning.tsx index 365bade0aa..f42942a84b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamPositiveConditioning.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamPositiveConditioning.tsx @@ -4,7 +4,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react'; import { createSelector } from '@reduxjs/toolkit'; -import { readinessSelector } from 'app/selectors/readinessSelector'; import { GenerationState, clampSymmetrySteps, @@ -17,6 +16,7 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { userInvoked } from 'app/store/actions'; import IAITextarea from 'common/components/IAITextarea'; +import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; const promptInputSelector = createSelector( [(state: RootState) => state.generation, activeTabNameSelector], @@ -39,7 +39,7 @@ const promptInputSelector = createSelector( const ParamPositiveConditioning = () => { const dispatch = useAppDispatch(); const { prompt, activeTabName } = useAppSelector(promptInputSelector); - const { isReady } = useAppSelector(readinessSelector); + const isReady = useIsReadyToInvoke(); const promptRef = useRef(null); @@ -70,19 +70,17 @@ const ParamPositiveConditioning = () => { return ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx index 2b5db18d93..8818dcba9b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx @@ -1,49 +1,58 @@ -import { Scheduler } from 'app/constants'; -import { RootState } from 'app/store/store'; +import { createSelector } from '@reduxjs/toolkit'; +import { SCHEDULER_LABEL_MAP, SCHEDULER_NAMES } from 'app/constants'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICustomSelect from 'common/components/IAICustomSelect'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setScheduler } from 'features/parameters/store/generationSlice'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas'; +import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +const selector = createSelector( + [uiSelector, generationSelector], + (ui, generation) => { + const { scheduler } = generation; + const { favoriteSchedulers: enabledSchedulers } = ui; + + const data = SCHEDULER_NAMES.map((schedulerName) => ({ + value: schedulerName, + label: SCHEDULER_LABEL_MAP[schedulerName as SchedulerParam], + group: enabledSchedulers.includes(schedulerName) + ? 'Favorites' + : undefined, + })).sort((a, b) => a.label.localeCompare(b.label)); + + return { + scheduler, + data, + }; + }, + defaultSelectorOptions +); + const ParamScheduler = () => { - const scheduler = useAppSelector( - (state: RootState) => state.generation.scheduler - ); - - const activeTabName = useAppSelector(activeTabNameSelector); - - const schedulers = useAppSelector((state: RootState) => state.ui.schedulers); - - const img2imgSchedulers = schedulers.filter((scheduler) => { - return !['dpmpp_2s'].includes(scheduler); - }); - const dispatch = useAppDispatch(); const { t } = useTranslation(); + const { scheduler, data } = useAppSelector(selector); const handleChange = useCallback( - (v: string | null | undefined) => { + (v: string | null) => { if (!v) { return; } - dispatch(setScheduler(v as Scheduler)); + dispatch(setScheduler(v as SchedulerParam)); }, [dispatch] ); return ( - ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx index 3b53f5005c..5092893eed 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSchedulerAndModel.tsx @@ -1,12 +1,12 @@ import { Box, Flex } from '@chakra-ui/react'; -import { memo } from 'react'; import ModelSelect from 'features/system/components/ModelSelect'; +import { memo } from 'react'; import ParamScheduler from './ParamScheduler'; const ParamSchedulerAndModel = () => { return ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreType.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreType.tsx index aa4231eb4b..9246b3bd5c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreType.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/FaceRestore/FaceRestoreType.tsx @@ -1,12 +1,11 @@ import { FACETOOL_TYPES } from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISelect from 'common/components/IAISelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { FacetoolType, setFacetoolType, } from 'features/parameters/store/postprocessingSlice'; -import { ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; export default function FaceRestoreType() { @@ -17,13 +16,13 @@ export default function FaceRestoreType() { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const handleChangeFacetoolType = (e: ChangeEvent) => - dispatch(setFacetoolType(e.target.value as FacetoolType)); + const handleChangeFacetoolType = (v: string) => + dispatch(setFacetoolType(v as FacetoolType)); return ( - diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx index f17ebcbdc0..19eb45a0a9 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx @@ -1,21 +1,20 @@ import { Flex } from '@chakra-ui/react'; import InitialImagePreview from './InitialImagePreview'; -import InitialImageButtons from 'common/components/InitialImageButtons'; const InitialImageDisplay = () => { return ( { gap: 4, }} > - diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index be40f548e6..2a05eee9b4 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -1,19 +1,22 @@ -import { Flex, Icon, Image } from '@chakra-ui/react'; +import { Flex, Spacer, Text } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { useGetUrl } from 'common/util/getUrl'; -import { clearInitialImage } from 'features/parameters/store/generationSlice'; -import { DragEvent, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { ImageType } from 'services/api'; -import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; +import { + clearInitialImage, + initialImageChanged, +} from 'features/parameters/store/generationSlice'; +import { useCallback } from 'react'; import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { initialImageSelected } from 'features/parameters/store/actions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner'; -import { FaImage } from 'react-icons/fa'; -import { configSelector } from '../../../../system/store/configSelectors'; -import { useAppToaster } from 'app/components/Toaster'; +import IAIDndImage from 'common/components/IAIDndImage'; +import { ImageDTO } from 'services/api/types'; +import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import { skipToken } from '@reduxjs/toolkit/dist/query'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { FaUndo, FaUpload } from 'react-icons/fa'; +import useImageUploader from 'common/hooks/useImageUploader'; +import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; const selector = createSelector( [generationSelector], @@ -28,79 +31,94 @@ const selector = createSelector( const InitialImagePreview = () => { const { initialImage } = useAppSelector(selector); - const { shouldFetchImages } = useAppSelector(configSelector); - const { getUrl } = useGetUrl(); const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const toaster = useAppToaster(); + const { openUploader } = useImageUploader(); - const handleError = useCallback(() => { - dispatch(clearInitialImage()); - if (shouldFetchImages) { - toaster({ - title: 'Something went wrong, please refresh', - status: 'error', - isClosable: true, - }); - } else { - toaster({ - title: t('toast.parametersFailed'), - description: t('toast.parametersFailedDesc'), - status: 'error', - isClosable: true, - }); - } - }, [dispatch, t, toaster, shouldFetchImages]); + const { + currentData: image, + isLoading, + isError, + isSuccess, + } = useGetImageDTOQuery(initialImage?.imageName ?? skipToken); + + const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({ + postUploadAction: { type: 'SET_INITIAL_IMAGE' }, + }); const handleDrop = useCallback( - (e: DragEvent) => { - const name = e.dataTransfer.getData('invokeai/imageName'); - const type = e.dataTransfer.getData('invokeai/imageType') as ImageType; - - dispatch(initialImageSelected({ image_name: name, image_type: type })); + (droppedImage: ImageDTO) => { + if (droppedImage.image_name === initialImage?.imageName) { + return; + } + dispatch(initialImageChanged(droppedImage)); }, - [dispatch] + [dispatch, initialImage] ); + const handleReset = useCallback(() => { + dispatch(clearInitialImage()); + }, [dispatch]); + + const handleUpload = useCallback(() => { + openUploader(); + }, [openUploader]); + return ( - {initialImage?.image_url && ( - <> - } - onError={handleError} - sx={{ - objectFit: 'contain', - maxWidth: '100%', - maxHeight: '100%', - height: 'auto', - position: 'absolute', - borderRadius: 'base', - }} - /> - - - )} - {!initialImage?.image_url && ( - + + Initial Image + + + } + onClick={handleUpload} + {...getUploadButtonProps()} /> - )} + } + onClick={handleReset} + isDisabled={!initialImage} + /> + + } + isUploadDisabled={true} + fitContainer + /> + ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedFull.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedFull.tsx new file mode 100644 index 0000000000..75a5d189ae --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedFull.tsx @@ -0,0 +1,17 @@ +import { Flex } from '@chakra-ui/react'; +import { memo } from 'react'; +import ParamSeed from './ParamSeed'; +import ParamSeedShuffle from './ParamSeedShuffle'; +import ParamSeedRandomize from './ParamSeedRandomize'; + +const ParamSeedFull = () => { + return ( + + + + + + ); +}; + +export default memo(ParamSeedFull); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedRandomize.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedRandomize.tsx index 13380f3660..6b1dd46780 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedRandomize.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeedRandomize.tsx @@ -2,30 +2,10 @@ import { ChangeEvent, memo } from 'react'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISwitch from 'common/components/IAISwitch'; import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; -import { FormControl, FormLabel, Switch } from '@chakra-ui/react'; - -// export default function RandomizeSeed() { -// const dispatch = useAppDispatch(); -// const { t } = useTranslation(); - -// const shouldRandomizeSeed = useAppSelector( -// (state: RootState) => state.generation.shouldRandomizeSeed -// ); - -// const handleChangeShouldRandomizeSeed = (e: ChangeEvent) => -// dispatch(setShouldRandomizeSeed(e.target.checked)); - -// return ( -// -// ); -// } +import { FormControl, FormLabel, Switch, Tooltip } from '@chakra-ui/react'; +import IAISwitch from 'common/components/IAISwitch'; const ParamSeedRandomize = () => { const dispatch = useAppDispatch(); @@ -38,6 +18,14 @@ const ParamSeedRandomize = () => { const handleChangeShouldRandomizeSeed = (e: ChangeEvent) => dispatch(setShouldRandomizeSeed(e.target.checked)); + return ( + + ); + return ( dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX))); + return ( + } + /> + ); + return ( ) => - dispatch(setUpscalingLevel(Number(e.target.value) as UpscalingLevel)); + const handleChangeLevel = (v: string) => + dispatch(setUpscalingLevel(Number(v) as UpscalingLevel)); return ( - ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx index 57b556f88c..bb6e7e862d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx @@ -1,33 +1,32 @@ +import { + ButtonGroup, + ButtonProps, + ButtonSpinner, + Menu, + MenuButton, + MenuItemOption, + MenuList, + MenuOptionGroup, +} from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton, { - IAIIconButtonProps, -} from 'common/components/IAIIconButton'; +import IAIIconButton from 'common/components/IAIIconButton'; import { systemSelector } from 'features/system/store/systemSelectors'; import { + CancelStrategy, SystemState, cancelScheduled, cancelTypeChanged, - CancelStrategy, } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; -import { useCallback, memo, useMemo } from 'react'; -import { - ButtonSpinner, - ButtonGroup, - Menu, - MenuButton, - MenuList, - MenuOptionGroup, - MenuItemOption, -} from '@chakra-ui/react'; +import { memo, useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { MdCancel, MdCancelScheduleSend } from 'react-icons/md'; -import { sessionCanceled } from 'services/thunks/session'; import { ChevronDownIcon } from '@chakra-ui/icons'; +import { sessionCanceled } from 'services/api/thunks/session'; const cancelButtonSelector = createSelector( systemSelector, @@ -55,7 +54,7 @@ interface CancelButtonProps { } const CancelButton = ( - props: CancelButtonProps & Omit + props: CancelButtonProps & Omit ) => { const dispatch = useAppDispatch(); const { btnGroupWidth = 'auto', ...rest } = props; @@ -78,7 +77,7 @@ const CancelButton = ( return; } - dispatch(sessionCanceled({ sessionId })); + dispatch(sessionCanceled({ session_id: sessionId })); }, [dispatch, sessionId, cancelType]); const { t } = useTranslation(); @@ -145,6 +144,7 @@ const CancelButton = ( paddingY={0} colorScheme="error" minWidth={5} + {...rest} /> { @@ -43,38 +44,55 @@ export default function InvokeButton(props: InvokeButton) { ); return ( - - {iconButton ? ( - } - isDisabled={!isReady} - onClick={handleInvoke} - flexGrow={1} - w="100%" - tooltip={t('parameters.invoke')} - tooltipProps={{ placement: 'bottom' }} - colorScheme="accent" - id="invoke-button" - {...rest} - /> - ) : ( - - Invoke - - )} + + + {!isReady && ( + + + + )} + {iconButton ? ( + } + isDisabled={!isReady} + onClick={handleInvoke} + flexGrow={1} + w="100%" + tooltip={t('parameters.invoke')} + tooltipProps={{ placement: 'top' }} + colorScheme="accent" + id="invoke-button" + {...rest} + /> + ) : ( + + Invoke + + )} + ); } diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts deleted file mode 100644 index 27ae63e5dd..0000000000 --- a/invokeai/frontend/web/src/features/parameters/hooks/useParameters.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { useAppDispatch } from 'app/store/storeHooks'; -import { isFinite, isString } from 'lodash-es'; -import { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import useSetBothPrompts from './usePrompt'; -import { allParametersSet, setSeed } from '../store/generationSlice'; -import { isImageField } from 'services/types/guards'; -import { NUMPY_RAND_MAX } from 'app/constants'; -import { initialImageSelected } from '../store/actions'; -import { setActiveTab } from 'features/ui/store/uiSlice'; -import { useAppToaster } from 'app/components/Toaster'; -import { ImageDTO } from 'services/api'; - -export const useParameters = () => { - const dispatch = useAppDispatch(); - const toaster = useAppToaster(); - const { t } = useTranslation(); - const setBothPrompts = useSetBothPrompts(); - - /** - * Sets prompt with toast - */ - const recallPrompt = useCallback( - (prompt: unknown, negativePrompt?: unknown) => { - if (!isString(prompt) || !isString(negativePrompt)) { - toaster({ - title: t('toast.promptNotSet'), - description: t('toast.promptNotSetDesc'), - status: 'warning', - duration: 2500, - isClosable: true, - }); - return; - } - - setBothPrompts(prompt, negativePrompt); - toaster({ - title: t('toast.promptSet'), - status: 'info', - duration: 2500, - isClosable: true, - }); - }, - [t, toaster, setBothPrompts] - ); - - /** - * Sets seed with toast - */ - const recallSeed = useCallback( - (seed: unknown) => { - const s = Number(seed); - if (!isFinite(s) || (isFinite(s) && !(s >= 0 && s <= NUMPY_RAND_MAX))) { - toaster({ - title: t('toast.seedNotSet'), - description: t('toast.seedNotSetDesc'), - status: 'warning', - duration: 2500, - isClosable: true, - }); - return; - } - - dispatch(setSeed(s)); - toaster({ - title: t('toast.seedSet'), - status: 'info', - duration: 2500, - isClosable: true, - }); - }, - [t, toaster, dispatch] - ); - - /** - * Sets initial image with toast - */ - const recallInitialImage = useCallback( - async (image: unknown) => { - if (!isImageField(image)) { - toaster({ - title: t('toast.initialImageNotSet'), - description: t('toast.initialImageNotSetDesc'), - status: 'warning', - duration: 2500, - isClosable: true, - }); - return; - } - - dispatch(initialImageSelected(image)); - toaster({ - title: t('toast.initialImageSet'), - status: 'info', - duration: 2500, - isClosable: true, - }); - }, - [t, toaster, dispatch] - ); - - /** - * Sets image as initial image with toast - */ - const sendToImageToImage = useCallback( - (image: ImageDTO) => { - dispatch(initialImageSelected(image)); - }, - [dispatch] - ); - - const recallAllParameters = useCallback( - (image: ImageDTO | undefined) => { - const type = image?.metadata?.type; - // not sure what this list should be - if (['t2l', 'l2l', 'inpaint'].includes(String(type))) { - dispatch(allParametersSet(image)); - - if (image?.metadata?.type === 'l2l') { - dispatch(setActiveTab('img2img')); - } else if (image?.metadata?.type === 't2l') { - dispatch(setActiveTab('txt2img')); - } - - toaster({ - title: t('toast.parametersSet'), - status: 'success', - duration: 2500, - isClosable: true, - }); - } else { - toaster({ - title: t('toast.parametersNotSet'), - description: t('toast.parametersNotSetDesc'), - status: 'error', - duration: 2500, - isClosable: true, - }); - } - }, - [t, toaster, dispatch] - ); - - return { - recallPrompt, - recallSeed, - recallInitialImage, - sendToImageToImage, - recallAllParameters, - }; -}; diff --git a/invokeai/frontend/web/src/features/parameters/hooks/usePrompt.ts b/invokeai/frontend/web/src/features/parameters/hooks/usePrompt.ts deleted file mode 100644 index 3fee0bcdd8..0000000000 --- a/invokeai/frontend/web/src/features/parameters/hooks/usePrompt.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getPromptAndNegative } from 'common/util/getPromptAndNegative'; - -import * as InvokeAI from 'app/types/invokeai'; -import promptToString from 'common/util/promptToString'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { setNegativePrompt, setPositivePrompt } from '../store/generationSlice'; -import { useCallback } from 'react'; - -// TECHDEBT: We have two metadata prompt formats and need to handle recalling either of them. -// This hook provides a function to do that. -const useSetBothPrompts = () => { - const dispatch = useAppDispatch(); - - return useCallback( - (inputPrompt: InvokeAI.Prompt, negativePrompt: InvokeAI.Prompt) => { - dispatch(setPositivePrompt(inputPrompt)); - dispatch(setNegativePrompt(negativePrompt)); - }, - [dispatch] - ); -}; - -export default useSetBothPrompts; diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts new file mode 100644 index 0000000000..f504c62ed6 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts @@ -0,0 +1,348 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + modelSelected, + setCfgScale, + setHeight, + setImg2imgStrength, + setNegativePrompt, + setPositivePrompt, + setScheduler, + setSeed, + setSteps, + setWidth, +} from '../store/generationSlice'; +import { isImageField } from 'services/api/guards'; +import { initialImageSelected } from '../store/actions'; +import { useAppToaster } from 'app/components/Toaster'; +import { ImageDTO } from 'services/api/types'; +import { + isValidCfgScale, + isValidHeight, + isValidModel, + isValidNegativePrompt, + isValidPositivePrompt, + isValidScheduler, + isValidSeed, + isValidSteps, + isValidStrength, + isValidWidth, +} from '../store/parameterZodSchemas'; + +export const useRecallParameters = () => { + const dispatch = useAppDispatch(); + const toaster = useAppToaster(); + const { t } = useTranslation(); + + const parameterSetToast = useCallback(() => { + toaster({ + title: t('toast.parameterSet'), + status: 'info', + duration: 2500, + isClosable: true, + }); + }, [t, toaster]); + + const parameterNotSetToast = useCallback(() => { + toaster({ + title: t('toast.parameterNotSet'), + status: 'warning', + duration: 2500, + isClosable: true, + }); + }, [t, toaster]); + + const allParameterSetToast = useCallback(() => { + toaster({ + title: t('toast.parametersSet'), + status: 'info', + duration: 2500, + isClosable: true, + }); + }, [t, toaster]); + + const allParameterNotSetToast = useCallback(() => { + toaster({ + title: t('toast.parametersNotSet'), + status: 'warning', + duration: 2500, + isClosable: true, + }); + }, [t, toaster]); + + /** + * Recall both prompts with toast + */ + const recallBothPrompts = useCallback( + (positivePrompt: unknown, negativePrompt: unknown) => { + if ( + isValidPositivePrompt(positivePrompt) || + isValidNegativePrompt(negativePrompt) + ) { + if (isValidPositivePrompt(positivePrompt)) { + dispatch(setPositivePrompt(positivePrompt)); + } + if (isValidNegativePrompt(negativePrompt)) { + dispatch(setNegativePrompt(negativePrompt)); + } + parameterSetToast(); + return; + } + parameterNotSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall positive prompt with toast + */ + const recallPositivePrompt = useCallback( + (positivePrompt: unknown) => { + if (!isValidPositivePrompt(positivePrompt)) { + parameterNotSetToast(); + return; + } + dispatch(setPositivePrompt(positivePrompt)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall negative prompt with toast + */ + const recallNegativePrompt = useCallback( + (negativePrompt: unknown) => { + if (!isValidNegativePrompt(negativePrompt)) { + parameterNotSetToast(); + return; + } + dispatch(setNegativePrompt(negativePrompt)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall seed with toast + */ + const recallSeed = useCallback( + (seed: unknown) => { + if (!isValidSeed(seed)) { + parameterNotSetToast(); + return; + } + dispatch(setSeed(seed)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall CFG scale with toast + */ + const recallCfgScale = useCallback( + (cfgScale: unknown) => { + if (!isValidCfgScale(cfgScale)) { + parameterNotSetToast(); + return; + } + dispatch(setCfgScale(cfgScale)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall model with toast + */ + const recallModel = useCallback( + (model: unknown) => { + if (!isValidModel(model)) { + parameterNotSetToast(); + return; + } + dispatch(modelSelected(model)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall scheduler with toast + */ + const recallScheduler = useCallback( + (scheduler: unknown) => { + if (!isValidScheduler(scheduler)) { + parameterNotSetToast(); + return; + } + dispatch(setScheduler(scheduler)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall steps with toast + */ + const recallSteps = useCallback( + (steps: unknown) => { + if (!isValidSteps(steps)) { + parameterNotSetToast(); + return; + } + dispatch(setSteps(steps)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall width with toast + */ + const recallWidth = useCallback( + (width: unknown) => { + if (!isValidWidth(width)) { + parameterNotSetToast(); + return; + } + dispatch(setWidth(width)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall height with toast + */ + const recallHeight = useCallback( + (height: unknown) => { + if (!isValidHeight(height)) { + parameterNotSetToast(); + return; + } + dispatch(setHeight(height)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Recall strength with toast + */ + const recallStrength = useCallback( + (strength: unknown) => { + if (!isValidStrength(strength)) { + parameterNotSetToast(); + return; + } + dispatch(setImg2imgStrength(strength)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Sets initial image with toast + */ + const recallInitialImage = useCallback( + async (image: unknown) => { + if (!isImageField(image)) { + parameterNotSetToast(); + return; + } + dispatch(initialImageSelected(image.image_name)); + parameterSetToast(); + }, + [dispatch, parameterSetToast, parameterNotSetToast] + ); + + /** + * Sets image as initial image with toast + */ + const sendToImageToImage = useCallback( + (image: ImageDTO) => { + dispatch(initialImageSelected(image)); + }, + [dispatch] + ); + + const recallAllParameters = useCallback( + (image: ImageDTO | undefined) => { + if (!image || !image.metadata) { + allParameterNotSetToast(); + return; + } + const { + cfg_scale, + height, + model, + positive_conditioning, + negative_conditioning, + scheduler, + seed, + steps, + width, + strength, + clip, + extra, + latents, + unet, + vae, + } = image.metadata; + + if (isValidCfgScale(cfg_scale)) { + dispatch(setCfgScale(cfg_scale)); + } + if (isValidModel(model)) { + dispatch(modelSelected(model)); + } + if (isValidPositivePrompt(positive_conditioning)) { + dispatch(setPositivePrompt(positive_conditioning)); + } + if (isValidNegativePrompt(negative_conditioning)) { + dispatch(setNegativePrompt(negative_conditioning)); + } + if (isValidScheduler(scheduler)) { + dispatch(setScheduler(scheduler)); + } + if (isValidSeed(seed)) { + dispatch(setSeed(seed)); + } + if (isValidSteps(steps)) { + dispatch(setSteps(steps)); + } + if (isValidWidth(width)) { + dispatch(setWidth(width)); + } + if (isValidHeight(height)) { + dispatch(setHeight(height)); + } + if (isValidStrength(strength)) { + dispatch(setImg2imgStrength(strength)); + } + + allParameterSetToast(); + }, + [allParameterNotSetToast, allParameterSetToast, dispatch] + ); + + return { + recallBothPrompts, + recallPositivePrompt, + recallNegativePrompt, + recallSeed, + recallInitialImage, + recallCfgScale, + recallModel, + recallScheduler, + recallSteps, + recallWidth, + recallHeight, + recallStrength, + recallAllParameters, + sendToImageToImage, + }; +}; diff --git a/invokeai/frontend/web/src/features/parameters/store/actions.ts b/invokeai/frontend/web/src/features/parameters/store/actions.ts index 853597c809..2fb56c0883 100644 --- a/invokeai/frontend/web/src/features/parameters/store/actions.ts +++ b/invokeai/frontend/web/src/features/parameters/store/actions.ts @@ -1,31 +1,6 @@ import { createAction } from '@reduxjs/toolkit'; -import { isObject } from 'lodash-es'; -import { ImageDTO, ImageType } from 'services/api'; +import { ImageDTO } from 'services/api/types'; -export type ImageNameAndType = { - image_name: string; - image_type: ImageType; -}; - -export const isImageDTO = (image: any): image is ImageDTO => { - return ( - image && - isObject(image) && - 'image_name' in image && - image?.image_name !== undefined && - 'image_type' in image && - image?.image_type !== undefined && - 'image_url' in image && - image?.image_url !== undefined && - 'thumbnail_url' in image && - image?.thumbnail_url !== undefined && - 'image_category' in image && - image?.image_category !== undefined && - 'created_at' in image && - image?.created_at !== undefined - ); -}; - -export const initialImageSelected = createAction< - ImageDTO | ImageNameAndType | undefined ->('generation/initialImageSelected'); +export const initialImageSelected = createAction( + 'generation/initialImageSelected' +); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSelectors.ts b/invokeai/frontend/web/src/features/parameters/store/generationSelectors.ts index dbf5eec791..b7322740ef 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSelectors.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSelectors.ts @@ -1,34 +1,3 @@ -import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; -import { selectResultsById } from 'features/gallery/store/resultsSlice'; -import { selectUploadsById } from 'features/gallery/store/uploadsSlice'; -import { isEqual } from 'lodash-es'; export const generationSelector = (state: RootState) => state.generation; - -export const mayGenerateMultipleImagesSelector = createSelector( - generationSelector, - ({ shouldRandomizeSeed, shouldGenerateVariations }) => { - return shouldRandomizeSeed || shouldGenerateVariations; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -export const initialImageSelector = createSelector( - [(state: RootState) => state, generationSelector], - (state, generation) => { - const { initialImage } = generation; - - if (initialImage?.type === 'results') { - return selectResultsById(state, initialImage.name); - } - - if (initialImage?.type === 'uploads') { - return selectUploadsById(state, initialImage.name); - } - } -); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 849f848ff3..c8e65314da 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,43 +1,52 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import * as InvokeAI from 'app/types/invokeai'; -import promptToString from 'common/util/promptToString'; -import { clamp, sample } from 'lodash-es'; -import { setAllParametersReducer } from './setAllParametersReducer'; -import { receivedModels } from 'services/thunks/model'; -import { Scheduler } from 'app/constants'; -import { ImageDTO } from 'services/api'; +import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; +import { configChanged } from 'features/system/store/configSlice'; +import { clamp } from 'lodash-es'; +import { ImageDTO } from 'services/api/types'; +import { + CfgScaleParam, + HeightParam, + ModelParam, + NegativePromptParam, + PositivePromptParam, + SchedulerParam, + SeedParam, + StepsParam, + StrengthParam, + WidthParam, +} from './parameterZodSchemas'; export interface GenerationState { - cfgScale: number; - height: number; - img2imgStrength: number; + cfgScale: CfgScaleParam; + height: HeightParam; + img2imgStrength: StrengthParam; infillMethod: string; - initialImage?: ImageDTO; + initialImage?: { imageName: string; width: number; height: number }; iterations: number; perlin: number; - positivePrompt: string; - negativePrompt: string; - scheduler: Scheduler; + positivePrompt: PositivePromptParam; + negativePrompt: NegativePromptParam; + scheduler: SchedulerParam; seamBlur: number; seamSize: number; seamSteps: number; seamStrength: number; - seed: number; + seed: SeedParam; seedWeights: string; shouldFitToWidthHeight: boolean; shouldGenerateVariations: boolean; shouldRandomizeSeed: boolean; shouldUseNoiseSettings: boolean; - steps: number; + steps: StepsParam; threshold: number; tileSize: number; variationAmount: number; - width: number; + width: WidthParam; shouldUseSymmetry: boolean; horizontalSymmetrySteps: number; verticalSymmetrySteps: number; - model: string; + model: ModelParam; shouldUseSeamless: boolean; seamlessXAxis: boolean; seamlessYAxis: boolean; @@ -52,7 +61,7 @@ export const initialGenerationState: GenerationState = { perlin: 0, positivePrompt: '', negativePrompt: '', - scheduler: 'euler', + scheduler: DEFAULT_SCHEDULER_NAME, seamBlur: 16, seamSize: 96, seamSteps: 30, @@ -83,27 +92,11 @@ export const generationSlice = createSlice({ name: 'generation', initialState, reducers: { - setPositivePrompt: ( - state, - action: PayloadAction - ) => { - const newPrompt = action.payload; - if (typeof newPrompt === 'string') { - state.positivePrompt = newPrompt; - } else { - state.positivePrompt = promptToString(newPrompt); - } + setPositivePrompt: (state, action: PayloadAction) => { + state.positivePrompt = action.payload; }, - setNegativePrompt: ( - state, - action: PayloadAction - ) => { - const newPrompt = action.payload; - if (typeof newPrompt === 'string') { - state.negativePrompt = newPrompt; - } else { - state.negativePrompt = promptToString(newPrompt); - } + setNegativePrompt: (state, action: PayloadAction) => { + state.negativePrompt = action.payload; }, setIterations: (state, action: PayloadAction) => { state.iterations = action.payload; @@ -138,7 +131,7 @@ export const generationSlice = createSlice({ setWidth: (state, action: PayloadAction) => { state.width = action.payload; }, - setScheduler: (state, action: PayloadAction) => { + setScheduler: (state, action: PayloadAction) => { state.scheduler = action.payload; }, setSeed: (state, action: PayloadAction) => { @@ -174,7 +167,6 @@ export const generationSlice = createSlice({ state.shouldGenerateVariations = true; state.variationAmount = 0; }, - allParametersSet: setAllParametersReducer, resetParametersState: (state) => { return { ...state, @@ -218,19 +210,18 @@ export const generationSlice = createSlice({ state.shouldUseNoiseSettings = action.payload; }, initialImageChanged: (state, action: PayloadAction) => { - state.initialImage = action.payload; + const { image_name, width, height } = action.payload; + state.initialImage = { imageName: image_name, width, height }; }, modelSelected: (state, action: PayloadAction) => { state.model = action.payload; }, }, extraReducers: (builder) => { - builder.addCase(receivedModels.fulfilled, (state, action) => { - if (!state.model) { - const randomModel = sample(action.payload); - if (randomModel) { - state.model = randomModel.name; - } + builder.addCase(configChanged, (state, action) => { + const defaultModel = action.payload.sd?.defaultModel; + if (defaultModel && !state.model) { + state.model = defaultModel; } }); }, @@ -273,7 +264,6 @@ export const { setSeamless, setSeamlessXAxis, setSeamlessYAxis, - allParametersSet, } = generationSlice.actions; export default generationSlice.reducer; diff --git a/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts new file mode 100644 index 0000000000..48eb309e7d --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/store/parameterZodSchemas.ts @@ -0,0 +1,170 @@ +import { NUMPY_RAND_MAX, SCHEDULER_NAMES_AS_CONST } from 'app/constants'; +import { z } from 'zod'; + +/** + * These zod schemas should match the pydantic node schemas. + * + * Parameters only need schemas if we want to recall them from metadata. + * + * Each parameter needs: + * - a zod schema + * - a type alias, inferred from the zod schema + * - a combo validation/type guard function, which returns true if the value is valid + */ + +/** + * Zod schema for positive prompt parameter + */ +export const zPositivePrompt = z.string(); +/** + * Type alias for positive prompt parameter, inferred from its zod schema + */ +export type PositivePromptParam = z.infer; +/** + * Validates/type-guards a value as a positive prompt parameter + */ +export const isValidPositivePrompt = ( + val: unknown +): val is PositivePromptParam => zPositivePrompt.safeParse(val).success; + +/** + * Zod schema for negative prompt parameter + */ +export const zNegativePrompt = z.string(); +/** + * Type alias for negative prompt parameter, inferred from its zod schema + */ +export type NegativePromptParam = z.infer; +/** + * Validates/type-guards a value as a negative prompt parameter + */ +export const isValidNegativePrompt = ( + val: unknown +): val is NegativePromptParam => zNegativePrompt.safeParse(val).success; + +/** + * Zod schema for steps parameter + */ +export const zSteps = z.number().int().min(1); +/** + * Type alias for steps parameter, inferred from its zod schema + */ +export type StepsParam = z.infer; +/** + * Validates/type-guards a value as a steps parameter + */ +export const isValidSteps = (val: unknown): val is StepsParam => + zSteps.safeParse(val).success; + +/** + * Zod schema for CFG scale parameter + */ +export const zCfgScale = z.number().min(1); +/** + * Type alias for CFG scale parameter, inferred from its zod schema + */ +export type CfgScaleParam = z.infer; +/** + * Validates/type-guards a value as a CFG scale parameter + */ +export const isValidCfgScale = (val: unknown): val is CfgScaleParam => + zCfgScale.safeParse(val).success; + +/** + * Zod schema for scheduler parameter + */ +export const zScheduler = z.enum(SCHEDULER_NAMES_AS_CONST); +/** + * Type alias for scheduler parameter, inferred from its zod schema + */ +export type SchedulerParam = z.infer; +/** + * Validates/type-guards a value as a scheduler parameter + */ +export const isValidScheduler = (val: unknown): val is SchedulerParam => + zScheduler.safeParse(val).success; + +/** + * Zod schema for seed parameter + */ +export const zSeed = z.number().int().min(0).max(NUMPY_RAND_MAX); +/** + * Type alias for seed parameter, inferred from its zod schema + */ +export type SeedParam = z.infer; +/** + * Validates/type-guards a value as a seed parameter + */ +export const isValidSeed = (val: unknown): val is SeedParam => + zSeed.safeParse(val).success; + +/** + * Zod schema for width parameter + */ +export const zWidth = z.number().multipleOf(8).min(64); +/** + * Type alias for width parameter, inferred from its zod schema + */ +export type WidthParam = z.infer; +/** + * Validates/type-guards a value as a width parameter + */ +export const isValidWidth = (val: unknown): val is WidthParam => + zWidth.safeParse(val).success; + +/** + * Zod schema for height parameter + */ +export const zHeight = z.number().multipleOf(8).min(64); +/** + * Type alias for height parameter, inferred from its zod schema + */ +export type HeightParam = z.infer; +/** + * Validates/type-guards a value as a height parameter + */ +export const isValidHeight = (val: unknown): val is HeightParam => + zHeight.safeParse(val).success; + +/** + * Zod schema for model parameter + * TODO: Make this a dynamically generated enum? + */ +export const zModel = z.string(); +/** + * Type alias for model parameter, inferred from its zod schema + */ +export type ModelParam = z.infer; +/** + * Validates/type-guards a value as a model parameter + */ +export const isValidModel = (val: unknown): val is ModelParam => + zModel.safeParse(val).success; + +/** + * Zod schema for l2l strength parameter + */ +export const zStrength = z.number().min(0).max(1); +/** + * Type alias for l2l strength parameter, inferred from its zod schema + */ +export type StrengthParam = z.infer; +/** + * Validates/type-guards a value as a l2l strength parameter + */ +export const isValidStrength = (val: unknown): val is StrengthParam => + zStrength.safeParse(val).success; + +// /** +// * Zod schema for BaseModelType +// */ +// export const zBaseModelType = z.enum(['sd-1', 'sd-2']); +// /** +// * Type alias for base model type, inferred from its zod schema. Should be identical to the type alias from OpenAPI. +// */ +// export type BaseModelType = z.infer; +// /** +// * Validates/type-guards a value as a base model type +// */ +// export const isValidBaseModelType = (val: unknown): val is BaseModelType => +// zBaseModelType.safeParse(val).success; diff --git a/invokeai/frontend/web/src/features/parameters/store/setAllParametersReducer.ts b/invokeai/frontend/web/src/features/parameters/store/setAllParametersReducer.ts deleted file mode 100644 index 8f06c7d0ef..0000000000 --- a/invokeai/frontend/web/src/features/parameters/store/setAllParametersReducer.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Draft, PayloadAction } from '@reduxjs/toolkit'; -import { GenerationState } from './generationSlice'; -import { ImageDTO, ImageToImageInvocation } from 'services/api'; -import { isScheduler } from 'app/constants'; - -export const setAllParametersReducer = ( - state: Draft, - action: PayloadAction -) => { - const metadata = action.payload?.metadata; - - if (!metadata) { - return; - } - - // not sure what this list should be - if ( - metadata.type === 't2l' || - metadata.type === 'l2l' || - metadata.type === 'inpaint' - ) { - const { - cfg_scale, - height, - model, - positive_conditioning, - negative_conditioning, - scheduler, - seed, - steps, - width, - } = metadata; - - if (cfg_scale !== undefined) { - state.cfgScale = Number(cfg_scale); - } - if (height !== undefined) { - state.height = Number(height); - } - if (model !== undefined) { - state.model = String(model); - } - if (positive_conditioning !== undefined) { - state.positivePrompt = String(positive_conditioning); - } - if (negative_conditioning !== undefined) { - state.negativePrompt = String(negative_conditioning); - } - if (scheduler !== undefined) { - const schedulerString = String(scheduler); - if (isScheduler(schedulerString)) { - state.scheduler = schedulerString; - } - } - if (seed !== undefined) { - state.seed = Number(seed); - state.shouldRandomizeSeed = false; - } - if (steps !== undefined) { - state.steps = Number(steps); - } - if (width !== undefined) { - state.width = Number(width); - } - } - - if (metadata.type === 'l2l') { - const { fit, image } = metadata as ImageToImageInvocation; - - if (fit !== undefined) { - state.shouldFitToWidthHeight = Boolean(fit); - } - // if (image !== undefined) { - // state.initialImage = image; - // } - } -}; diff --git a/invokeai/frontend/web/src/features/system/components/ClearTempFolderButtonModal.tsx b/invokeai/frontend/web/src/features/system/components/ClearTempFolderButtonModal.tsx deleted file mode 100644 index a220c93b3f..0000000000 --- a/invokeai/frontend/web/src/features/system/components/ClearTempFolderButtonModal.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// import { emptyTempFolder } from 'app/socketio/actions'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIAlertDialog from 'common/components/IAIAlertDialog'; -import IAIButton from 'common/components/IAIButton'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { - clearCanvasHistory, - resetCanvas, -} from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; -import { FaTrash } from 'react-icons/fa'; - -const EmptyTempFolderButtonModal = () => { - const isStaging = useAppSelector(isStagingSelector); - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const acceptCallback = () => { - dispatch(emptyTempFolder()); - dispatch(resetCanvas()); - dispatch(clearCanvasHistory()); - }; - - return ( - } size="sm" isDisabled={isStaging}> - {t('unifiedCanvas.emptyTempImageFolder')} - - } - > -

{t('unifiedCanvas.emptyTempImagesFolderMessage')}

-
-

{t('unifiedCanvas.emptyTempImagesFolderConfirm')}

- - ); -}; -export default EmptyTempFolderButtonModal; diff --git a/invokeai/frontend/web/src/features/system/components/ColorModeButton.tsx b/invokeai/frontend/web/src/features/system/components/ColorModeButton.tsx new file mode 100644 index 0000000000..9bf748146c --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/ColorModeButton.tsx @@ -0,0 +1,32 @@ +import { useColorMode } from '@chakra-ui/react'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { useTranslation } from 'react-i18next'; +import { FaMoon, FaSun } from 'react-icons/fa'; + +const ColorModeButton = () => { + const { colorMode, toggleColorMode } = useColorMode(); + const { t } = useTranslation(); + + return ( + + ) : ( + + ) + } + onClick={toggleColorMode} + variant="link" + /> + ); +}; + +export default ColorModeButton; diff --git a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx index f6017d02f0..bec2c32b61 100644 --- a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx +++ b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx @@ -13,22 +13,16 @@ const InvokeAILogoComponent = () => { invoke-ai-logo - - + /> + + invoke ai - setAddmanually(!addManually)} /> - setAddmanually(!addManually)} diff --git a/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx b/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx index 6ba148cac4..219d49d4ee 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx @@ -24,7 +24,7 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import * as InvokeAI from 'app/types/invokeai'; import IAISlider from 'common/components/IAISlider'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; export default function MergeModels() { const dispatch = useAppDispatch(); @@ -286,7 +286,7 @@ export default function MergeModels() { )} - setModelMergeForce(e.target.checked)} diff --git a/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx b/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx index 3a99997ac8..3381cb85d3 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx @@ -1,5 +1,5 @@ import IAIButton from 'common/components/IAIButton'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import React from 'react'; @@ -81,13 +81,13 @@ function SearchModelEntry({ borderRadius={4} > - {model.name}} isChecked={modelsToAdd.includes(model.name)} isDisabled={existingModels.includes(model.location)} onChange={foundModelsChangeHandler} - > + > {existingModels.includes(model.location) && ( {t('modelManager.modelExists')} )} @@ -324,7 +324,7 @@ export default function SearchModels() { > {t('modelManager.deselectAll')} - diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx index 520e30b60a..f9eda624f2 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx @@ -1,38 +1,61 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { memo, useCallback } from 'react'; -import { isEqual } from 'lodash-es'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { selectModelsById, selectModelsIds } from '../store/modelSlice'; -import { RootState } from 'app/store/store'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { modelSelected } from 'features/parameters/store/generationSlice'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; -import IAICustomSelect from 'common/components/IAICustomSelect'; -const selector = createSelector( - [(state: RootState) => state, generationSelector], - (state, generation) => { - const selectedModel = selectModelsById(state, generation.model); - const allModelNames = selectModelsIds(state).map((id) => String(id)); - return { - allModelNames, - selectedModel, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); +import { forEach, isString } from 'lodash-es'; +import { SelectItem } from '@mantine/core'; +import { RootState } from 'app/store/store'; +import { useListModelsQuery } from 'services/api/endpoints/models'; + +export const MODEL_TYPE_MAP = { + 'sd-1': 'Stable Diffusion 1.x', + 'sd-2': 'Stable Diffusion 2.x', +}; const ModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const { allModelNames, selectedModel } = useAppSelector(selector); + + const selectedModelId = useAppSelector( + (state: RootState) => state.generation.model + ); + + const { data: pipelineModels } = useListModelsQuery({ + model_type: 'main', + }); + + const data = useMemo(() => { + if (!pipelineModels) { + return []; + } + + const data: SelectItem[] = []; + + forEach(pipelineModels.entities, (model, id) => { + if (!model) { + return; + } + + data.push({ + value: id, + label: model.name, + group: MODEL_TYPE_MAP[model.base_model], + }); + }); + + return data; + }, [pipelineModels]); + + const selectedModel = useMemo( + () => pipelineModels?.entities[selectedModelId], + [pipelineModels?.entities, selectedModelId] + ); + const handleChangeModel = useCallback( - (v: string | null | undefined) => { + (v: string | null) => { if (!v) { return; } @@ -41,15 +64,28 @@ const ModelSelect = () => { [dispatch] ); + useEffect(() => { + if (selectedModelId && pipelineModels?.ids.includes(selectedModelId)) { + return; + } + + const firstModel = pipelineModels?.ids[0]; + + if (!isString(firstModel)) { + return; + } + + handleChangeModel(firstModel); + }, [handleChangeModel, pipelineModels?.ids, selectedModelId]); + return ( - ); }; diff --git a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx index 4584bee644..140a8b5978 100644 --- a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx +++ b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx @@ -5,7 +5,6 @@ import { SystemState } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PROGRESS_BAR_THICKNESS } from 'theme/util/constants'; import { systemSelector } from '../store/systemSelectors'; const progressBarSelector = createSelector( @@ -35,7 +34,7 @@ const ProgressBar = () => { value={value} aria-label={t('accessibility.invokeProgressBar')} isIndeterminate={isProcessing && !currentStatusHasSteps} - height={PROGRESS_BAR_THICKNESS} + height="full" /> ); }; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 54556124c9..c9508bb5fe 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -1,5 +1,4 @@ import { - ChakraProps, Flex, Heading, Modal, @@ -13,19 +12,21 @@ import { useDisclosure, } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; +import { VALID_LOG_LEVELS } from 'app/logging/useLogger'; +import { LOCALSTORAGE_KEYS, LOCALSTORAGE_PREFIX } from 'app/store/constants'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; -import IAISelect from 'common/components/IAISelect'; +import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAISwitch from 'common/components/IAISwitch'; import { systemSelector } from 'features/system/store/systemSelectors'; import { + SystemState, consoleLogLevelChanged, setEnableImageDebugging, setShouldConfirmOnDelete, setShouldDisplayGuides, shouldAntialiasProgressImageChanged, shouldLogToConsoleChanged, - SystemState, } from 'features/system/store/systemSlice'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { @@ -35,11 +36,16 @@ import { } from 'features/ui/store/uiSlice'; import { UIState } from 'features/ui/store/uiTypes'; import { isEqual } from 'lodash-es'; -import { ChangeEvent, cloneElement, ReactElement, useCallback } from 'react'; +import { + ChangeEvent, + PropsWithChildren, + ReactElement, + cloneElement, + useCallback, + useEffect, +} from 'react'; import { useTranslation } from 'react-i18next'; -import { VALID_LOG_LEVELS } from 'app/logging/useLogger'; import { LogLevelName } from 'roarr'; -import { LOCALSTORAGE_KEYS, LOCALSTORAGE_PREFIX } from 'app/store/constants'; import SettingsSchedulers from './SettingsSchedulers'; const selector = createSelector( @@ -77,23 +83,33 @@ const selector = createSelector( } ); -const modalSectionStyles: ChakraProps['sx'] = { - flexDirection: 'column', - gap: 2, - p: 4, - bg: 'base.900', - borderRadius: 'base', +type ConfigOptions = { + shouldShowDeveloperSettings: boolean; + shouldShowResetWebUiText: boolean; + shouldShowBetaLayout: boolean; }; type SettingsModalProps = { /* The button to open the Settings Modal */ children: ReactElement; + config?: ConfigOptions; }; -const SettingsModal = ({ children }: SettingsModalProps) => { +const SettingsModal = ({ children, config }: SettingsModalProps) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); + const shouldShowBetaLayout = config?.shouldShowBetaLayout ?? true; + const shouldShowDeveloperSettings = + config?.shouldShowDeveloperSettings ?? true; + const shouldShowResetWebUiText = config?.shouldShowResetWebUiText ?? true; + + useEffect(() => { + if (!shouldShowDeveloperSettings) { + dispatch(shouldLogToConsoleChanged(false)); + } + }, [shouldShowDeveloperSettings, dispatch]); + const { isOpen: isSettingsModalOpen, onOpen: onSettingsModalOpen, @@ -133,8 +149,8 @@ const SettingsModal = ({ children }: SettingsModalProps) => { }, [onSettingsModalClose, onRefreshModalOpen]); const handleLogLevelChanged = useCallback( - (e: ChangeEvent) => { - dispatch(consoleLogLevelChanged(e.target.value as LogLevelName)); + (v: string) => { + dispatch(consoleLogLevelChanged(v as LogLevelName)); }, [dispatch] ); @@ -159,12 +175,12 @@ const SettingsModal = ({ children }: SettingsModalProps) => { isCentered > - + {t('common.settingsLabel')} - + {t('settings.general')} { dispatch(setShouldConfirmOnDelete(e.target.checked)) } /> - + - + {t('settings.generation')} - + - + {t('settings.ui')} { dispatch(setShouldDisplayGuides(e.target.checked)) } /> - ) => - dispatch(setShouldUseCanvasBetaLayout(e.target.checked)) - } - /> + {shouldShowBetaLayout && ( + ) => + dispatch(setShouldUseCanvasBetaLayout(e.target.checked)) + } + /> + )} { ) } /> - + - - {t('settings.developer')} - - - ) => - dispatch(setEnableImageDebugging(e.target.checked)) - } - /> - + {shouldShowDeveloperSettings && ( + + {t('settings.developer')} + + + ) => + dispatch(setEnableImageDebugging(e.target.checked)) + } + /> + + )} - + {t('settings.resetWebUI')} {t('settings.resetWebUI')} - {t('settings.resetWebUIDesc1')} - {t('settings.resetWebUIDesc2')} - + {shouldShowResetWebUiText && ( + <> + {t('settings.resetWebUIDesc1')} + {t('settings.resetWebUIDesc2')} + + )} + @@ -289,3 +311,19 @@ const SettingsModal = ({ children }: SettingsModalProps) => { }; export default SettingsModal; + +const StyledFlex = (props: PropsWithChildren) => { + return ( + + {props.children} + + ); +}; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx index 7e44257408..26c11604e1 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx @@ -1,49 +1,44 @@ -import { - Box, - Menu, - MenuButton, - MenuItemOption, - MenuList, - MenuOptionGroup, -} from '@chakra-ui/react'; -import { SCHEDULERS } from 'app/constants'; - +import { SCHEDULER_LABEL_MAP, SCHEDULER_NAMES } from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIButton from 'common/components/IAIButton'; -import { setSchedulers } from 'features/ui/store/uiSlice'; -import { isArray } from 'lodash-es'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import IAIMantineMultiSelect from 'common/components/IAIMantineMultiSelect'; +import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas'; +import { favoriteSchedulersChanged } from 'features/ui/store/uiSlice'; +import { map } from 'lodash-es'; +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -export default function SettingsSchedulers() { - const schedulers = useAppSelector((state: RootState) => state.ui.schedulers); +const data = map(SCHEDULER_NAMES, (s) => ({ + value: s, + label: SCHEDULER_LABEL_MAP[s], +})).sort((a, b) => a.label.localeCompare(b.label)); +export default function SettingsSchedulers() { const dispatch = useAppDispatch(); + const { t } = useTranslation(); - const schedulerSettingsHandler = (v: string | string[]) => { - if (isArray(v)) dispatch(setSchedulers(v.sort())); - }; + const enabledSchedulers = useAppSelector( + (state: RootState) => state.ui.favoriteSchedulers + ); + + const handleChange = useCallback( + (v: string[]) => { + dispatch(favoriteSchedulersChanged(v as SchedulerParam[])); + }, + [dispatch] + ); return ( - - - {t('settings.availableSchedulers')} - - - - {SCHEDULERS.map((scheduler) => ( - - {scheduler} - - ))} - - - + ); } diff --git a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx index 9b4159ecb6..0c94c1d2f9 100644 --- a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx +++ b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx @@ -1,66 +1,138 @@ -import { Flex, Grid } from '@chakra-ui/react'; -import { memo, useState } from 'react'; +import { Flex, Spacer } from '@chakra-ui/react'; +import { memo } from 'react'; import StatusIndicator from './StatusIndicator'; -import InvokeAILogoComponent from './InvokeAILogoComponent'; -import SiteHeaderMenu from './SiteHeaderMenu'; -import useResolution from 'common/hooks/useResolution'; -import { FaBars } from 'react-icons/fa'; -import { useTranslation } from 'react-i18next'; +import { Link } from '@chakra-ui/react'; import IAIIconButton from 'common/components/IAIIconButton'; +import { useTranslation } from 'react-i18next'; +import { FaBug, FaCube, FaDiscord, FaGithub, FaKeyboard } from 'react-icons/fa'; +import { MdSettings } from 'react-icons/md'; +import HotkeysModal from './HotkeysModal/HotkeysModal'; +import InvokeAILogoComponent from './InvokeAILogoComponent'; +import LanguagePicker from './LanguagePicker'; +import ModelManagerModal from './ModelManager/ModelManagerModal'; +import SettingsModal from './SettingsModal/SettingsModal'; +import { useFeatureStatus } from '../hooks/useFeatureStatus'; +import ColorModeButton from './ColorModeButton'; -/** - * Header, includes color mode toggle, settings button, status message. - */ const SiteHeader = () => { - const [menuOpened, setMenuOpened] = useState(false); - const resolution = useResolution(); const { t } = useTranslation(); + const isModelManagerEnabled = + useFeatureStatus('modelManager').isFeatureEnabled; + const isLocalizationEnabled = + useFeatureStatus('localization').isFeatureEnabled; + const isBugLinkEnabled = useFeatureStatus('bugLink').isFeatureEnabled; + const isDiscordLinkEnabled = useFeatureStatus('discordLink').isFeatureEnabled; + const isGithubLinkEnabled = useFeatureStatus('githubLink').isFeatureEnabled; + return ( - - - - - - + + + - {resolution === 'desktop' ? ( - - ) : ( + {isModelManagerEnabled && ( + } - aria-label={t('accessibility.menu')} - background={menuOpened ? 'base.800' : 'none'} - _hover={{ background: menuOpened ? 'base.800' : 'none' }} - onClick={() => setMenuOpened(!menuOpened)} - p={0} - > - )} - - - {resolution !== 'desktop' && menuOpened && ( - - - + aria-label={t('modelManager.modelManager')} + tooltip={t('modelManager.modelManager')} + size="sm" + variant="link" + data-variant="link" + fontSize={20} + icon={} + /> + )} - + + + } + /> + + + {isLocalizationEnabled && } + + {isBugLinkEnabled && ( + + } + /> + + )} + + {isGithubLinkEnabled && ( + + } + /> + + )} + + {isDiscordLinkEnabled && ( + + } + /> + + )} + + + + + } + /> + + ); }; diff --git a/invokeai/frontend/web/src/features/system/components/SiteHeaderMenu.tsx b/invokeai/frontend/web/src/features/system/components/SiteHeaderMenu.tsx index ddb18cb8d7..fc64f0275b 100644 --- a/invokeai/frontend/web/src/features/system/components/SiteHeaderMenu.tsx +++ b/invokeai/frontend/web/src/features/system/components/SiteHeaderMenu.tsx @@ -1,12 +1,12 @@ import { Flex, Link } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; -import { FaCube, FaKeyboard, FaBug, FaGithub, FaDiscord } from 'react-icons/fa'; +import { FaBug, FaCube, FaDiscord, FaGithub, FaKeyboard } from 'react-icons/fa'; import { MdSettings } from 'react-icons/md'; import HotkeysModal from './HotkeysModal/HotkeysModal'; import LanguagePicker from './LanguagePicker'; import ModelManagerModal from './ModelManager/ModelManagerModal'; import SettingsModal from './SettingsModal/SettingsModal'; -import ThemeChanger from './ThemeChanger'; + import IAIIconButton from 'common/components/IAIIconButton'; import { useFeatureStatus } from '../hooks/useFeatureStatus'; @@ -53,8 +53,6 @@ const SiteHeaderMenu = () => { /> - - {isLocalizationEnabled && } {isBugLinkEnabled && ( diff --git a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx index cd0a4eacc3..c5945140c3 100644 --- a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx +++ b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx @@ -35,6 +35,18 @@ const statusIndicatorSelector = createSelector( defaultSelectorOptions ); +const DARK_COLOR_MAP = { + ok: 'green.400', + working: 'yellow.400', + error: 'red.400', +}; + +const LIGHT_COLOR_MAP = { + ok: 'green.600', + working: 'yellow.500', + error: 'red.500', +}; + const StatusIndicator = () => { const { isConnected, @@ -46,7 +58,7 @@ const StatusIndicator = () => { const { t } = useTranslation(); const ref = useRef(null); - const statusColorScheme = useMemo(() => { + const statusString = useMemo(() => { if (isProcessing) { return 'working'; } @@ -90,9 +102,10 @@ const StatusIndicator = () => { sx={{ fontSize: 'sm', fontWeight: '600', - color: `${statusColorScheme}.400`, pb: '1px', userSelect: 'none', + color: LIGHT_COLOR_MAP[statusString], + _dark: { color: DARK_COLOR_MAP[statusString] }, }} > {t(statusTranslationKey as ResourceKey)} @@ -101,7 +114,14 @@ const StatusIndicator = () => {
)} - + ); }; diff --git a/invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx b/invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx deleted file mode 100644 index d9426eecf2..0000000000 --- a/invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - IconButton, - Menu, - MenuButton, - MenuItemOption, - MenuList, - MenuOptionGroup, - Tooltip, -} from '@chakra-ui/react'; -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { setCurrentTheme } from 'features/ui/store/uiSlice'; -import i18n from 'i18n'; -import { map } from 'lodash-es'; -import { useTranslation } from 'react-i18next'; -import { FaPalette } from 'react-icons/fa'; - -export const THEMES = { - dark: i18n.t('common.darkTheme'), - light: i18n.t('common.lightTheme'), - green: i18n.t('common.greenTheme'), - ocean: i18n.t('common.oceanTheme'), -}; - -export default function ThemeChanger() { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - const currentTheme = useAppSelector( - (state: RootState) => state.ui.currentTheme - ); - - return ( - - - } - variant="link" - aria-label={t('common.themeLabel')} - fontSize={20} - minWidth={8} - /> - - - - {map(THEMES, (themeName, themeKey: keyof typeof THEMES) => ( - dispatch(setCurrentTheme(themeKey))} - > - {themeName} - - ))} - - - - ); -} diff --git a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts index 6e62c3642b..8ba5731a5b 100644 --- a/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts +++ b/invokeai/frontend/web/src/features/system/hooks/useIsApplicationReady.ts @@ -7,34 +7,32 @@ import { systemSelector } from '../store/systemSelectors'; const isApplicationReadySelector = createSelector( [systemSelector, configSelector], (system, config) => { - const { wereModelsReceived, wasSchemaParsed } = system; + const { wasSchemaParsed } = system; const { disabledTabs } = config; return { disabledTabs, - wereModelsReceived, wasSchemaParsed, }; } ); +/** + * Checks if the application is ready to be used, i.e. if the initial startup process is finished. + */ export const useIsApplicationReady = () => { - const { disabledTabs, wereModelsReceived, wasSchemaParsed } = useAppSelector( + const { disabledTabs, wasSchemaParsed } = useAppSelector( isApplicationReadySelector ); const isApplicationReady = useMemo(() => { - if (!wereModelsReceived) { - return false; - } - if (!disabledTabs.includes('nodes') && !wasSchemaParsed) { return false; } return true; - }, [disabledTabs, wereModelsReceived, wasSchemaParsed]); + }, [disabledTabs, wasSchemaParsed]); return isApplicationReady; }; diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index f8cb3a483c..cf257032ff 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -4,12 +4,14 @@ import { AppConfig, PartialAppConfig } from 'app/types/invokeai'; import { merge } from 'lodash-es'; export const initialConfigState: AppConfig = { - shouldTransformUrls: false, + shouldUpdateImagesOnConnect: false, disabledTabs: [], disabledFeatures: [], disabledSDFeatures: [], canRestoreDeletedImagesFromBin: true, sd: { + disabledControlNetModels: [], + disabledControlNetProcessors: [], iterations: { initial: 1, min: 1, @@ -58,6 +60,14 @@ export const initialConfigState: AppConfig = { fineStep: 0.01, coarseStep: 0.05, }, + dynamicPrompts: { + maxPrompts: { + initial: 100, + min: 1, + sliderMax: 1000, + inputMax: 10000, + }, + }, }, }; diff --git a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts b/invokeai/frontend/web/src/features/system/store/modelSelectors.ts deleted file mode 100644 index f857bc85bc..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelSelectors.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { RootState } from 'app/store/store'; - -export const modelSelector = (state: RootState) => state.models; diff --git a/invokeai/frontend/web/src/features/system/store/modelSlice.ts b/invokeai/frontend/web/src/features/system/store/modelSlice.ts deleted file mode 100644 index ed38425872..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelSlice.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createEntityAdapter } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { CkptModelInfo, DiffusersModelInfo } from 'services/api'; -import { receivedModels } from 'services/thunks/model'; - -export type Model = (CkptModelInfo | DiffusersModelInfo) & { - name: string; -}; - -export const modelsAdapter = createEntityAdapter({ - selectId: (model) => model.name, - sortComparer: (a, b) => a.name.localeCompare(b.name), -}); - -export const initialModelsState = modelsAdapter.getInitialState(); - -export type ModelsState = typeof initialModelsState; - -export const modelsSlice = createSlice({ - name: 'models', - initialState: initialModelsState, - reducers: { - modelAdded: modelsAdapter.upsertOne, - }, - extraReducers(builder) { - /** - * Received Models - FULFILLED - */ - builder.addCase(receivedModels.fulfilled, (state, action) => { - const models = action.payload; - modelsAdapter.setAll(state, models); - }); - }, -}); - -export const { - selectAll: selectModelsAll, - selectById: selectModelsById, - selectEntities: selectModelsEntities, - selectIds: selectModelsIds, - selectTotal: selectModelsTotal, -} = modelsAdapter.getSelectors((state) => state.models); - -export const { modelAdded } = modelsSlice.actions; - -export default modelsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts deleted file mode 100644 index aa9fb057e1..0000000000 --- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ModelsState } from './modelSlice'; - -/** - * Models slice persist denylist - */ -export const modelsPersistDenylist: (keyof ModelsState)[] = ['entities', 'ids']; diff --git a/invokeai/frontend/web/src/features/system/store/systemSelectors.ts b/invokeai/frontend/web/src/features/system/store/systemSelectors.ts index d9fd836ece..e280210069 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSelectors.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSelectors.ts @@ -47,3 +47,6 @@ export const languageSelector = createSelector( (system) => system.language, defaultSelectorOptions ); + +export const isProcessingSelector = (state: RootState) => + state.system.isProcessing; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 403fd60501..2de0f75963 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -1,35 +1,31 @@ import { UseToastOptions } from '@chakra-ui/react'; -import { PayloadAction, isAnyOf } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import * as InvokeAI from 'app/types/invokeai'; -import { - generatorProgress, - graphExecutionStateComplete, - invocationComplete, - invocationError, - invocationStarted, - socketConnected, - socketDisconnected, - socketSubscribed, - socketUnsubscribed, -} from 'services/events/actions'; -import { ProgressImage } from 'services/events/types'; -import { makeToast } from '../../../app/components/Toaster'; -import { - sessionCanceled, - sessionCreated, - sessionInvoked, -} from 'services/thunks/session'; -import { receivedModels } from 'services/thunks/model'; -import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; -import { LogLevelName } from 'roarr'; import { InvokeLogLevel } from 'app/logging/useLogger'; -import { TFuncKey } from 'i18next'; -import { t } from 'i18next'; import { userInvoked } from 'app/store/actions'; +import { TFuncKey, t } from 'i18next'; +import { LogLevelName } from 'roarr'; +import { + appSocketConnected, + appSocketDisconnected, + appSocketGeneratorProgress, + appSocketGraphExecutionStateComplete, + appSocketInvocationComplete, + appSocketInvocationError, + appSocketInvocationStarted, + appSocketSubscribed, + appSocketUnsubscribed, +} from 'services/events/actions'; +import { ProgressImage } from 'services/events/types'; +import { imageUploaded } from 'services/api/thunks/image'; +import { + isAnySessionRejected, + sessionCanceled, +} from 'services/api/thunks/session'; +import { makeToast } from '../../../app/components/Toaster'; import { LANGUAGES } from '../components/LanguagePicker'; -import { imageUploaded } from 'services/thunks/image'; +import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice'; export type CancelStrategy = 'immediate' | 'scheduled'; @@ -99,6 +95,7 @@ export interface SystemState { shouldAntialiasProgressImage: boolean; language: keyof typeof LANGUAGES; isUploading: boolean; + boardIdToAddTo?: string; } export const initialSystemState: SystemState = { @@ -227,22 +224,24 @@ export const systemSlice = createSlice({ /** * Socket Subscribed */ - builder.addCase(socketSubscribed, (state, action) => { + builder.addCase(appSocketSubscribed, (state, action) => { state.sessionId = action.payload.sessionId; + state.boardIdToAddTo = action.payload.boardId; state.canceledSession = ''; }); /** * Socket Unsubscribed */ - builder.addCase(socketUnsubscribed, (state) => { + builder.addCase(appSocketUnsubscribed, (state) => { state.sessionId = null; + state.boardIdToAddTo = undefined; }); /** * Socket Connected */ - builder.addCase(socketConnected, (state) => { + builder.addCase(appSocketConnected, (state) => { state.isConnected = true; state.isCancelable = true; state.isProcessing = false; @@ -257,7 +256,7 @@ export const systemSlice = createSlice({ /** * Socket Disconnected */ - builder.addCase(socketDisconnected, (state) => { + builder.addCase(appSocketDisconnected, (state) => { state.isConnected = false; state.isProcessing = false; state.isCancelable = true; @@ -272,7 +271,7 @@ export const systemSlice = createSlice({ /** * Invocation Started */ - builder.addCase(invocationStarted, (state) => { + builder.addCase(appSocketInvocationStarted, (state) => { state.isCancelable = true; state.isProcessing = true; state.currentStatusHasSteps = false; @@ -286,7 +285,7 @@ export const systemSlice = createSlice({ /** * Generator Progress */ - builder.addCase(generatorProgress, (state, action) => { + builder.addCase(appSocketGeneratorProgress, (state, action) => { const { step, total_steps, progress_image } = action.payload.data; state.isProcessing = true; @@ -303,7 +302,7 @@ export const systemSlice = createSlice({ /** * Invocation Complete */ - builder.addCase(invocationComplete, (state, action) => { + builder.addCase(appSocketInvocationComplete, (state, action) => { const { data } = action.payload; // state.currentIteration = 0; @@ -322,7 +321,7 @@ export const systemSlice = createSlice({ /** * Invocation Error */ - builder.addCase(invocationError, (state) => { + builder.addCase(appSocketInvocationError, (state) => { state.isProcessing = false; state.isCancelable = true; // state.currentIteration = 0; @@ -339,7 +338,20 @@ export const systemSlice = createSlice({ }); /** - * Session Invoked - PENDING + * Graph Execution State Complete + */ + builder.addCase(appSocketGraphExecutionStateComplete, (state) => { + state.isProcessing = false; + state.isCancelable = false; + state.isCancelScheduled = false; + state.currentStep = 0; + state.totalSteps = 0; + state.statusTranslationKey = 'common.statusConnected'; + state.progressImage = null; + }); + + /** + * User Invoked */ builder.addCase(userInvoked, (state) => { @@ -353,7 +365,7 @@ export const systemSlice = createSlice({ * Session Canceled - FULFILLED */ builder.addCase(sessionCanceled.fulfilled, (state, action) => { - state.canceledSession = action.meta.arg.sessionId; + state.canceledSession = action.meta.arg.session_id; state.isProcessing = false; state.isCancelable = false; state.isCancelScheduled = false; @@ -367,29 +379,10 @@ export const systemSlice = createSlice({ ); }); - /** - * Session Canceled - */ - builder.addCase(graphExecutionStateComplete, (state) => { - state.isProcessing = false; - state.isCancelable = false; - state.isCancelScheduled = false; - state.currentStep = 0; - state.totalSteps = 0; - state.statusTranslationKey = 'common.statusConnected'; - }); - - /** - * Received available models from the backend - */ - builder.addCase(receivedModels.fulfilled, (state) => { - state.wereModelsReceived = true; - }); - /** * OpenAPI schema was parsed */ - builder.addCase(parsedOpenAPISchema, (state) => { + builder.addCase(nodeTemplatesBuilt, (state) => { state.wasSchemaParsed = true; }); @@ -461,8 +454,3 @@ export const { } = systemSlice.actions; export default systemSlice.reducer; - -const isAnySessionRejected = isAnyOf( - sessionCreated.rejected, - sessionInvoked.rejected -); diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx index c816c9b39e..3e2c2153e6 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx @@ -51,6 +51,7 @@ const FloatingGalleryButton = () => { w: 8, borderStartEndRadius: 0, borderEndEndRadius: 0, + shadow: '2xl', }} > diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index 4f4995ffa7..0969426339 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -19,6 +19,7 @@ import { FaSlidersH } from 'react-icons/fa'; const floatingButtonStyles: ChakraProps['sx'] = { borderStartStartRadius: 0, borderEndStartRadius: 0, + shadow: '2xl', }; export const floatingParametersPanelButtonSelector = createSelector( diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 23fc6bd192..6bbeedcaaa 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -14,10 +14,9 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice'; -import { memo, ReactNode, useCallback, useMemo } from 'react'; +import { memo, MouseEvent, ReactNode, useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { MdDeviceHub, MdGridOn } from 'react-icons/md'; -import { GoTextSize } from 'react-icons/go'; import { activeTabIndexSelector, activeTabNameSelector, @@ -33,10 +32,11 @@ import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent import TextToImageTab from './tabs/TextToImage/TextToImageTab'; import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab'; import NodesTab from './tabs/Nodes/NodesTab'; -import { FaImage } from 'react-icons/fa'; +import { FaFont, FaImage } from 'react-icons/fa'; import ResizeHandle from './tabs/ResizeHandle'; import ImageTab from './tabs/ImageToImage/ImageToImageTab'; import AuxiliaryProgressIndicator from 'app/components/AuxiliaryProgressIndicator'; +import { useMinimumPanelSize } from '../hooks/useMinimumPanelSize'; export interface InvokeTabInfo { id: InvokeTabName; @@ -47,22 +47,22 @@ export interface InvokeTabInfo { const tabs: InvokeTabInfo[] = [ { id: 'txt2img', - icon: , + icon: , content: , }, { id: 'img2img', - icon: , + icon: , content: , }, { id: 'unifiedCanvas', - icon: , + icon: , content: , }, { id: 'nodes', - icon: , + icon: , content: , }, ]; @@ -79,6 +79,9 @@ const enabledTabsSelector = createSelector( } ); +const MIN_GALLERY_WIDTH = 300; +const DEFAULT_GALLERY_PCT = 20; + const InvokeTabs = () => { const activeTab = useAppSelector(activeTabIndexSelector); const activeTabName = useAppSelector(activeTabNameSelector); @@ -119,6 +122,12 @@ const InvokeTabs = () => { } }, [dispatch, activeTabName]); + const handleClickTab = useCallback((e: MouseEvent) => { + if (e.target instanceof HTMLElement) { + e.target.blur(); + } + }, []); + const tabs = useMemo( () => enabledTabs.map((tab) => ( @@ -128,7 +137,7 @@ const InvokeTabs = () => { label={String(t(`common.${tab.id}` as ResourceKey))} placement="end" > - + {String(t(`common.${tab.id}` as ResourceKey))} @@ -136,7 +145,7 @@ const InvokeTabs = () => { )), - [t, enabledTabs] + [enabledTabs, t, handleClickTab] ); const tabPanels = useMemo( @@ -145,6 +154,9 @@ const InvokeTabs = () => { [enabledTabs] ); + const { ref: galleryPanelRef, minSizePct: galleryMinSizePct } = + useMinimumPanelSize(MIN_GALLERY_WIDTH, DEFAULT_GALLERY_PCT, 'app'); + return ( { onChange={(index: number) => { dispatch(setActiveTab(index)); }} - flexGrow={1} - flexDir={{ base: 'column', xl: 'row' }} - gap={{ base: 4 }} + sx={{ + flexGrow: 1, + gap: 4, + }} isLazy > {tabs} { <> DEFAULT_GALLERY_PCT + ? galleryMinSizePct + : DEFAULT_GALLERY_PCT + } + minSize={galleryMinSizePct} maxSize={50} > diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx index 9c19d988fe..b41017c2c9 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx @@ -1,21 +1,21 @@ +import { Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { memo, useMemo } from 'react'; -import { Box, Flex } from '@chakra-ui/react'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; -import OverlayScrollable from './common/OverlayScrollable'; -import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; import { activeTabNameSelector, uiSelector, } from 'features/ui/store/uiSelectors'; import { setShouldShowParametersPanel } from 'features/ui/store/uiSlice'; -import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; +import { memo, useMemo } from 'react'; +import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; import PinParametersPanelButton from './PinParametersPanelButton'; -import TextToImageTabParameters from './tabs/TextToImage/TextToImageTabParameters'; +import OverlayScrollable from './common/OverlayScrollable'; +import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer'; import ImageToImageTabParameters from './tabs/ImageToImage/ImageToImageTabParameters'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import TextToImageTabParameters from './tabs/TextToImage/TextToImageTabParameters'; import UnifiedCanvasParameters from './tabs/UnifiedCanvas/UnifiedCanvasParameters'; const selector = createSelector( diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx index 407187294c..d47ca3e1ba 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPinnedWrapper.tsx @@ -1,11 +1,11 @@ import { Box, Flex } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; import { PropsWithChildren, memo } from 'react'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; -import OverlayScrollable from './common/OverlayScrollable'; -import PinParametersPanelButton from './PinParametersPanelButton'; -import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from '../store/uiSelectors'; -import { useAppSelector } from 'app/store/storeHooks'; +import PinParametersPanelButton from './PinParametersPanelButton'; +import OverlayScrollable from './common/OverlayScrollable'; const selector = createSelector(uiSelector, (ui) => { const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; @@ -35,19 +35,27 @@ const ParametersPinnedWrapper = (props: ParametersPinnedWrapperProps) => { flexShrink: 0, }} > - - - {props.children} - - + + + + {props.children} + + + + diff --git a/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx b/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx index 46d0fa3f93..a742e2a587 100644 --- a/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx @@ -33,7 +33,6 @@ const PinParametersPanelButton = (props: PinParametersPanelButtonProps) => { icon={shouldPinParametersPanel ? : } variant="ghost" size="sm" - px={{ base: 10, xl: 0 }} sx={{ color: 'base.700', _hover: { diff --git a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx index 71413fd01a..722ee46fd5 100644 --- a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx +++ b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx @@ -1,6 +1,5 @@ import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { PropsWithChildren, memo } from 'react'; - const OverlayScrollable = (props: PropsWithChildren) => { return ( { ); }; - export default memo(OverlayScrollable); diff --git a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx index 760b910632..0eb4677182 100644 --- a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx @@ -6,6 +6,7 @@ import { useOutsideClick, useTheme, SlideDirection, + useColorMode, } from '@chakra-ui/react'; import { Resizable, @@ -21,6 +22,7 @@ import { getSlideDirection, getStyles, } from './util'; +import { mode } from 'theme/util/mode'; type ResizableDrawerProps = ResizableProps & { children: ReactNode; @@ -64,7 +66,7 @@ const ResizableDrawer = ({ sx = {}, }: ResizableDrawerProps) => { const langDirection = useTheme().direction as LangDirection; - + const { colorMode } = useColorMode(); const outsideClickRef = useRef(null); const defaultWidth = useMemo( @@ -160,11 +162,11 @@ const ResizableDrawer = ({ handleStyles={handleStyles} {...minMaxDimensions} sx={{ - borderColor: 'base.800', + borderColor: mode('base.200', 'base.800')(colorMode), p: 4, - bg: 'base.900', + bg: mode('base.100', 'base.900')(colorMode), height: 'full', - boxShadow: '0 0 4rem 0 rgba(0, 0, 0, 0.8)', + shadow: isOpen ? 'dark-lg' : undefined, ...containerStyles, ...sx, }} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTab.tsx index cbd261f455..a0ec95d72d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTab.tsx @@ -1,14 +1,17 @@ import { Box, Flex } from '@chakra-ui/react'; -import { memo, useCallback, useRef } from 'react'; -import { Panel, PanelGroup } from 'react-resizable-panels'; import { useAppDispatch } from 'app/store/storeHooks'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import ResizeHandle from '../ResizeHandle'; -import ImageToImageTabParameters from './ImageToImageTabParameters'; -import TextToImageTabMain from '../TextToImage/TextToImageTabMain'; -import { ImperativePanelGroupHandle } from 'react-resizable-panels'; -import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; import InitialImageDisplay from 'features/parameters/components/Parameters/ImageToImage/InitialImageDisplay'; +import { memo, useCallback, useRef } from 'react'; +import { + ImperativePanelGroupHandle, + Panel, + PanelGroup, +} from 'react-resizable-panels'; +import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; +import ResizeHandle from '../ResizeHandle'; +import TextToImageTabMain from '../TextToImage/TextToImageTabMain'; +import ImageToImageTabParameters from './ImageToImageTabParameters'; const ImageToImageTab = () => { const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx index c4161154bb..cdbec9b55d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx @@ -1,5 +1,5 @@ import { memo } from 'react'; -import { Flex } from '@chakra-ui/react'; +import { Box, Flex, useDisclosure } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { useAppSelector } from 'app/store/storeHooks'; @@ -13,6 +13,8 @@ import ImageToImageStrength from 'features/parameters/components/Parameters/Imag import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel'; +import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull'; +import IAICollapse from 'common/components/IAICollapse'; const selector = createSelector( [uiSelector, generationSelector], @@ -27,43 +29,47 @@ const selector = createSelector( const ImageToImageTabCoreParameters = () => { const { shouldUseSliders, shouldFitToWidthHeight } = useAppSelector(selector); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); return ( - - {shouldUseSliders ? ( - - - - - - - - - - - ) : ( - - + + + {shouldUseSliders ? ( + <> + + + + - - - - - - - - )} - + + + + ) : ( + <> + + + + + + + + + + + + + )} + + + + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx index 3b3daeaa4c..4f04abffa1 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx @@ -2,12 +2,13 @@ import { memo } from 'react'; import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; -import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; import ImageToImageTabCoreParameters from './ImageToImageTabCoreParameters'; +import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; +import ParamDynamicPromptsCollapse from 'features/dynamicPrompts/components/ParamDynamicPromptsCollapse'; const ImageToImageTabParameters = () => { return ( @@ -16,7 +17,8 @@ const ImageToImageTabParameters = () => { - + + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx index d53a4d4fef..7ef0b48784 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -1,6 +1,7 @@ -import { Box, Flex, FlexProps } from '@chakra-ui/react'; +import { Box, Flex, FlexProps, useColorMode } from '@chakra-ui/react'; import { memo } from 'react'; import { PanelResizeHandle } from 'react-resizable-panels'; +import { mode } from 'theme/util/mode'; type ResizeHandleProps = FlexProps & { direction?: 'horizontal' | 'vertical'; @@ -8,6 +9,7 @@ type ResizeHandleProps = FlexProps & { const ResizeHandle = (props: ResizeHandleProps) => { const { direction = 'horizontal', ...rest } = props; + const { colorMode } = useColorMode(); if (direction === 'horizontal') { return ( @@ -21,7 +23,13 @@ const ResizeHandle = (props: ResizeHandleProps) => { }} {...rest} > - + ); @@ -38,7 +46,13 @@ const ResizeHandle = (props: ResizeHandleProps) => { }} {...rest} > - + ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx index 87e77cc3ba..90141af785 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTab.tsx @@ -1,8 +1,8 @@ import { Flex } from '@chakra-ui/react'; import { memo } from 'react'; +import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; import TextToImageTabMain from './TextToImageTabMain'; import TextToImageTabParameters from './TextToImageTabParameters'; -import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; const TextToImageTab = () => { return ( diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx index 59512775bc..07297bda31 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx @@ -3,13 +3,15 @@ import ParamSteps from 'features/parameters/components/Parameters/Core/ParamStep import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; -import { Flex } from '@chakra-ui/react'; +import { Box, Flex, useDisclosure } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { memo } from 'react'; import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel'; +import IAICollapse from 'common/components/IAICollapse'; +import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull'; const selector = createSelector( uiSelector, @@ -23,39 +25,45 @@ const selector = createSelector( const TextToImageTabCoreParameters = () => { const { shouldUseSliders } = useAppSelector(selector); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); return ( - - {shouldUseSliders ? ( - - - - - - - - - ) : ( - - + + + {shouldUseSliders ? ( + <> + + + + - - - - - - )} - + + + + ) : ( + <> + + + + + + + + + + + + + )} + + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabMain.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabMain.tsx index b6cfcf72c3..de21cb14eb 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabMain.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabMain.tsx @@ -4,13 +4,13 @@ import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay const TextToImageTabMain = () => { return ( { return ( @@ -17,7 +18,8 @@ const TextToImageTabParameters = () => { - + + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasContentBeta.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasContentBeta.tsx deleted file mode 100644 index 601c36b9e2..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasContentBeta.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -// import IAICanvas from 'features/canvas/components/IAICanvas'; -import { Box, Flex } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICanvas from 'features/canvas/components/IAICanvas'; -import IAICanvasResizer from 'features/canvas/components/IAICanvasResizer'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; - -import { isEqual } from 'lodash-es'; -import { useLayoutEffect } from 'react'; -import UnifiedCanvasToolbarBeta from './UnifiedCanvasToolbarBeta'; -import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasToolSettingsBeta'; -import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; - -const selector = createSelector( - [canvasSelector], - (canvas) => { - const { doesCanvasNeedScaling } = canvas; - return { - doesCanvasNeedScaling, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -const UnifiedCanvasContentBeta = () => { - const dispatch = useAppDispatch(); - - const { doesCanvasNeedScaling } = useAppSelector(selector); - - useLayoutEffect(() => { - dispatch(requestCanvasRescale()); - const resizeCallback = () => { - dispatch(requestCanvasRescale()); - }; - - window.addEventListener('resize', resizeCallback); - - return () => window.removeEventListener('resize', resizeCallback); - }, [dispatch]); - - return ( - - - - - - {doesCanvasNeedScaling ? : } - - - - ); -}; - -export default UnifiedCanvasContentBeta; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx index 042749e792..53e36f62b6 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -14,7 +14,7 @@ export default function UnifiedCanvasDarkenOutsideSelection() { const { t } = useTranslation(); return ( - diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx index 24f3f45a25..ceb58cb5ca 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -16,7 +16,7 @@ export default function UnifiedCanvasEnableMask() { dispatch(setIsMaskEnabled(!isMaskEnabled)); return ( - diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx index 9b4b20e936..fd3396533c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -13,7 +13,7 @@ export default function UnifiedCanvasPreserveMask() { ); return ( - dispatch(setShouldPreserveMaskedArea(e.target.checked))} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx index bfaa7cdae8..a179a95c3f 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx @@ -1,7 +1,7 @@ import { Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; @@ -12,7 +12,6 @@ import { setShouldShowCanvasDebugInfo, setShouldShowIntermediates, } from 'features/canvas/store/canvasSlice'; -import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal'; import { FaWrench } from 'react-icons/fa'; @@ -73,39 +72,38 @@ const UnifiedCanvasSettings = () => { } > - dispatch(setShouldShowIntermediates(e.target.checked)) } /> - dispatch(setShouldAutoSave(e.target.checked))} /> - dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)) } /> - dispatch(setShouldShowCanvasDebugInfo(e.target.checked)) } /> - dispatch(setShouldAntialias(e.target.checked))} /> - ); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx index e3d8a518ef..e17f74ce41 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldShowGrid } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -13,7 +13,7 @@ export default function UnifiedCanvasShowGrid() { const { t } = useTranslation(); return ( - dispatch(setShouldShowGrid(e.target.checked))} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx index c334bd213b..69e9a4e78b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice'; import { ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; @@ -17,7 +17,7 @@ export default function UnifiedCanvasSnapToGrid() { dispatch(setShouldSnapToGrid(e.target.checked)); return ( - ) => { - const newLayer = e.target.value as CanvasLayer; + const handleChangeLayer = (v: string) => { + const newLayer = v as CanvasLayer; dispatch(setLayer(newLayer)); if (newLayer === 'mask' && !isMaskEnabled) { dispatch(setIsMaskEnabled(true)); } }; return ( - ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx index ece4386d45..b5eec3bec3 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx @@ -2,6 +2,7 @@ import { Flex } from '@chakra-ui/react'; import IAICanvasRedoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton'; import IAICanvasUndoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasUndoButton'; +import UnifiedCanvasSettings from './UnifiedCanvasToolSettings/UnifiedCanvasSettings'; import UnifiedCanvasCopyToClipboard from './UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard'; import UnifiedCanvasDownloadImage from './UnifiedCanvasToolbar/UnifiedCanvasDownloadImage'; import UnifiedCanvasFileUploader from './UnifiedCanvasToolbar/UnifiedCanvasFileUploader'; @@ -13,11 +14,10 @@ import UnifiedCanvasResetCanvas from './UnifiedCanvasToolbar/UnifiedCanvasResetC import UnifiedCanvasResetView from './UnifiedCanvasToolbar/UnifiedCanvasResetView'; import UnifiedCanvasSaveToGallery from './UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery'; import UnifiedCanvasToolSelect from './UnifiedCanvasToolbar/UnifiedCanvasToolSelect'; -import UnifiedCanvasSettings from './UnifiedCanvasToolSettings/UnifiedCanvasSettings'; const UnifiedCanvasToolbarBeta = () => { return ( - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasContent.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasContent.tsx index e56e6126a5..77085bcb75 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasContent.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasContent.tsx @@ -1,38 +1,60 @@ import { Box, Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAICanvas from 'features/canvas/components/IAICanvas'; import IAICanvasResizer from 'features/canvas/components/IAICanvasResizer'; import IAICanvasToolbar from 'features/canvas/components/IAICanvasToolbar/IAICanvasToolbar'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; -import { isEqual } from 'lodash-es'; +import { uiSelector } from 'features/ui/store/uiSelectors'; -import { memo, useLayoutEffect } from 'react'; +import { memo, useCallback, useLayoutEffect } from 'react'; +import UnifiedCanvasToolbarBeta from './UnifiedCanvasBeta/UnifiedCanvasToolbarBeta'; +import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta'; +import { ImageDTO } from 'services/api/types'; +import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; +import { useDroppable } from '@dnd-kit/core'; +import IAIDropOverlay from 'common/components/IAIDropOverlay'; const selector = createSelector( - [canvasSelector], - (canvas) => { + [canvasSelector, uiSelector], + (canvas, ui) => { const { doesCanvasNeedScaling } = canvas; + const { shouldUseCanvasBetaLayout } = ui; return { doesCanvasNeedScaling, + shouldUseCanvasBetaLayout, }; }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } + defaultSelectorOptions ); const UnifiedCanvasContent = () => { const dispatch = useAppDispatch(); - const { doesCanvasNeedScaling } = useAppSelector(selector); + const { doesCanvasNeedScaling, shouldUseCanvasBetaLayout } = + useAppSelector(selector); + + const onDrop = useCallback( + (droppedImage: ImageDTO) => { + dispatch(setInitialCanvasImage(droppedImage)); + }, + [dispatch] + ); + + const { + isOver, + setNodeRef: setDroppableRef, + active, + } = useDroppable({ + id: 'unifiedCanvas', + data: { + handleDrop: onDrop, + }, + }); useLayoutEffect(() => { - dispatch(requestCanvasRescale()); - const resizeCallback = () => { dispatch(requestCanvasRescale()); }; @@ -42,14 +64,57 @@ const UnifiedCanvasContent = () => { return () => window.removeEventListener('resize', resizeCallback); }, [dispatch]); + if (shouldUseCanvasBetaLayout) { + return ( + + + + + + + {doesCanvasNeedScaling ? : } + {active && } + + + + + ); + } + return ( { flexDirection: 'column', alignItems: 'center', gap: 4, - width: '100%', - height: '100%', + w: 'full', + h: 'full', }} > @@ -68,11 +133,14 @@ const UnifiedCanvasContent = () => { alignItems: 'center', justifyContent: 'center', gap: 4, - width: '100%', - height: '100%', + w: 'full', + h: 'full', }} > - {doesCanvasNeedScaling ? : } + + {doesCanvasNeedScaling ? : } + {active && } + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx index f2529e5529..42e19eb096 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx @@ -1,5 +1,5 @@ import { memo } from 'react'; -import { Flex } from '@chakra-ui/react'; +import { Box, Flex, useDisclosure } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { useAppSelector } from 'app/store/storeHooks'; @@ -7,11 +7,12 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; -import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; -import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength'; -import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit'; import ParamSchedulerAndModel from 'features/parameters/components/Parameters/Core/ParamSchedulerAndModel'; +import ParamBoundingBoxWidth from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth'; +import ParamBoundingBoxHeight from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight'; +import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull'; +import IAICollapse from 'common/components/IAICollapse'; const selector = createSelector( uiSelector, @@ -25,42 +26,46 @@ const selector = createSelector( const UnifiedCanvasCoreParameters = () => { const { shouldUseSliders } = useAppSelector(selector); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); return ( - - {shouldUseSliders ? ( - - - - - - - - - - - ) : ( - - + + + {shouldUseSliders ? ( + <> + + + + - - - - - - - )} - + + + + ) : ( + <> + + + + + + + + + + + + + )} + + + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx index 4aa68ad56a..061ebb962e 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx @@ -1,14 +1,14 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; -import ParamSeedCollapse from 'features/parameters/components/Parameters/Seed/ParamSeedCollapse'; import ParamVariationCollapse from 'features/parameters/components/Parameters/Variations/ParamVariationCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; -import ParamBoundingBoxCollapse from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse'; import ParamInfillAndScalingCollapse from 'features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse'; import ParamSeamCorrectionCollapse from 'features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse'; import UnifiedCanvasCoreParameters from './UnifiedCanvasCoreParameters'; import { memo } from 'react'; import ParamPositiveConditioning from 'features/parameters/components/Parameters/Core/ParamPositiveConditioning'; import ParamNegativeConditioning from 'features/parameters/components/Parameters/Core/ParamNegativeConditioning'; +import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; +import ParamDynamicPromptsCollapse from 'features/dynamicPrompts/components/ParamDynamicPromptsCollapse'; const UnifiedCanvasParameters = () => { return ( @@ -17,10 +17,10 @@ const UnifiedCanvasParameters = () => { - + + - diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx index 2d591d1ecc..4c36c45e13 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx @@ -1,34 +1,16 @@ import { Flex } from '@chakra-ui/react'; import { memo } from 'react'; -import { createSelector } from '@reduxjs/toolkit'; -import { uiSelector } from 'features/ui/store/uiSelectors'; -import { useAppSelector } from 'app/store/storeHooks'; +import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; import UnifiedCanvasContent from './UnifiedCanvasContent'; import UnifiedCanvasParameters from './UnifiedCanvasParameters'; -import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; -import ParametersPinnedWrapper from '../../ParametersPinnedWrapper'; - -const selector = createSelector(uiSelector, (ui) => { - const { shouldUseCanvasBetaLayout } = ui; - - return { - shouldUseCanvasBetaLayout, - }; -}); const UnifiedCanvasTab = () => { - const { shouldUseCanvasBetaLayout } = useAppSelector(selector); - return ( - {shouldUseCanvasBetaLayout ? ( - - ) : ( - - )} + ); }; diff --git a/invokeai/frontend/web/src/features/ui/hooks/useMinimumPanelSize.ts b/invokeai/frontend/web/src/features/ui/hooks/useMinimumPanelSize.ts new file mode 100644 index 0000000000..b382b27329 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/hooks/useMinimumPanelSize.ts @@ -0,0 +1,70 @@ +// adapted from https://github.com/bvaughn/react-resizable-panels/issues/141#issuecomment-1540048714 + +import { + RefObject, + useCallback, + useLayoutEffect, + useRef, + useState, +} from 'react'; +import { ImperativePanelHandle } from 'react-resizable-panels'; + +export const useMinimumPanelSize = ( + minSizePx: number, + defaultSizePct: number, + groupId: string, + orientation: 'horizontal' | 'vertical' = 'horizontal' +): { ref: RefObject; minSizePct: number } => { + const ref = useRef(null); + const [minSizePct, setMinSizePct] = useState(defaultSizePct); + + const handleWindowResize = useCallback(() => { + const size = ref.current?.getSize(); + + if (size !== undefined && size < minSizePct) { + ref.current?.resize(minSizePct); + } + }, [minSizePct]); + + useLayoutEffect(() => { + const panelGroup = document.querySelector( + `[data-panel-group-id="${groupId}"]` + ); + const resizeHandles = document.querySelectorAll( + '[data-panel-resize-handle-id]' + ); + + if (!panelGroup) { + return; + } + const observer = new ResizeObserver(() => { + let dim = + orientation === 'horizontal' + ? panelGroup.getBoundingClientRect().width + : panelGroup.getBoundingClientRect().height; + + resizeHandles.forEach((resizeHandle) => { + dim -= + orientation === 'horizontal' + ? resizeHandle.getBoundingClientRect().width + : resizeHandle.getBoundingClientRect().height; + }); + + // Minimum size in pixels is a percentage of the PanelGroup's width/height + setMinSizePct((minSizePx / dim) * 100); + }); + observer.observe(panelGroup); + resizeHandles.forEach((resizeHandle) => { + observer.observe(resizeHandle); + }); + + window.addEventListener('resize', handleWindowResize); + + return () => { + observer.disconnect(); + window.removeEventListener('resize', handleWindowResize); + }; + }, [groupId, handleWindowResize, minSizePct, minSizePx, orientation]); + + return { ref, minSizePct }; +}; diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 4893bb3bf6..38af668cac 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -1,14 +1,13 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; +import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { setActiveTabReducer } from './extraReducers'; import { InvokeTabName } from './tabMap'; import { AddNewModelType, UIState } from './uiTypes'; -import { initialImageChanged } from 'features/parameters/store/generationSlice'; -import { SCHEDULERS } from 'app/constants'; +import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas'; export const initialUIState: UIState = { activeTab: 0, - currentTheme: 'dark', shouldPinParametersPanel: true, shouldShowParametersPanel: true, shouldShowImageDetails: false, @@ -19,8 +18,8 @@ export const initialUIState: UIState = { shouldPinGallery: true, shouldShowGallery: true, shouldHidePreview: false, - shouldShowProgressInViewer: false, - schedulers: SCHEDULERS, + shouldShowProgressInViewer: true, + favoriteSchedulers: [], }; export const uiSlice = createSlice({ @@ -30,9 +29,6 @@ export const uiSlice = createSlice({ setActiveTab: (state, action: PayloadAction) => { setActiveTabReducer(state, action.payload); }, - setCurrentTheme: (state, action: PayloadAction) => { - state.currentTheme = action.payload; - }, setShouldPinParametersPanel: (state, action: PayloadAction) => { state.shouldPinParametersPanel = action.payload; state.shouldShowParametersPanel = true; @@ -94,9 +90,11 @@ export const uiSlice = createSlice({ setShouldShowProgressInViewer: (state, action: PayloadAction) => { state.shouldShowProgressInViewer = action.payload; }, - setSchedulers: (state, action: PayloadAction) => { - state.schedulers = []; - state.schedulers = action.payload; + favoriteSchedulersChanged: ( + state, + action: PayloadAction + ) => { + state.favoriteSchedulers = action.payload; }, }, extraReducers(builder) { @@ -108,7 +106,6 @@ export const uiSlice = createSlice({ export const { setActiveTab, - setCurrentTheme, setShouldPinParametersPanel, setShouldShowParametersPanel, setShouldShowImageDetails, @@ -124,7 +121,7 @@ export const { toggleParametersPanel, toggleGalleryPanel, setShouldShowProgressInViewer, - setSchedulers, + favoriteSchedulersChanged, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 18a758cdd6..d55a1d8fcf 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -1,3 +1,5 @@ +import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas'; + export type AddNewModelType = 'ckpt' | 'diffusers' | null; export type Coordinates = { @@ -14,7 +16,6 @@ export type Rect = Coordinates & Dimensions; export interface UIState { activeTab: number; - currentTheme: string; shouldPinParametersPanel: boolean; shouldShowParametersPanel: boolean; shouldShowImageDetails: boolean; @@ -26,5 +27,5 @@ export interface UIState { shouldPinGallery: boolean; shouldShowGallery: boolean; shouldShowProgressInViewer: boolean; - schedulers: string[]; + favoriteSchedulers: SchedulerParam[]; } diff --git a/invokeai/frontend/web/src/i18.d.ts b/invokeai/frontend/web/src/i18.d.ts index 90cee53385..aac2653d4f 100644 --- a/invokeai/frontend/web/src/i18.d.ts +++ b/invokeai/frontend/web/src/i18.d.ts @@ -1,17 +1,20 @@ -import 'i18next'; +// TODO: Disabled for IDE performance issues with our translation JSON -import en from '../public/locales/en.json'; +// import 'i18next'; -declare module 'i18next' { - // Extend CustomTypeOptions - interface CustomTypeOptions { - // Setting Default Namespace As English - defaultNS: 'en'; - // Custom Types For Resources - resources: { - en: typeof en; - }; - // Never Return Null - returnNull: false; - } -} +// import en from '../public/locales/en.json'; + +// declare module 'i18next' { +// // Extend CustomTypeOptions +// interface CustomTypeOptions { +// // Setting Default Namespace As English +// defaultNS: 'en'; +// // Custom Types For Resources +// resources: { +// en: typeof en; +// }; +// // Never Return Null +// returnNull: false; +// } +// } +export default {}; diff --git a/invokeai/frontend/web/src/i18n.ts b/invokeai/frontend/web/src/i18n.ts index 68b457eabe..706086cf51 100644 --- a/invokeai/frontend/web/src/i18n.ts +++ b/invokeai/frontend/web/src/i18n.ts @@ -3,6 +3,8 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; import { initReactI18next } from 'react-i18next'; +// TODO: Disabled for IDE performance issues with our translation JSON +// @ts-ignore import translationEN from '../public/locales/en.json'; import { LOCALSTORAGE_PREFIX } from 'app/store/constants'; diff --git a/invokeai/frontend/web/src/index.ts b/invokeai/frontend/web/src/index.ts index 274f9d5a0b..e70e756ed9 100644 --- a/invokeai/frontend/web/src/index.ts +++ b/invokeai/frontend/web/src/index.ts @@ -1,9 +1,9 @@ -export { default as InvokeAiLogoComponent } from './features/system/components/InvokeAILogoComponent'; -export { default as ThemeChanger } from './features/system/components/ThemeChanger'; -export { default as IAIPopover } from './common/components/IAIPopover'; -export { default as IAIIconButton } from './common/components/IAIIconButton'; -export { default as SettingsModal } from './features/system/components/SettingsModal/SettingsModal'; -export { default as StatusIndicator } from './features/system/components/StatusIndicator'; -export { default as ModelSelect } from './features/system/components/ModelSelect'; export { default as InvokeAIUI } from './app/components/InvokeAIUI'; export type { PartialAppConfig } from './app/types/invokeai'; +export { default as IAIIconButton } from './common/components/IAIIconButton'; +export { default as IAIPopover } from './common/components/IAIPopover'; +export { default as InvokeAiLogoComponent } from './features/system/components/InvokeAILogoComponent'; +export { default as ModelSelect } from './features/system/components/ModelSelect'; +export { default as SettingsModal } from './features/system/components/SettingsModal/SettingsModal'; +export { default as StatusIndicator } from './features/system/components/StatusIndicator'; +export { default as ColorModeButton } from './features/system/components/ColorModeButton'; diff --git a/invokeai/frontend/web/src/mantine-theme/theme.ts b/invokeai/frontend/web/src/mantine-theme/theme.ts new file mode 100644 index 0000000000..21dbd285f1 --- /dev/null +++ b/invokeai/frontend/web/src/mantine-theme/theme.ts @@ -0,0 +1,23 @@ +import { MantineThemeOverride } from '@mantine/core'; + +export const mantineTheme: MantineThemeOverride = { + colorScheme: 'dark', + fontFamily: `'Inter Variable', sans-serif`, + components: { + ScrollArea: { + defaultProps: { + scrollbarSize: 10, + }, + styles: { + scrollbar: { + '&:hover': { + backgroundColor: 'var(--invokeai-colors-baseAlpha-300)', + }, + }, + thumb: { + backgroundColor: 'var(--invokeai-colors-baseAlpha-300)', + }, + }, + }, + }, +}; diff --git a/invokeai/frontend/web/src/services/api/client.ts b/invokeai/frontend/web/src/services/api/client.ts new file mode 100644 index 0000000000..dd4caa460e --- /dev/null +++ b/invokeai/frontend/web/src/services/api/client.ts @@ -0,0 +1,33 @@ +import { atom, computed } from 'nanostores'; +import createClient from 'openapi-fetch'; +import { paths } from 'services/api/schema'; + +/** + * We use nanostores to store the token and base url for very simple reactivity + */ + +/** + * The user's auth token. + */ +export const $authToken = atom(); + +/** + * The OpenAPI base url. + */ +export const $baseUrl = atom(); + +/** + * Autogenerated, type-safe fetch client for the API. Used when RTK Query is not an option. + * Dynamically updates when the token or base url changes. + * Use `$client.get()` to get the client. + * + * @example + * const { get, post, del } = $client.get(); + */ +export const $client = computed([$authToken, $baseUrl], (authToken, baseUrl) => + createClient({ + headers: authToken ? { Authorization: `Bearer ${authToken}` } : {}, + // do not include `api/v1` in the base url for this client + baseUrl: `${baseUrl ?? ''}`, + }) +); diff --git a/invokeai/frontend/web/src/services/api/core/ApiError.ts b/invokeai/frontend/web/src/services/api/core/ApiError.ts deleted file mode 100644 index 41a9605a3a..0000000000 --- a/invokeai/frontend/web/src/services/api/core/ApiError.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; - -export class ApiError extends Error { - public readonly url: string; - public readonly status: number; - public readonly statusText: string; - public readonly body: any; - public readonly request: ApiRequestOptions; - - constructor(request: ApiRequestOptions, response: ApiResult, message: string) { - super(message); - - this.name = 'ApiError'; - this.url = response.url; - this.status = response.status; - this.statusText = response.statusText; - this.body = response.body; - this.request = request; - } -} diff --git a/invokeai/frontend/web/src/services/api/core/ApiRequestOptions.ts b/invokeai/frontend/web/src/services/api/core/ApiRequestOptions.ts deleted file mode 100644 index c9350406a1..0000000000 --- a/invokeai/frontend/web/src/services/api/core/ApiRequestOptions.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type ApiRequestOptions = { - readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; - readonly url: string; - readonly path?: Record; - readonly cookies?: Record; - readonly headers?: Record; - readonly query?: Record; - readonly formData?: Record; - readonly body?: any; - readonly mediaType?: string; - readonly responseHeader?: string; - readonly errors?: Record; -}; diff --git a/invokeai/frontend/web/src/services/api/core/ApiResult.ts b/invokeai/frontend/web/src/services/api/core/ApiResult.ts deleted file mode 100644 index 91f60ae082..0000000000 --- a/invokeai/frontend/web/src/services/api/core/ApiResult.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type ApiResult = { - readonly url: string; - readonly ok: boolean; - readonly status: number; - readonly statusText: string; - readonly body: any; -}; diff --git a/invokeai/frontend/web/src/services/api/core/CancelablePromise.ts b/invokeai/frontend/web/src/services/api/core/CancelablePromise.ts deleted file mode 100644 index a0f92e01b7..0000000000 --- a/invokeai/frontend/web/src/services/api/core/CancelablePromise.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export class CancelError extends Error { - - constructor(message: string) { - super(message); - this.name = 'CancelError'; - } - - public get isCancelled(): boolean { - return true; - } -} - -export interface OnCancel { - readonly isResolved: boolean; - readonly isRejected: boolean; - readonly isCancelled: boolean; - - (cancelHandler: () => void): void; -} - -export class CancelablePromise implements Promise { - #isResolved: boolean; - #isRejected: boolean; - #isCancelled: boolean; - readonly #cancelHandlers: (() => void)[]; - readonly #promise: Promise; - #resolve?: (value: T | PromiseLike) => void; - #reject?: (reason?: any) => void; - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: OnCancel - ) => void - ) { - this.#isResolved = false; - this.#isRejected = false; - this.#isCancelled = false; - this.#cancelHandlers = []; - this.#promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - - const onResolve = (value: T | PromiseLike): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isResolved = true; - this.#resolve?.(value); - }; - - const onReject = (reason?: any): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isRejected = true; - this.#reject?.(reason); - }; - - const onCancel = (cancelHandler: () => void): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#cancelHandlers.push(cancelHandler); - }; - - Object.defineProperty(onCancel, 'isResolved', { - get: (): boolean => this.#isResolved, - }); - - Object.defineProperty(onCancel, 'isRejected', { - get: (): boolean => this.#isRejected, - }); - - Object.defineProperty(onCancel, 'isCancelled', { - get: (): boolean => this.#isCancelled, - }); - - return executor(onResolve, onReject, onCancel as OnCancel); - }); - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise"; - } - - public then( - onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onRejected?: ((reason: any) => TResult2 | PromiseLike) | null - ): Promise { - return this.#promise.then(onFulfilled, onRejected); - } - - public catch( - onRejected?: ((reason: any) => TResult | PromiseLike) | null - ): Promise { - return this.#promise.catch(onRejected); - } - - public finally(onFinally?: (() => void) | null): Promise { - return this.#promise.finally(onFinally); - } - - public cancel(): void { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isCancelled = true; - if (this.#cancelHandlers.length) { - try { - for (const cancelHandler of this.#cancelHandlers) { - cancelHandler(); - } - } catch (error) { - console.warn('Cancellation threw an error', error); - return; - } - } - this.#cancelHandlers.length = 0; - this.#reject?.(new CancelError('Request aborted')); - } - - public get isCancelled(): boolean { - return this.#isCancelled; - } -} diff --git a/invokeai/frontend/web/src/services/api/core/OpenAPI.ts b/invokeai/frontend/web/src/services/api/core/OpenAPI.ts deleted file mode 100644 index ba65dcd55d..0000000000 --- a/invokeai/frontend/web/src/services/api/core/OpenAPI.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; - -type Resolver = (options: ApiRequestOptions) => Promise; -type Headers = Record; - -export type OpenAPIConfig = { - BASE: string; - VERSION: string; - WITH_CREDENTIALS: boolean; - CREDENTIALS: 'include' | 'omit' | 'same-origin'; - TOKEN?: string | Resolver; - USERNAME?: string | Resolver; - PASSWORD?: string | Resolver; - HEADERS?: Headers | Resolver; - ENCODE_PATH?: (path: string) => string; -}; - -export const OpenAPI: OpenAPIConfig = { - BASE: '', - VERSION: '1.0.0', - WITH_CREDENTIALS: false, - CREDENTIALS: 'include', - TOKEN: undefined, - USERNAME: undefined, - PASSWORD: undefined, - HEADERS: undefined, - ENCODE_PATH: undefined, -}; diff --git a/invokeai/frontend/web/src/services/api/core/request.ts b/invokeai/frontend/web/src/services/api/core/request.ts deleted file mode 100644 index 51c5f0f708..0000000000 --- a/invokeai/frontend/web/src/services/api/core/request.ts +++ /dev/null @@ -1,353 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * DO NOT DELETE EVEN THOUGH IT IS NOT USED! - * - * Custom `request.ts` file for OpenAPI code generator. - * - * Patches the request logic in such a way that we can extract headers from requests. - * - * Copied from https://github.com/ferdikoomen/openapi-typescript-codegen/issues/829#issuecomment-1228224477 - * - * This file should be excluded in `tsconfig.json` and ignored by prettier/eslint! - */ - -import axios from 'axios'; -import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; -import FormData from 'form-data'; - -import { ApiError } from './ApiError'; -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; -import { CancelablePromise } from './CancelablePromise'; -import type { OnCancel } from './CancelablePromise'; -import type { OpenAPIConfig } from './OpenAPI'; - -export const HEADERS = Symbol('HEADERS'); - -const isDefined = ( - value: T | null | undefined -): value is Exclude => { - return value !== undefined && value !== null; -}; - -const isString = (value: any): value is string => { - return typeof value === 'string'; -}; - -const isStringWithValue = (value: any): value is string => { - return isString(value) && value !== ''; -}; - -const isBlob = (value: any): value is Blob => { - return ( - typeof value === 'object' && - typeof value.type === 'string' && - typeof value.stream === 'function' && - typeof value.arrayBuffer === 'function' && - typeof value.constructor === 'function' && - typeof value.constructor.name === 'string' && - /^(Blob|File)$/.test(value.constructor.name) && - /^(Blob|File)$/.test(value[Symbol.toStringTag]) - ); -}; - -const isFormData = (value: any): value is FormData => { - return value instanceof FormData; -}; - -const isSuccess = (status: number): boolean => { - return status >= 200 && status < 300; -}; - -const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString('base64'); - } -}; - -const getQueryString = (params: Record): string => { - const qs: string[] = []; - - const append = (key: string, value: any) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; - - const process = (key: string, value: any) => { - if (isDefined(value)) { - if (Array.isArray(value)) { - value.forEach((v) => { - process(key, v); - }); - } else if (typeof value === 'object') { - Object.entries(value).forEach(([k, v]) => { - process(`${key}[${k}]`, v); - }); - } else { - append(key, value); - } - } - }; - - Object.entries(params).forEach(([key, value]) => { - process(key, value); - }); - - if (qs.length > 0) { - return `?${qs.join('&')}`; - } - - return ''; -}; - -const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace('{api-version}', config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); - - const url = `${config.BASE}${path}`; - if (options.query) { - return `${url}${getQueryString(options.query)}`; - } - return url; -}; - -const getFormData = (options: ApiRequestOptions): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); - - const process = (key: string, value: any) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; - - Object.entries(options.formData) - .filter(([_, value]) => isDefined(value)) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach((v) => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; -}; - -type Resolver = (options: ApiRequestOptions) => Promise; - -const resolve = async ( - options: ApiRequestOptions, - resolver?: T | Resolver -): Promise => { - if (typeof resolver === 'function') { - return (resolver as Resolver)(options); - } - return resolver; -}; - -const getHeaders = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - formData?: FormData -): Promise> => { - const token = await resolve(options, config.TOKEN); - const username = await resolve(options, config.USERNAME); - const password = await resolve(options, config.PASSWORD); - const additionalHeaders = await resolve(options, config.HEADERS); - const formHeaders = - (typeof formData?.getHeaders === 'function' && formData?.getHeaders()) || - {}; - - const headers = Object.entries({ - Accept: 'application/json', - ...additionalHeaders, - ...options.headers, - ...formHeaders, - }) - .filter(([_, value]) => isDefined(value)) - .reduce( - (headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), - {} as Record - ); - - if (isStringWithValue(token)) { - headers['Authorization'] = `Bearer ${token}`; - } - - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers['Authorization'] = `Basic ${credentials}`; - } - - if (options.body) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } else if (isBlob(options.body)) { - headers['Content-Type'] = options.body.type || 'application/octet-stream'; - } else if (isString(options.body)) { - headers['Content-Type'] = 'text/plain'; - } else if (!isFormData(options.body)) { - headers['Content-Type'] = 'application/json'; - } - } - - return headers; -}; - -const getRequestBody = (options: ApiRequestOptions): any => { - if (options.body) { - return options.body; - } - return undefined; -}; - -const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: any, - formData: FormData | undefined, - headers: Record, - onCancel: OnCancel -): Promise> => { - const source = axios.CancelToken.source(); - - const requestConfig: AxiosRequestConfig = { - url, - headers, - data: body ?? formData, - method: options.method, - withCredentials: config.WITH_CREDENTIALS, - cancelToken: source.token, - }; - - onCancel(() => source.cancel('The user aborted a request.')); - - try { - return await axios.request(requestConfig); - } catch (error) { - const axiosError = error as AxiosError; - if (axiosError.response) { - return axiosError.response; - } - throw error; - } -}; - -const getResponseHeader = ( - response: AxiosResponse, - responseHeader?: string -): string | undefined => { - if (responseHeader) { - const content = response.headers[responseHeader]; - if (isString(content)) { - return content; - } - } - return undefined; -}; - -const getResponseBody = (response: AxiosResponse): any => { - if (response.status !== 204) { - return response.data; - } - return undefined; -}; - -const catchErrorCodes = ( - options: ApiRequestOptions, - result: ApiResult -): void => { - const errors: Record = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Forbidden', - 404: 'Not Found', - 500: 'Internal Server Error', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - ...options.errors, - }; - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - throw new ApiError(options, result, 'Generic Error'); - } -}; - -/** - * Request method - * @param config The OpenAPI configuration object - * @param options The request options from the service - * @returns CancelablePromise - * @throws ApiError - */ -export const request = ( - config: OpenAPIConfig, - options: ApiRequestOptions -): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options, formData); - - if (!onCancel.isCancelled) { - const response = await sendRequest( - config, - options, - url, - body, - formData, - headers, - onCancel - ); - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader( - response, - options.responseHeader - ); - - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader ?? responseBody, - }; - - catchErrorCodes(options, result); - - resolve({ ...result.body, [HEADERS]: response.headers }); - } - } catch (error) { - reject(error); - } - }); -}; diff --git a/invokeai/frontend/web/src/services/api/endpoints/boardImages.ts b/invokeai/frontend/web/src/services/api/endpoints/boardImages.ts new file mode 100644 index 0000000000..cef9ab7cae --- /dev/null +++ b/invokeai/frontend/web/src/services/api/endpoints/boardImages.ts @@ -0,0 +1,66 @@ +import { OffsetPaginatedResults_ImageDTO_ } from 'services/api/types'; +import { api } from '..'; +import { paths } from '../schema'; + +type ListBoardImagesArg = + paths['/api/v1/board_images/{board_id}']['get']['parameters']['path'] & + paths['/api/v1/board_images/{board_id}']['get']['parameters']['query']; + +type AddImageToBoardArg = + paths['/api/v1/board_images/']['post']['requestBody']['content']['application/json']; + +type RemoveImageFromBoardArg = + paths['/api/v1/board_images/']['delete']['requestBody']['content']['application/json']; + +export const boardImagesApi = api.injectEndpoints({ + endpoints: (build) => ({ + /** + * Board Images Queries + */ + + listBoardImages: build.query< + OffsetPaginatedResults_ImageDTO_, + ListBoardImagesArg + >({ + query: ({ board_id, offset, limit }) => ({ + url: `board_images/${board_id}`, + method: 'DELETE', + body: { offset, limit }, + }), + }), + + /** + * Board Images Mutations + */ + + addImageToBoard: build.mutation({ + query: ({ board_id, image_name }) => ({ + url: `board_images/`, + method: 'POST', + body: { board_id, image_name }, + }), + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + { type: 'Image', id: arg.image_name }, + ], + }), + + removeImageFromBoard: build.mutation({ + query: ({ board_id, image_name }) => ({ + url: `board_images/`, + method: 'DELETE', + body: { board_id, image_name }, + }), + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + { type: 'Image', id: arg.image_name }, + ], + }), + }), +}); + +export const { + useAddImageToBoardMutation, + useRemoveImageFromBoardMutation, + useListBoardImagesQuery, +} = boardImagesApi; diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts new file mode 100644 index 0000000000..64ab21075d --- /dev/null +++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts @@ -0,0 +1,103 @@ +import { BoardDTO, OffsetPaginatedResults_BoardDTO_ } from 'services/api/types'; +import { ApiFullTagDescription, LIST_TAG, api } from '..'; +import { paths } from '../schema'; + +type ListBoardsArg = NonNullable< + paths['/api/v1/boards/']['get']['parameters']['query'] +>; + +type UpdateBoardArg = + paths['/api/v1/boards/{board_id}']['patch']['parameters']['path'] & { + changes: paths['/api/v1/boards/{board_id}']['patch']['requestBody']['content']['application/json']; + }; + +export const boardsApi = api.injectEndpoints({ + endpoints: (build) => ({ + /** + * Boards Queries + */ + listBoards: build.query({ + query: (arg) => ({ url: 'boards/', params: arg }), + providesTags: (result, error, arg) => { + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST_TAG }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.items.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })) + ); + } + + return tags; + }, + }), + + listAllBoards: build.query, void>({ + query: () => ({ + url: 'boards/', + params: { all: true }, + }), + providesTags: (result, error, arg) => { + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST_TAG }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.map(({ board_id }) => ({ + type: 'Board' as const, + id: board_id, + })) + ); + } + + return tags; + }, + }), + + /** + * Boards Mutations + */ + + createBoard: build.mutation({ + query: (board_name) => ({ + url: `boards/`, + method: 'POST', + params: { board_name }, + }), + invalidatesTags: [{ id: 'Board', type: LIST_TAG }], + }), + + updateBoard: build.mutation({ + query: ({ board_id, changes }) => ({ + url: `boards/${board_id}`, + method: 'PATCH', + body: changes, + }), + invalidatesTags: (result, error, arg) => [ + { type: 'Board', id: arg.board_id }, + ], + }), + deleteBoard: build.mutation({ + query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }), + invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }], + }), + deleteBoardAndImages: build.mutation({ + query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE', params: { include_images: true } }), + invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }, { type: 'Image', id: LIST_TAG }], + }), + }), +}); + +export const { + useListBoardsQuery, + useListAllBoardsQuery, + useCreateBoardMutation, + useUpdateBoardMutation, + useDeleteBoardMutation, + useDeleteBoardAndImagesMutation +} = boardsApi; diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts new file mode 100644 index 0000000000..5090fc4fc1 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -0,0 +1,23 @@ +import { ApiFullTagDescription, api } from '..'; +import { ImageDTO } from '../types'; + +export const imagesApi = api.injectEndpoints({ + endpoints: (build) => ({ + /** + * Image Queries + */ + getImageDTO: build.query({ + query: (image_name) => ({ url: `images/${image_name}/metadata` }), + providesTags: (result, error, arg) => { + const tags: ApiFullTagDescription[] = [{ type: 'Image', id: arg }]; + if (result?.board_id) { + tags.push({ type: 'Board', id: result.board_id }); + } + return tags; + }, + keepUnusedDataFor: 86400, // 24 hours + }), + }), +}); + +export const { useGetImageDTOQuery } = imagesApi; diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts new file mode 100644 index 0000000000..39e4e46d3b --- /dev/null +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -0,0 +1,52 @@ +import { ModelsList } from 'services/api/types'; +import { EntityState, createEntityAdapter } from '@reduxjs/toolkit'; +import { keyBy } from 'lodash-es'; + +import { ApiFullTagDescription, LIST_TAG, api } from '..'; +import { paths } from '../schema'; + +type ModelConfig = ModelsList['models'][number]; + +type ListModelsArg = NonNullable< + paths['/api/v1/models/']['get']['parameters']['query'] +>; + +const modelsAdapter = createEntityAdapter({ + selectId: (model) => getModelId(model), + sortComparer: (a, b) => a.name.localeCompare(b.name), +}); + +const getModelId = ({ base_model, type, name }: ModelConfig) => + `${base_model}/${type}/${name}`; + +export const modelsApi = api.injectEndpoints({ + endpoints: (build) => ({ + listModels: build.query, ListModelsArg>({ + query: (arg) => ({ url: 'models/', params: arg }), + providesTags: (result, error, arg) => { + // any list of boards + const tags: ApiFullTagDescription[] = [{ id: 'Model', type: LIST_TAG }]; + + if (result) { + // and individual tags for each board + tags.push( + ...result.ids.map((id) => ({ + type: 'Model' as const, + id, + })) + ); + } + + return tags; + }, + transformResponse: (response: ModelsList, meta, arg) => { + return modelsAdapter.setAll( + modelsAdapter.getInitialState(), + keyBy(response.models, getModelId) + ); + }, + }), + }), +}); + +export const { useListModelsQuery } = modelsApi; diff --git a/invokeai/frontend/web/src/services/api/guards.ts b/invokeai/frontend/web/src/services/api/guards.ts new file mode 100644 index 0000000000..0a28617f98 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/guards.ts @@ -0,0 +1,77 @@ +import { get, isObject, isString } from 'lodash-es'; +import { + GraphExecutionState, + GraphInvocationOutput, + ImageOutput, + MaskOutput, + PromptOutput, + IterateInvocationOutput, + CollectInvocationOutput, + ImageField, + LatentsOutput, + ResourceOrigin, + ImageDTO, + BoardDTO, +} from 'services/api/types'; + +export const isImageDTO = (obj: unknown): obj is ImageDTO => { + return ( + isObject(obj) && + 'image_name' in obj && + isString(obj?.image_name) && + 'thumbnail_url' in obj && + isString(obj?.thumbnail_url) && + 'image_url' in obj && + isString(obj?.image_url) && + 'image_origin' in obj && + isString(obj?.image_origin) && + 'created_at' in obj && + isString(obj?.created_at) + ); +}; + +export const isBoardDTO = (obj: unknown): obj is BoardDTO => { + return ( + isObject(obj) && + 'board_id' in obj && + isString(obj?.board_id) && + 'board_name' in obj && + isString(obj?.board_name) + ); +}; + +export const isImageOutput = ( + output: GraphExecutionState['results'][string] +): output is ImageOutput => output?.type === 'image_output'; + +export const isLatentsOutput = ( + output: GraphExecutionState['results'][string] +): output is LatentsOutput => output?.type === 'latents_output'; + +export const isMaskOutput = ( + output: GraphExecutionState['results'][string] +): output is MaskOutput => output?.type === 'mask'; + +export const isPromptOutput = ( + output: GraphExecutionState['results'][string] +): output is PromptOutput => output?.type === 'prompt'; + +export const isGraphOutput = ( + output: GraphExecutionState['results'][string] +): output is GraphInvocationOutput => output?.type === 'graph_output'; + +export const isIterateOutput = ( + output: GraphExecutionState['results'][string] +): output is IterateInvocationOutput => output?.type === 'iterate_output'; + +export const isCollectOutput = ( + output: GraphExecutionState['results'][string] +): output is CollectInvocationOutput => output?.type === 'collect_output'; + +export const isResourceOrigin = (t: unknown): t is ResourceOrigin => + isString(t) && ['internal', 'external'].includes(t); + +export const isImageField = (imageField: unknown): imageField is ImageField => + isObject(imageField) && + isString(get(imageField, 'image_name')) && + isResourceOrigin(get(imageField, 'image_origin')); diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index e75aeac6cb..f91c3b90fd 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -1,87 +1,44 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export { ApiError } from './core/ApiError'; -export { CancelablePromise, CancelError } from './core/CancelablePromise'; -export { OpenAPI } from './core/OpenAPI'; -export type { OpenAPIConfig } from './core/OpenAPI'; +import { + BaseQueryFn, + FetchArgs, + FetchBaseQueryError, + createApi, + fetchBaseQuery, +} from '@reduxjs/toolkit/query/react'; +import { FullTagDescription } from '@reduxjs/toolkit/dist/query/endpointDefinitions'; +import { $authToken, $baseUrl } from 'services/api/client'; -export type { AddInvocation } from './models/AddInvocation'; -export type { Body_upload_image } from './models/Body_upload_image'; -export type { CkptModelInfo } from './models/CkptModelInfo'; -export type { CollectInvocation } from './models/CollectInvocation'; -export type { CollectInvocationOutput } from './models/CollectInvocationOutput'; -export type { ColorField } from './models/ColorField'; -export type { CompelInvocation } from './models/CompelInvocation'; -export type { CompelOutput } from './models/CompelOutput'; -export type { ConditioningField } from './models/ConditioningField'; -export type { CreateModelRequest } from './models/CreateModelRequest'; -export type { CvInpaintInvocation } from './models/CvInpaintInvocation'; -export type { DiffusersModelInfo } from './models/DiffusersModelInfo'; -export type { DivideInvocation } from './models/DivideInvocation'; -export type { Edge } from './models/Edge'; -export type { EdgeConnection } from './models/EdgeConnection'; -export type { Graph } from './models/Graph'; -export type { GraphExecutionState } from './models/GraphExecutionState'; -export type { GraphInvocation } from './models/GraphInvocation'; -export type { GraphInvocationOutput } from './models/GraphInvocationOutput'; -export type { HTTPValidationError } from './models/HTTPValidationError'; -export type { ImageBlurInvocation } from './models/ImageBlurInvocation'; -export type { ImageCategory } from './models/ImageCategory'; -export type { ImageChannelInvocation } from './models/ImageChannelInvocation'; -export type { ImageConvertInvocation } from './models/ImageConvertInvocation'; -export type { ImageCropInvocation } from './models/ImageCropInvocation'; -export type { ImageDTO } from './models/ImageDTO'; -export type { ImageField } from './models/ImageField'; -export type { ImageInverseLerpInvocation } from './models/ImageInverseLerpInvocation'; -export type { ImageLerpInvocation } from './models/ImageLerpInvocation'; -export type { ImageMetadata } from './models/ImageMetadata'; -export type { ImageMultiplyInvocation } from './models/ImageMultiplyInvocation'; -export type { ImageOutput } from './models/ImageOutput'; -export type { ImagePasteInvocation } from './models/ImagePasteInvocation'; -export type { ImageRecordChanges } from './models/ImageRecordChanges'; -export type { ImageToImageInvocation } from './models/ImageToImageInvocation'; -export type { ImageToLatentsInvocation } from './models/ImageToLatentsInvocation'; -export type { ImageType } from './models/ImageType'; -export type { ImageUrlsDTO } from './models/ImageUrlsDTO'; -export type { InfillColorInvocation } from './models/InfillColorInvocation'; -export type { InfillPatchMatchInvocation } from './models/InfillPatchMatchInvocation'; -export type { InfillTileInvocation } from './models/InfillTileInvocation'; -export type { InpaintInvocation } from './models/InpaintInvocation'; -export type { IntCollectionOutput } from './models/IntCollectionOutput'; -export type { IntOutput } from './models/IntOutput'; -export type { IterateInvocation } from './models/IterateInvocation'; -export type { IterateInvocationOutput } from './models/IterateInvocationOutput'; -export type { LatentsField } from './models/LatentsField'; -export type { LatentsOutput } from './models/LatentsOutput'; -export type { LatentsToImageInvocation } from './models/LatentsToImageInvocation'; -export type { LatentsToLatentsInvocation } from './models/LatentsToLatentsInvocation'; -export type { LoadImageInvocation } from './models/LoadImageInvocation'; -export type { MaskFromAlphaInvocation } from './models/MaskFromAlphaInvocation'; -export type { MaskOutput } from './models/MaskOutput'; -export type { ModelsList } from './models/ModelsList'; -export type { MultiplyInvocation } from './models/MultiplyInvocation'; -export type { NoiseInvocation } from './models/NoiseInvocation'; -export type { NoiseOutput } from './models/NoiseOutput'; -export type { PaginatedResults_GraphExecutionState_ } from './models/PaginatedResults_GraphExecutionState_'; -export type { PaginatedResults_ImageDTO_ } from './models/PaginatedResults_ImageDTO_'; -export type { ParamIntInvocation } from './models/ParamIntInvocation'; -export type { PromptOutput } from './models/PromptOutput'; -export type { RandomIntInvocation } from './models/RandomIntInvocation'; -export type { RandomRangeInvocation } from './models/RandomRangeInvocation'; -export type { RangeInvocation } from './models/RangeInvocation'; -export type { RangeOfSizeInvocation } from './models/RangeOfSizeInvocation'; -export type { ResizeLatentsInvocation } from './models/ResizeLatentsInvocation'; -export type { RestoreFaceInvocation } from './models/RestoreFaceInvocation'; -export type { ScaleLatentsInvocation } from './models/ScaleLatentsInvocation'; -export type { ShowImageInvocation } from './models/ShowImageInvocation'; -export type { SubtractInvocation } from './models/SubtractInvocation'; -export type { TextToImageInvocation } from './models/TextToImageInvocation'; -export type { TextToLatentsInvocation } from './models/TextToLatentsInvocation'; -export type { UpscaleInvocation } from './models/UpscaleInvocation'; -export type { VaeRepo } from './models/VaeRepo'; -export type { ValidationError } from './models/ValidationError'; +export const tagTypes = ['Board', 'Image', 'Model']; +export type ApiFullTagDescription = FullTagDescription< + (typeof tagTypes)[number] +>; +export const LIST_TAG = 'LIST'; -export { ImagesService } from './services/ImagesService'; -export { ModelsService } from './services/ModelsService'; -export { SessionsService } from './services/SessionsService'; +const dynamicBaseQuery: BaseQueryFn< + string | FetchArgs, + unknown, + FetchBaseQueryError +> = async (args, api, extraOptions) => { + const baseUrl = $baseUrl.get(); + const authToken = $authToken.get(); + + const rawBaseQuery = fetchBaseQuery({ + baseUrl: `${baseUrl ?? ''}/api/v1`, + prepareHeaders: (headers) => { + if (authToken) { + headers.set('Authorization', `Bearer ${authToken}`); + } + + return headers; + }, + }); + + return rawBaseQuery(args, api, extraOptions); +}; + +export const api = createApi({ + baseQuery: dynamicBaseQuery, + reducerPath: 'api', + tagTypes, + endpoints: () => ({}), +}); diff --git a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts b/invokeai/frontend/web/src/services/api/models/AddInvocation.ts deleted file mode 100644 index e9671a918f..0000000000 --- a/invokeai/frontend/web/src/services/api/models/AddInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Adds two numbers - */ -export type AddInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'add'; - /** - * The first number - */ - 'a'?: number; - /** - * The second number - */ - 'b'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts b/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts deleted file mode 100644 index b81146d3ab..0000000000 --- a/invokeai/frontend/web/src/services/api/models/Body_upload_image.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type Body_upload_image = { - file: Blob; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts deleted file mode 100644 index 474f1d3f3c..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CannyImageProcessorInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Canny edge detection for ControlNet - */ -export type CannyImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'canny_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * low threshold of Canny pixel gradient - */ - low_threshold?: number; - /** - * high threshold of Canny pixel gradient - */ - high_threshold?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts b/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts deleted file mode 100644 index 2ae7c09674..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CkptModelInfo.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type CkptModelInfo = { - /** - * A description of the model - */ - description?: string; - format?: 'ckpt'; - /** - * The path to the model config - */ - config: string; - /** - * The path to the model weights - */ - weights: string; - /** - * The path to the model VAE - */ - vae: string; - /** - * The width of the model - */ - width?: number; - /** - * The height of the model - */ - height?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts deleted file mode 100644 index f190ab7073..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Collects values into a collection - */ -export type CollectInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'collect'; - /** - * The item to collect (all inputs must be of the same type) - */ - item?: any; - /** - * The collection, will be provided on execution - */ - collection?: Array; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts deleted file mode 100644 index a5976242ea..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CollectInvocationOutput.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Base class for all invocation outputs - */ -export type CollectInvocationOutput = { - type: 'collect_output'; - /** - * The collection of input items - */ - collection: Array; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ColorField.ts b/invokeai/frontend/web/src/services/api/models/ColorField.ts deleted file mode 100644 index e0a609ec12..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ColorField.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type ColorField = { - /** - * The red component - */ - 'r': number; - /** - * The green component - */ - 'g': number; - /** - * The blue component - */ - 'b': number; - /** - * The alpha component - */ - 'a': number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts b/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts deleted file mode 100644 index 1dc390c1be..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CompelInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Parse prompt using compel package to conditioning. - */ -export type CompelInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'compel'; - /** - * Prompt - */ - prompt?: string; - /** - * Model to use - */ - model?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts b/invokeai/frontend/web/src/services/api/models/CompelOutput.ts deleted file mode 100644 index 94f1fcb282..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CompelOutput.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ConditioningField } from './ConditioningField'; - -/** - * Compel parser output - */ -export type CompelOutput = { - type?: 'compel_output'; - /** - * Conditioning - */ - conditioning?: ConditioningField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts b/invokeai/frontend/web/src/services/api/models/ConditioningField.ts deleted file mode 100644 index 7e53a63b42..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ConditioningField.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type ConditioningField = { - /** - * The name of conditioning data - */ - conditioning_name: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts deleted file mode 100644 index 4a07508be7..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ContentShuffleImageProcessorInvocation.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies content shuffle processing to image - */ -export type ContentShuffleImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'content_shuffle_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * pixel resolution for edge detection - */ - detect_resolution?: number; - /** - * pixel resolution for output image - */ - image_resolution?: number; - /** - * content shuffle h parameter - */ - 'h'?: number; - /** - * content shuffle w parameter - */ - 'w'?: number; - /** - * cont - */ - 'f'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlField.ts b/invokeai/frontend/web/src/services/api/models/ControlField.ts deleted file mode 100644 index 4f493d4410..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ControlField.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -export type ControlField = { - /** - * processed image - */ - image: ImageField; - /** - * control model used - */ - control_model: string; - /** - * weight given to controlnet - */ - control_weight: number; - /** - * % of total steps at which controlnet is first applied - */ - begin_step_percent: number; - /** - * % of total steps at which controlnet is last applied - */ - end_step_percent: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts b/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts deleted file mode 100644 index e8372f43dd..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ControlNetInvocation.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Collects ControlNet info to pass to other nodes - */ -export type ControlNetInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'controlnet'; - /** - * image to process - */ - image?: ImageField; - /** - * control model used - */ - control_model?: 'lllyasviel/sd-controlnet-canny' | 'lllyasviel/sd-controlnet-depth' | 'lllyasviel/sd-controlnet-hed' | 'lllyasviel/sd-controlnet-seg' | 'lllyasviel/sd-controlnet-openpose' | 'lllyasviel/sd-controlnet-scribble' | 'lllyasviel/sd-controlnet-normal' | 'lllyasviel/sd-controlnet-mlsd' | 'lllyasviel/control_v11p_sd15_canny' | 'lllyasviel/control_v11p_sd15_openpose' | 'lllyasviel/control_v11p_sd15_seg' | 'lllyasviel/control_v11f1p_sd15_depth' | 'lllyasviel/control_v11p_sd15_normalbae' | 'lllyasviel/control_v11p_sd15_scribble' | 'lllyasviel/control_v11p_sd15_mlsd' | 'lllyasviel/control_v11p_sd15_softedge' | 'lllyasviel/control_v11p_sd15s2_lineart_anime' | 'lllyasviel/control_v11p_sd15_lineart' | 'lllyasviel/control_v11p_sd15_inpaint' | 'lllyasviel/control_v11e_sd15_shuffle' | 'lllyasviel/control_v11e_sd15_ip2p' | 'lllyasviel/control_v11f1e_sd15_tile' | 'thibaud/controlnet-sd21-openpose-diffusers' | 'thibaud/controlnet-sd21-canny-diffusers' | 'thibaud/controlnet-sd21-depth-diffusers' | 'thibaud/controlnet-sd21-scribble-diffusers' | 'thibaud/controlnet-sd21-hed-diffusers' | 'thibaud/controlnet-sd21-zoedepth-diffusers' | 'thibaud/controlnet-sd21-color-diffusers' | 'thibaud/controlnet-sd21-openposev2-diffusers' | 'thibaud/controlnet-sd21-lineart-diffusers' | 'thibaud/controlnet-sd21-normalbae-diffusers' | 'thibaud/controlnet-sd21-ade20k-diffusers' | 'CrucibleAI/ControlNetMediaPipeFace'; - /** - * weight given to controlnet - */ - control_weight?: number; - /** - * % of total steps at which controlnet is first applied - */ - begin_step_percent?: number; - /** - * % of total steps at which controlnet is last applied - */ - end_step_percent?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts b/invokeai/frontend/web/src/services/api/models/ControlOutput.ts deleted file mode 100644 index 43f1b3341c..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ControlOutput.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ControlField } from './ControlField'; - -/** - * node output for ControlNet info - */ -export type ControlOutput = { - type?: 'control_output'; - /** - * The control info dict - */ - control?: ControlField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts b/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts deleted file mode 100644 index 0b0f52b8fe..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CreateModelRequest.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { CkptModelInfo } from './CkptModelInfo'; -import type { DiffusersModelInfo } from './DiffusersModelInfo'; - -export type CreateModelRequest = { - /** - * The name of the model - */ - name: string; - /** - * The model info - */ - info: (CkptModelInfo | DiffusersModelInfo); -}; - diff --git a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts deleted file mode 100644 index 874df93c30..0000000000 --- a/invokeai/frontend/web/src/services/api/models/CvInpaintInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Simple inpaint using opencv. - */ -export type CvInpaintInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'cv_inpaint'; - /** - * The image to inpaint - */ - image?: ImageField; - /** - * The mask to use when inpainting - */ - mask?: ImageField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts b/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts deleted file mode 100644 index 5be4801cdd..0000000000 --- a/invokeai/frontend/web/src/services/api/models/DiffusersModelInfo.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { VaeRepo } from './VaeRepo'; - -export type DiffusersModelInfo = { - /** - * A description of the model - */ - description?: string; - format?: 'diffusers'; - /** - * The VAE repo to use for this model - */ - vae?: VaeRepo; - /** - * The repo ID to use for this model - */ - repo_id?: string; - /** - * The path to the model - */ - path?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts b/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts deleted file mode 100644 index fd5b3475ae..0000000000 --- a/invokeai/frontend/web/src/services/api/models/DivideInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Divides two numbers - */ -export type DivideInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'div'; - /** - * The first number - */ - 'a'?: number; - /** - * The second number - */ - 'b'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/Edge.ts b/invokeai/frontend/web/src/services/api/models/Edge.ts deleted file mode 100644 index bba275cb26..0000000000 --- a/invokeai/frontend/web/src/services/api/models/Edge.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { EdgeConnection } from './EdgeConnection'; - -export type Edge = { - /** - * The connection for the edge's from node and field - */ - source: EdgeConnection; - /** - * The connection for the edge's to node and field - */ - destination: EdgeConnection; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts b/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts deleted file mode 100644 index ecbddccd76..0000000000 --- a/invokeai/frontend/web/src/services/api/models/EdgeConnection.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type EdgeConnection = { - /** - * The id of the node for this edge connection - */ - node_id: string; - /** - * The field for this connection - */ - field: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/Graph.ts b/invokeai/frontend/web/src/services/api/models/Graph.ts deleted file mode 100644 index 6be925841b..0000000000 --- a/invokeai/frontend/web/src/services/api/models/Graph.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { AddInvocation } from './AddInvocation'; -import type { CollectInvocation } from './CollectInvocation'; -import type { CompelInvocation } from './CompelInvocation'; -import type { CvInpaintInvocation } from './CvInpaintInvocation'; -import type { DivideInvocation } from './DivideInvocation'; -import type { Edge } from './Edge'; -import type { GraphInvocation } from './GraphInvocation'; -import type { ImageBlurInvocation } from './ImageBlurInvocation'; -import type { ImageChannelInvocation } from './ImageChannelInvocation'; -import type { ImageConvertInvocation } from './ImageConvertInvocation'; -import type { ImageCropInvocation } from './ImageCropInvocation'; -import type { ImageInverseLerpInvocation } from './ImageInverseLerpInvocation'; -import type { ImageLerpInvocation } from './ImageLerpInvocation'; -import type { ImageMultiplyInvocation } from './ImageMultiplyInvocation'; -import type { ImagePasteInvocation } from './ImagePasteInvocation'; -import type { ImageToImageInvocation } from './ImageToImageInvocation'; -import type { ImageToLatentsInvocation } from './ImageToLatentsInvocation'; -import type { InfillColorInvocation } from './InfillColorInvocation'; -import type { InfillPatchMatchInvocation } from './InfillPatchMatchInvocation'; -import type { InfillTileInvocation } from './InfillTileInvocation'; -import type { InpaintInvocation } from './InpaintInvocation'; -import type { IterateInvocation } from './IterateInvocation'; -import type { LatentsToImageInvocation } from './LatentsToImageInvocation'; -import type { LatentsToLatentsInvocation } from './LatentsToLatentsInvocation'; -import type { LoadImageInvocation } from './LoadImageInvocation'; -import type { MaskFromAlphaInvocation } from './MaskFromAlphaInvocation'; -import type { MultiplyInvocation } from './MultiplyInvocation'; -import type { NoiseInvocation } from './NoiseInvocation'; -import type { ParamIntInvocation } from './ParamIntInvocation'; -import type { RandomIntInvocation } from './RandomIntInvocation'; -import type { RandomRangeInvocation } from './RandomRangeInvocation'; -import type { RangeInvocation } from './RangeInvocation'; -import type { RangeOfSizeInvocation } from './RangeOfSizeInvocation'; -import type { ResizeLatentsInvocation } from './ResizeLatentsInvocation'; -import type { RestoreFaceInvocation } from './RestoreFaceInvocation'; -import type { ScaleLatentsInvocation } from './ScaleLatentsInvocation'; -import type { ShowImageInvocation } from './ShowImageInvocation'; -import type { SubtractInvocation } from './SubtractInvocation'; -import type { TextToImageInvocation } from './TextToImageInvocation'; -import type { TextToLatentsInvocation } from './TextToLatentsInvocation'; -import type { UpscaleInvocation } from './UpscaleInvocation'; - -export type Graph = { - /** - * The id of this graph - */ - id?: string; - /** - * The nodes in this graph - */ - nodes?: Record; - /** - * The connections between nodes and their fields in this graph - */ - edges?: Array; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts b/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts deleted file mode 100644 index 8c2eb05657..0000000000 --- a/invokeai/frontend/web/src/services/api/models/GraphExecutionState.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { CollectInvocationOutput } from './CollectInvocationOutput'; -import type { CompelOutput } from './CompelOutput'; -import type { Graph } from './Graph'; -import type { GraphInvocationOutput } from './GraphInvocationOutput'; -import type { ImageOutput } from './ImageOutput'; -import type { IntCollectionOutput } from './IntCollectionOutput'; -import type { IntOutput } from './IntOutput'; -import type { IterateInvocationOutput } from './IterateInvocationOutput'; -import type { LatentsOutput } from './LatentsOutput'; -import type { MaskOutput } from './MaskOutput'; -import type { NoiseOutput } from './NoiseOutput'; -import type { PromptOutput } from './PromptOutput'; - -/** - * Tracks the state of a graph execution - */ -export type GraphExecutionState = { - /** - * The id of the execution state - */ - id: string; - /** - * The graph being executed - */ - graph: Graph; - /** - * The expanded graph of activated and executed nodes - */ - execution_graph: Graph; - /** - * The set of node ids that have been executed - */ - executed: Array; - /** - * The list of node ids that have been executed, in order of execution - */ - executed_history: Array; - /** - * The results of node executions - */ - results: Record; - /** - * Errors raised when executing nodes - */ - errors: Record; - /** - * The map of prepared nodes to original graph nodes - */ - prepared_source_mapping: Record; - /** - * The map of original graph nodes to prepared nodes - */ - source_prepared_mapping: Record>; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts deleted file mode 100644 index 8512faae74..0000000000 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocation.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { Graph } from './Graph'; - -/** - * Execute a graph - */ -export type GraphInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'graph'; - /** - * The graph to run - */ - graph?: Graph; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts deleted file mode 100644 index af0aae3edb..0000000000 --- a/invokeai/frontend/web/src/services/api/models/GraphInvocationOutput.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Base class for all invocation outputs - */ -export type GraphInvocationOutput = { - type: 'graph_output'; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts b/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts deleted file mode 100644 index 5e13adc4e5..0000000000 --- a/invokeai/frontend/web/src/services/api/models/HTTPValidationError.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ValidationError } from './ValidationError'; - -export type HTTPValidationError = { - detail?: Array; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/HedImageprocessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/HedImageprocessorInvocation.ts index 6dea43dc32..387e8c8634 100644 --- a/invokeai/frontend/web/src/services/api/models/HedImageprocessorInvocation.ts +++ b/invokeai/frontend/web/src/services/api/models/HedImageprocessorInvocation.ts @@ -7,27 +7,30 @@ import type { ImageField } from './ImageField'; /** * Applies HED edge detection to image */ -export type HedImageprocessorInvocation = { +export type HedImageProcessorInvocation = { /** * The id of this node. Must be unique among all nodes. */ id: string; + /** + * Whether or not this node is an intermediate node. + */ + is_intermediate?: boolean; type?: 'hed_image_processor'; /** - * image to process + * The image to process */ image?: ImageField; /** - * pixel resolution for edge detection + * The pixel resolution for detection */ detect_resolution?: number; /** - * pixel resolution for output image + * The pixel resolution for the output image */ image_resolution?: number; /** - * whether to use scribble mode + * Whether to use scribble mode */ scribble?: boolean; }; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts deleted file mode 100644 index 3ba86d8fab..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageBlurInvocation.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Blurs an image - */ -export type ImageBlurInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_blur'; - /** - * The image to blur - */ - image?: ImageField; - /** - * The blur radius - */ - radius?: number; - /** - * The type of blur - */ - blur_type?: 'gaussian' | 'box'; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageCategory.ts b/invokeai/frontend/web/src/services/api/models/ImageCategory.ts deleted file mode 100644 index 6b04a0b864..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageCategory.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * The category of an image. Use ImageCategory.OTHER for non-default categories. - */ -export type ImageCategory = 'general' | 'control' | 'mask' | 'other'; diff --git a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts deleted file mode 100644 index 47bfd4110f..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageChannelInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Gets a channel from an image. - */ -export type ImageChannelInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_chan'; - /** - * The image to get the channel from - */ - image?: ImageField; - /** - * The channel to get - */ - channel?: 'A' | 'R' | 'G' | 'B'; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts deleted file mode 100644 index 4bd59d03b0..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageConvertInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Converts an image to a different mode. - */ -export type ImageConvertInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_conv'; - /** - * The image to convert - */ - image?: ImageField; - /** - * The mode to convert to - */ - mode?: 'L' | 'RGB' | 'RGBA' | 'CMYK' | 'YCbCr' | 'LAB' | 'HSV' | 'I' | 'F'; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts deleted file mode 100644 index 5207ebbf6d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageCropInvocation.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Crops an image to a specified box. The box can be outside of the image. - */ -export type ImageCropInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_crop'; - /** - * The image to crop - */ - image?: ImageField; - /** - * The left x coordinate of the crop rectangle - */ - 'x'?: number; - /** - * The top y coordinate of the crop rectangle - */ - 'y'?: number; - /** - * The width of the crop rectangle - */ - width?: number; - /** - * The height of the crop rectangle - */ - height?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageDTO.ts deleted file mode 100644 index bc2f19f1b5..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageDTO.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageCategory } from './ImageCategory'; -import type { ImageMetadata } from './ImageMetadata'; -import type { ImageType } from './ImageType'; - -/** - * Deserialized image record, enriched for the frontend with URLs. - */ -export type ImageDTO = { - /** - * The unique name of the image. - */ - image_name: string; - /** - * The type of the image. - */ - image_type: ImageType; - /** - * The URL of the image. - */ - image_url: string; - /** - * The URL of the image's thumbnail. - */ - thumbnail_url: string; - /** - * The category of the image. - */ - image_category: ImageCategory; - /** - * The width of the image in px. - */ - width: number; - /** - * The height of the image in px. - */ - height: number; - /** - * The created timestamp of the image. - */ - created_at: string; - /** - * The updated timestamp of the image. - */ - updated_at: string; - /** - * The deleted timestamp of the image. - */ - deleted_at?: string; - /** - * Whether this is an intermediate image. - */ - is_intermediate: boolean; - /** - * The session ID that generated this image, if it is a generated image. - */ - session_id?: string; - /** - * The node ID that generated this image, if it is a generated image. - */ - node_id?: string; - /** - * A limited subset of the image's generation metadata. Retrieve the image's session for full metadata. - */ - metadata?: ImageMetadata; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageField.ts b/invokeai/frontend/web/src/services/api/models/ImageField.ts deleted file mode 100644 index fa22ae8007..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageField.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageType } from './ImageType'; - -/** - * An image field used for passing image objects between invocations - */ -export type ImageField = { - /** - * The type of the image - */ - image_type: ImageType; - /** - * The name of the image - */ - image_name: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts deleted file mode 100644 index 0347d4dc38..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageInverseLerpInvocation.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Inverse linear interpolation of all pixels of an image - */ -export type ImageInverseLerpInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_ilerp'; - /** - * The image to lerp - */ - image?: ImageField; - /** - * The minimum input value - */ - min?: number; - /** - * The maximum input value - */ - max?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts deleted file mode 100644 index 388c86061c..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageLerpInvocation.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Linear interpolation of all pixels of an image - */ -export type ImageLerpInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_lerp'; - /** - * The image to lerp - */ - image?: ImageField; - /** - * The minimum output value - */ - min?: number; - /** - * The maximum output value - */ - max?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts b/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts deleted file mode 100644 index 76c0155e97..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageMetadata.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Core generation metadata for an image/tensor generated in InvokeAI. - * - * Also includes any metadata from the image's PNG tEXt chunks. - * - * Generated by traversing the execution graph, collecting the parameters of the nearest ancestors - * of a given node. - * - * Full metadata may be accessed by querying for the session in the `graph_executions` table. - */ -export type ImageMetadata = { - /** - * The type of the ancestor node of the image output node. - */ - type?: string; - /** - * The positive conditioning. - */ - positive_conditioning?: string; - /** - * The negative conditioning. - */ - negative_conditioning?: string; - /** - * Width of the image/latents in pixels. - */ - width?: number; - /** - * Height of the image/latents in pixels. - */ - height?: number; - /** - * The seed used for noise generation. - */ - seed?: number; - /** - * The classifier-free guidance scale. - */ - cfg_scale?: number; - /** - * The number of steps used for inference. - */ - steps?: number; - /** - * The scheduler used for inference. - */ - scheduler?: string; - /** - * The model used for inference. - */ - model?: string; - /** - * The strength used for image-to-image/latents-to-latents. - */ - strength?: number; - /** - * The ID of the initial latents. - */ - latents?: string; - /** - * The VAE used for decoding. - */ - vae?: string; - /** - * The UNet used dor inference. - */ - unet?: string; - /** - * The CLIP Encoder used for conditioning. - */ - clip?: string; - /** - * Uploaded image metadata, extracted from the PNG tEXt chunk. - */ - extra?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts deleted file mode 100644 index 751ee49158..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageMultiplyInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Multiplies two images together using `PIL.ImageChops.multiply()`. - */ -export type ImageMultiplyInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_mul'; - /** - * The first image to multiply - */ - image1?: ImageField; - /** - * The second image to multiply - */ - image2?: ImageField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts b/invokeai/frontend/web/src/services/api/models/ImageOutput.ts deleted file mode 100644 index d7db0c11de..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageOutput.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Base class for invocations that output an image - */ -export type ImageOutput = { - type: 'image_output'; - /** - * The output image - */ - image: ImageField; - /** - * The width of the image in pixels - */ - width: number; - /** - * The height of the image in pixels - */ - height: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts deleted file mode 100644 index c883b9a5d8..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImagePasteInvocation.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Pastes an image into another image. - */ -export type ImagePasteInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img_paste'; - /** - * The base image - */ - base_image?: ImageField; - /** - * The image to paste - */ - image?: ImageField; - /** - * The mask to use when pasting - */ - mask?: ImageField; - /** - * The left x coordinate at which to paste the image - */ - 'x'?: number; - /** - * The top y coordinate at which to paste the image - */ - 'y'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts deleted file mode 100644 index 90639a0569..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageProcessorInvocation.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Base class for invocations that preprocess images for ControlNet - */ -export type ImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'image_processor'; - /** - * image to process - */ - image?: ImageField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts b/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts deleted file mode 100644 index 51f0ee2079..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageRecordChanges.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageCategory } from './ImageCategory'; - -/** - * A set of changes to apply to an image record. - * - * Only limited changes are valid: - * - `image_category`: change the category of an image - * - `session_id`: change the session associated with an image - */ -export type ImageRecordChanges = { - /** - * The image's new category. - */ - image_category?: ImageCategory; - /** - * The image's new session ID. - */ - session_id?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts deleted file mode 100644 index e63ec93ada..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageToImageInvocation.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Generates an image using img2img. - */ -export type ImageToImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'img2img'; - /** - * The prompt to generate an image from - */ - prompt?: string; - /** - * The seed to use (omit for random) - */ - seed?: number; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The width of the resulting image - */ - width?: number; - /** - * The height of the resulting image - */ - height?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; - /** - * The input image - */ - image?: ImageField; - /** - * The strength of the original image - */ - strength?: number; - /** - * Whether or not the result should be fit to the aspect ratio of the input image - */ - fit?: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts deleted file mode 100644 index 5569c2fa86..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageToLatentsInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Encodes an image into latents. - */ -export type ImageToLatentsInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'i2l'; - /** - * The image to encode - */ - image?: ImageField; - /** - * The model to use - */ - model?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ImageType.ts b/invokeai/frontend/web/src/services/api/models/ImageType.ts deleted file mode 100644 index dfc10bf455..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageType.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * The type of an image. - */ -export type ImageType = 'results' | 'uploads'; diff --git a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts b/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts deleted file mode 100644 index af80519ef2..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ImageUrlsDTO.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageType } from './ImageType'; - -/** - * The URLs for an image and its thumbnail. - */ -export type ImageUrlsDTO = { - /** - * The unique name of the image. - */ - image_name: string; - /** - * The type of the image. - */ - image_type: ImageType; - /** - * The URL of the image. - */ - image_url: string; - /** - * The URL of the image's thumbnail. - */ - thumbnail_url: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts deleted file mode 100644 index 3e637b299c..0000000000 --- a/invokeai/frontend/web/src/services/api/models/InfillColorInvocation.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ColorField } from './ColorField'; -import type { ImageField } from './ImageField'; - -/** - * Infills transparent areas of an image with a solid color - */ -export type InfillColorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'infill_rgba'; - /** - * The image to infill - */ - image?: ImageField; - /** - * The color to use to infill - */ - color?: ColorField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts deleted file mode 100644 index 325bfe2080..0000000000 --- a/invokeai/frontend/web/src/services/api/models/InfillPatchMatchInvocation.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Infills transparent areas of an image using the PatchMatch algorithm - */ -export type InfillPatchMatchInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'infill_patchmatch'; - /** - * The image to infill - */ - image?: ImageField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts b/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts deleted file mode 100644 index dfb1cbc61d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/InfillTileInvocation.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Infills transparent areas of an image with tiles of the image - */ -export type InfillTileInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'infill_tile'; - /** - * The image to infill - */ - image?: ImageField; - /** - * The tile size (px) - */ - tile_size?: number; - /** - * The seed to use for tile generation (omit for random) - */ - seed?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts b/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts deleted file mode 100644 index b8ed268ef9..0000000000 --- a/invokeai/frontend/web/src/services/api/models/InpaintInvocation.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ColorField } from './ColorField'; -import type { ImageField } from './ImageField'; - -/** - * Generates an image using inpaint. - */ -export type InpaintInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'inpaint'; - /** - * The prompt to generate an image from - */ - prompt?: string; - /** - * The seed to use (omit for random) - */ - seed?: number; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The width of the resulting image - */ - width?: number; - /** - * The height of the resulting image - */ - height?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; - /** - * The input image - */ - image?: ImageField; - /** - * The strength of the original image - */ - strength?: number; - /** - * Whether or not the result should be fit to the aspect ratio of the input image - */ - fit?: boolean; - /** - * The mask - */ - mask?: ImageField; - /** - * The seam inpaint size (px) - */ - seam_size?: number; - /** - * The seam inpaint blur radius (px) - */ - seam_blur?: number; - /** - * The seam inpaint strength - */ - seam_strength?: number; - /** - * The number of steps to use for seam inpaint - */ - seam_steps?: number; - /** - * The tile infill method size (px) - */ - tile_size?: number; - /** - * The method used to infill empty regions (px) - */ - infill_method?: 'patchmatch' | 'tile' | 'solid'; - /** - * The width of the inpaint region (px) - */ - inpaint_width?: number; - /** - * The height of the inpaint region (px) - */ - inpaint_height?: number; - /** - * The solid infill method color - */ - inpaint_fill?: ColorField; - /** - * The amount by which to replace masked areas with latent noise - */ - inpaint_replace?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts b/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts deleted file mode 100644 index 93a115f980..0000000000 --- a/invokeai/frontend/web/src/services/api/models/IntCollectionOutput.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * A collection of integers - */ -export type IntCollectionOutput = { - type?: 'int_collection'; - /** - * The int collection - */ - collection?: Array; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/IntOutput.ts b/invokeai/frontend/web/src/services/api/models/IntOutput.ts deleted file mode 100644 index eeea6c68b4..0000000000 --- a/invokeai/frontend/web/src/services/api/models/IntOutput.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * An integer output - */ -export type IntOutput = { - type?: 'int_output'; - /** - * The output integer - */ - 'a'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts deleted file mode 100644 index 15bf92dfea..0000000000 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Iterates over a list of items - */ -export type IterateInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'iterate'; - /** - * The list of items to iterate over - */ - collection?: Array; - /** - * The index, will be provided on executed iterators - */ - index?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts b/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts deleted file mode 100644 index ce8d9f8c4b..0000000000 --- a/invokeai/frontend/web/src/services/api/models/IterateInvocationOutput.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Used to connect iteration outputs. Will be expanded to a specific output. - */ -export type IterateInvocationOutput = { - type: 'iterate_output'; - /** - * The item being iterated over - */ - item: any; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsField.ts b/invokeai/frontend/web/src/services/api/models/LatentsField.ts deleted file mode 100644 index bc6a525f7c..0000000000 --- a/invokeai/frontend/web/src/services/api/models/LatentsField.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * A latents field used for passing latents between invocations - */ -export type LatentsField = { - /** - * The name of the latents - */ - latents_name: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts b/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts deleted file mode 100644 index 3e9c2f60e4..0000000000 --- a/invokeai/frontend/web/src/services/api/models/LatentsOutput.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { LatentsField } from './LatentsField'; - -/** - * Base class for invocations that output latents - */ -export type LatentsOutput = { - type?: 'latents_output'; - /** - * The output latents - */ - latents?: LatentsField; - /** - * The width of the latents in pixels - */ - width: number; - /** - * The height of the latents in pixels - */ - height: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts deleted file mode 100644 index fcaa37d7e8..0000000000 --- a/invokeai/frontend/web/src/services/api/models/LatentsToImageInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { LatentsField } from './LatentsField'; - -/** - * Generates an image from latents. - */ -export type LatentsToImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'l2i'; - /** - * The latents to generate an image from - */ - latents?: LatentsField; - /** - * The model to use - */ - model?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts deleted file mode 100644 index 6436557f64..0000000000 --- a/invokeai/frontend/web/src/services/api/models/LatentsToLatentsInvocation.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ConditioningField } from './ConditioningField'; -import type { LatentsField } from './LatentsField'; - -/** - * Generates latents using latents as base image. - */ -export type LatentsToLatentsInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'l2l'; - /** - * Positive conditioning for generation - */ - positive_conditioning?: ConditioningField; - /** - * Negative conditioning for generation - */ - negative_conditioning?: ConditioningField; - /** - * The noise to use - */ - noise?: LatentsField; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * The latents to use as a base image - */ - latents?: LatentsField; - /** - * The strength of the latents to use - */ - strength?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts deleted file mode 100644 index a9bdab56ec..0000000000 --- a/invokeai/frontend/web/src/services/api/models/LineartAnimeImageProcessorInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies line art anime processing to image - */ -export type LineartAnimeImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'lineart_anime_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * pixel resolution for edge detection - */ - detect_resolution?: number; - /** - * pixel resolution for output image - */ - image_resolution?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts deleted file mode 100644 index 1aa931525f..0000000000 --- a/invokeai/frontend/web/src/services/api/models/LineartImageProcessorInvocation.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies line art processing to image - */ -export type LineartImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'lineart_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * pixel resolution for edge detection - */ - detect_resolution?: number; - /** - * pixel resolution for output image - */ - image_resolution?: number; - /** - * whether to use coarse mode - */ - coarse?: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts deleted file mode 100644 index f20d983f9b..0000000000 --- a/invokeai/frontend/web/src/services/api/models/LoadImageInvocation.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Load an image and provide it as output. - */ -export type LoadImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'load_image'; - /** - * The image to load - */ - image?: ImageField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts b/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts deleted file mode 100644 index e3693f6d98..0000000000 --- a/invokeai/frontend/web/src/services/api/models/MaskFromAlphaInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Extracts the alpha channel of an image as a mask. - */ -export type MaskFromAlphaInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'tomask'; - /** - * The image to create the mask from - */ - image?: ImageField; - /** - * Whether or not to invert the mask - */ - invert?: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts b/invokeai/frontend/web/src/services/api/models/MaskOutput.ts deleted file mode 100644 index d4594fe6e9..0000000000 --- a/invokeai/frontend/web/src/services/api/models/MaskOutput.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Base class for invocations that output a mask - */ -export type MaskOutput = { - type: 'mask'; - /** - * The output mask - */ - mask: ImageField; - /** - * The width of the mask in pixels - */ - width?: number; - /** - * The height of the mask in pixels - */ - height?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts deleted file mode 100644 index 71283b0614..0000000000 --- a/invokeai/frontend/web/src/services/api/models/MidasDepthImageProcessorInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies Midas depth processing to image - */ -export type MidasDepthImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'midas_depth_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * Midas parameter a = amult * PI - */ - a_mult?: number; - /** - * Midas parameter bg_th - */ - bg_th?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts deleted file mode 100644 index 85a2ad15cc..0000000000 --- a/invokeai/frontend/web/src/services/api/models/MlsdImageProcessorInvocation.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies MLSD processing to image - */ -export type MlsdImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'mlsd_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * pixel resolution for edge detection - */ - detect_resolution?: number; - /** - * pixel resolution for output image - */ - image_resolution?: number; - /** - * MLSD parameter thr_v - */ - thr_v?: number; - /** - * MLSD parameter thr_d - */ - thr_d?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ModelsList.ts b/invokeai/frontend/web/src/services/api/models/ModelsList.ts deleted file mode 100644 index 7a7449542d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ModelsList.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { CkptModelInfo } from './CkptModelInfo'; -import type { DiffusersModelInfo } from './DiffusersModelInfo'; - -export type ModelsList = { - models: Record; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts b/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts deleted file mode 100644 index 9fd716f33d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/MultiplyInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Multiplies two numbers - */ -export type MultiplyInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'mul'; - /** - * The first number - */ - 'a'?: number; - /** - * The second number - */ - 'b'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts b/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts deleted file mode 100644 index 239a24bfe5..0000000000 --- a/invokeai/frontend/web/src/services/api/models/NoiseInvocation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Generates latent noise. - */ -export type NoiseInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'noise'; - /** - * The seed to use - */ - seed?: number; - /** - * The width of the resulting noise - */ - width?: number; - /** - * The height of the resulting noise - */ - height?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts b/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts deleted file mode 100644 index f1832d7aa2..0000000000 --- a/invokeai/frontend/web/src/services/api/models/NoiseOutput.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { LatentsField } from './LatentsField'; - -/** - * Invocation noise output - */ -export type NoiseOutput = { - type?: 'noise_output'; - /** - * The output noise - */ - noise?: LatentsField; - /** - * The width of the noise in pixels - */ - width: number; - /** - * The height of the noise in pixels - */ - height: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts deleted file mode 100644 index 519ea7a89d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/NormalbaeImageProcessorInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies NormalBae processing to image - */ -export type NormalbaeImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'normalbae_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * pixel resolution for edge detection - */ - detect_resolution?: number; - /** - * pixel resolution for output image - */ - image_resolution?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts deleted file mode 100644 index 44947df15b..0000000000 --- a/invokeai/frontend/web/src/services/api/models/OpenposeImageProcessorInvocation.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies Openpose processing to image - */ -export type OpenposeImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'openpose_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * whether to use hands and face mode - */ - hand_and_face?: boolean; - /** - * pixel resolution for edge detection - */ - detect_resolution?: number; - /** - * pixel resolution for output image - */ - image_resolution?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts b/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts deleted file mode 100644 index dd9f50cd4a..0000000000 --- a/invokeai/frontend/web/src/services/api/models/PaginatedResults_GraphExecutionState_.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { GraphExecutionState } from './GraphExecutionState'; - -/** - * Paginated results - */ -export type PaginatedResults_GraphExecutionState_ = { - /** - * Items - */ - items: Array; - /** - * Current Page - */ - page: number; - /** - * Total number of pages - */ - pages: number; - /** - * Number of items per page - */ - per_page: number; - /** - * Total number of items in result - */ - total: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/PaginatedResults_ImageDTO_.ts b/invokeai/frontend/web/src/services/api/models/PaginatedResults_ImageDTO_.ts deleted file mode 100644 index 5d2bdae5ab..0000000000 --- a/invokeai/frontend/web/src/services/api/models/PaginatedResults_ImageDTO_.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageDTO } from './ImageDTO'; - -/** - * Paginated results - */ -export type PaginatedResults_ImageDTO_ = { - /** - * Items - */ - items: Array; - /** - * Current Page - */ - page: number; - /** - * Total number of pages - */ - pages: number; - /** - * Number of items per page - */ - per_page: number; - /** - * Total number of items in result - */ - total: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts deleted file mode 100644 index 7a45d0a0ac..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ParamIntInvocation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * An integer parameter - */ -export type ParamIntInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'param_int'; - /** - * The integer value - */ - 'a'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts deleted file mode 100644 index 59076cb2e1..0000000000 --- a/invokeai/frontend/web/src/services/api/models/PidiImageProcessorInvocation.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Applies PIDI processing to image - */ -export type PidiImageProcessorInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - type?: 'pidi_image_processor'; - /** - * image to process - */ - image?: ImageField; - /** - * pixel resolution for edge detection - */ - detect_resolution?: number; - /** - * pixel resolution for output image - */ - image_resolution?: number; - /** - * whether to use safe mode - */ - safe?: boolean; - /** - * whether to use scribble mode - */ - scribble?: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts b/invokeai/frontend/web/src/services/api/models/PromptOutput.ts deleted file mode 100644 index 5bca3f3037..0000000000 --- a/invokeai/frontend/web/src/services/api/models/PromptOutput.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Base class for invocations that output a prompt - */ -export type PromptOutput = { - type: 'prompt'; - /** - * The output prompt - */ - prompt: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts deleted file mode 100644 index a2f7c2f02a..0000000000 --- a/invokeai/frontend/web/src/services/api/models/RandomIntInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Outputs a single random integer. - */ -export type RandomIntInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'rand_int'; - /** - * The inclusive low value - */ - low?: number; - /** - * The exclusive high value - */ - high?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts deleted file mode 100644 index 925511578d..0000000000 --- a/invokeai/frontend/web/src/services/api/models/RandomRangeInvocation.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Creates a collection of random numbers - */ -export type RandomRangeInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'random_range'; - /** - * The inclusive low value - */ - low?: number; - /** - * The exclusive high value - */ - high?: number; - /** - * The number of values to generate - */ - size?: number; - /** - * The seed for the RNG (omit for random) - */ - seed?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts deleted file mode 100644 index 3681602a95..0000000000 --- a/invokeai/frontend/web/src/services/api/models/RangeInvocation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Creates a range of numbers from start to stop with step - */ -export type RangeInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'range'; - /** - * The start of the range - */ - start?: number; - /** - * The stop of the range - */ - stop?: number; - /** - * The step of the range - */ - step?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts b/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts deleted file mode 100644 index 7dfac68d39..0000000000 --- a/invokeai/frontend/web/src/services/api/models/RangeOfSizeInvocation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Creates a range from start to start + size with step - */ -export type RangeOfSizeInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'range_of_size'; - /** - * The start of the range - */ - start?: number; - /** - * The number of values - */ - size?: number; - /** - * The step of the range - */ - step?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts deleted file mode 100644 index 9a7b6c61e4..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ResizeLatentsInvocation.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { LatentsField } from './LatentsField'; - -/** - * Resizes latents to explicit width/height (in pixels). Provided dimensions are floor-divided by 8. - */ -export type ResizeLatentsInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'lresize'; - /** - * The latents to resize - */ - latents?: LatentsField; - /** - * The width to resize to (px) - */ - width: number; - /** - * The height to resize to (px) - */ - height: number; - /** - * The interpolation mode - */ - mode?: 'nearest' | 'linear' | 'bilinear' | 'bicubic' | 'trilinear' | 'area' | 'nearest-exact'; - /** - * Whether or not to antialias (applied in bilinear and bicubic modes only) - */ - antialias?: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts b/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts deleted file mode 100644 index 0bacb5d805..0000000000 --- a/invokeai/frontend/web/src/services/api/models/RestoreFaceInvocation.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Restores faces in an image. - */ -export type RestoreFaceInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'restore_face'; - /** - * The input image - */ - image?: ImageField; - /** - * The strength of the restoration - */ - strength?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts deleted file mode 100644 index 506b21e540..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ScaleLatentsInvocation.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { LatentsField } from './LatentsField'; - -/** - * Scales latents by a given factor. - */ -export type ScaleLatentsInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'lscale'; - /** - * The latents to scale - */ - latents?: LatentsField; - /** - * The factor by which to scale the latents - */ - scale_factor: number; - /** - * The interpolation mode - */ - mode?: 'nearest' | 'linear' | 'bilinear' | 'bicubic' | 'trilinear' | 'area' | 'nearest-exact'; - /** - * Whether or not to antialias (applied in bilinear and bicubic modes only) - */ - antialias?: boolean; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts deleted file mode 100644 index 1b73055584..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ShowImageInvocation.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Displays a provided image, and passes it forward in the pipeline. - */ -export type ShowImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'show_image'; - /** - * The image to show - */ - image?: ImageField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts b/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts deleted file mode 100644 index 23334bd891..0000000000 --- a/invokeai/frontend/web/src/services/api/models/SubtractInvocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Subtracts two numbers - */ -export type SubtractInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'sub'; - /** - * The first number - */ - 'a'?: number; - /** - * The second number - */ - 'b'?: number; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts deleted file mode 100644 index 7128ea8440..0000000000 --- a/invokeai/frontend/web/src/services/api/models/TextToImageInvocation.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Generates an image using text2img. - */ -export type TextToImageInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'txt2img'; - /** - * The prompt to generate an image from - */ - prompt?: string; - /** - * The seed to use (omit for random) - */ - seed?: number; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The width of the resulting image - */ - width?: number; - /** - * The height of the resulting image - */ - height?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; - /** - * Whether or not to produce progress images during generation - */ - progress_images?: boolean; - /** - * The control model to use - */ - control_model?: string; - /** - * The processed control image - */ - control_image?: ImageField; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts b/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts deleted file mode 100644 index 33eedc0f02..0000000000 --- a/invokeai/frontend/web/src/services/api/models/TextToLatentsInvocation.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ConditioningField } from './ConditioningField'; -import type { LatentsField } from './LatentsField'; - -/** - * Generates latents from conditionings. - */ -export type TextToLatentsInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 't2l'; - /** - * Positive conditioning for generation - */ - positive_conditioning?: ConditioningField; - /** - * Negative conditioning for generation - */ - negative_conditioning?: ConditioningField; - /** - * The noise to use - */ - noise?: LatentsField; - /** - * The number of steps to use to generate the image - */ - steps?: number; - /** - * The Classifier-Free Guidance, higher values may result in a result closer to the prompt - */ - cfg_scale?: number; - /** - * The scheduler to use - */ - scheduler?: 'ddim' | 'ddpm' | 'deis' | 'lms' | 'pndm' | 'heun' | 'heun_k' | 'euler' | 'euler_k' | 'euler_a' | 'kdpm_2' | 'kdpm_2_a' | 'dpmpp_2s' | 'dpmpp_2m' | 'dpmpp_2m_k' | 'unipc'; - /** - * The model to use (currently ignored) - */ - model?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts b/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts deleted file mode 100644 index d0aca63964..0000000000 --- a/invokeai/frontend/web/src/services/api/models/UpscaleInvocation.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { ImageField } from './ImageField'; - -/** - * Upscales an image. - */ -export type UpscaleInvocation = { - /** - * The id of this node. Must be unique among all nodes. - */ - id: string; - /** - * Whether or not this node is an intermediate node. - */ - is_intermediate?: boolean; - type?: 'upscale'; - /** - * The input image - */ - image?: ImageField; - /** - * The strength - */ - strength?: number; - /** - * The upscale level - */ - level?: 2 | 4; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts b/invokeai/frontend/web/src/services/api/models/VaeRepo.ts deleted file mode 100644 index 0e233626c6..0000000000 --- a/invokeai/frontend/web/src/services/api/models/VaeRepo.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type VaeRepo = { - /** - * The repo ID to use for this VAE - */ - repo_id: string; - /** - * The path to the VAE - */ - path?: string; - /** - * The subfolder to use for this VAE - */ - subfolder?: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/models/ValidationError.ts b/invokeai/frontend/web/src/services/api/models/ValidationError.ts deleted file mode 100644 index 14e1fdecd0..0000000000 --- a/invokeai/frontend/web/src/services/api/models/ValidationError.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type ValidationError = { - loc: Array<(string | number)>; - msg: string; - type: string; -}; - diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts new file mode 100644 index 0000000000..767fe7b2b3 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -0,0 +1,5083 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + + +export type paths = { + "/api/v1/sessions/": { + /** + * List Sessions + * @description Gets a list of sessions, optionally searching + */ + get: operations["list_sessions"]; + /** + * Create Session + * @description Creates a new session, optionally initializing it with an invocation graph + */ + post: operations["create_session"]; + }; + "/api/v1/sessions/{session_id}": { + /** + * Get Session + * @description Gets a session + */ + get: operations["get_session"]; + }; + "/api/v1/sessions/{session_id}/nodes": { + /** + * Add Node + * @description Adds a node to the graph + */ + post: operations["add_node"]; + }; + "/api/v1/sessions/{session_id}/nodes/{node_path}": { + /** + * Update Node + * @description Updates a node in the graph and removes all linked edges + */ + put: operations["update_node"]; + /** + * Delete Node + * @description Deletes a node in the graph and removes all linked edges + */ + delete: operations["delete_node"]; + }; + "/api/v1/sessions/{session_id}/edges": { + /** + * Add Edge + * @description Adds an edge to the graph + */ + post: operations["add_edge"]; + }; + "/api/v1/sessions/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}": { + /** + * Delete Edge + * @description Deletes an edge from the graph + */ + delete: operations["delete_edge"]; + }; + "/api/v1/sessions/{session_id}/invoke": { + /** + * Invoke Session + * @description Invokes a session + */ + put: operations["invoke_session"]; + /** + * Cancel Session Invoke + * @description Invokes a session + */ + delete: operations["cancel_session_invoke"]; + }; + "/api/v1/models/": { + /** + * List Models + * @description Gets a list of models + */ + get: operations["list_models"]; + /** + * Update Model + * @description Add Model + */ + post: operations["update_model"]; + }; + "/api/v1/models/{model_name}": { + /** + * Delete Model + * @description Delete Model + */ + delete: operations["del_model"]; + }; + "/api/v1/images/": { + /** + * List Images With Metadata + * @description Gets a list of images + */ + get: operations["list_images_with_metadata"]; + /** + * Upload Image + * @description Uploads an image + */ + post: operations["upload_image"]; + }; + "/api/v1/images/{image_name}": { + /** + * Get Image Full + * @description Gets a full-resolution image file + */ + get: operations["get_image_full"]; + /** + * Delete Image + * @description Deletes an image + */ + delete: operations["delete_image"]; + /** + * Update Image + * @description Updates an image + */ + patch: operations["update_image"]; + }; + "/api/v1/images/{image_name}/metadata": { + /** + * Get Image Metadata + * @description Gets an image's metadata + */ + get: operations["get_image_metadata"]; + }; + "/api/v1/images/{image_name}/thumbnail": { + /** + * Get Image Thumbnail + * @description Gets a thumbnail image file + */ + get: operations["get_image_thumbnail"]; + }; + "/api/v1/images/{image_name}/urls": { + /** + * Get Image Urls + * @description Gets an image and thumbnail URL + */ + get: operations["get_image_urls"]; + }; + "/api/v1/boards/": { + /** + * List Boards + * @description Gets a list of boards + */ + get: operations["list_boards"]; + /** + * Create Board + * @description Creates a board + */ + post: operations["create_board"]; + }; + "/api/v1/boards/{board_id}": { + /** + * Get Board + * @description Gets a board + */ + get: operations["get_board"]; + /** + * Delete Board + * @description Deletes a board + */ + delete: operations["delete_board"]; + /** + * Update Board + * @description Updates a board + */ + patch: operations["update_board"]; + }; + "/api/v1/board_images/": { + /** + * Create Board Image + * @description Creates a board_image + */ + post: operations["create_board_image"]; + /** + * Remove Board Image + * @description Deletes a board_image + */ + delete: operations["remove_board_image"]; + }; + "/api/v1/board_images/{board_id}": { + /** + * List Board Images + * @description Gets a list of images for a board + */ + get: operations["list_board_images"]; + }; +}; + +export type webhooks = Record; + +export type components = { + schemas: { + /** + * AddInvocation + * @description Adds two numbers + */ + AddInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default add + * @enum {string} + */ + type?: "add"; + /** + * A + * @description The first number + * @default 0 + */ + a?: number; + /** + * B + * @description The second number + * @default 0 + */ + b?: number; + }; + /** + * BaseModelType + * @description An enumeration. + * @enum {string} + */ + BaseModelType: "sd-1" | "sd-2"; + /** BoardChanges */ + BoardChanges: { + /** + * Board Name + * @description The board's new name. + */ + board_name?: string; + /** + * Cover Image Name + * @description The name of the board's new cover image. + */ + cover_image_name?: string; + }; + /** + * BoardDTO + * @description Deserialized board record with cover image URL and image count. + */ + BoardDTO: { + /** + * Board Id + * @description The unique ID of the board. + */ + board_id: string; + /** + * Board Name + * @description The name of the board. + */ + board_name: string; + /** + * Created At + * @description The created timestamp of the board. + */ + created_at: string; + /** + * Updated At + * @description The updated timestamp of the board. + */ + updated_at: string; + /** + * Deleted At + * @description The deleted timestamp of the board. + */ + deleted_at?: string; + /** + * Cover Image Name + * @description The name of the board's cover image. + */ + cover_image_name?: string; + /** + * Image Count + * @description The number of images in the board. + */ + image_count: number; + }; + /** Body_create_board_image */ + Body_create_board_image: { + /** + * Board Id + * @description The id of the board to add to + */ + board_id: string; + /** + * Image Name + * @description The name of the image to add + */ + image_name: string; + }; + /** Body_remove_board_image */ + Body_remove_board_image: { + /** + * Board Id + * @description The id of the board + */ + board_id: string; + /** + * Image Name + * @description The name of the image to remove + */ + image_name: string; + }; + /** Body_upload_image */ + Body_upload_image: { + /** + * File + * Format: binary + */ + file: string; + }; + /** + * CannyImageProcessorInvocation + * @description Canny edge detection for ControlNet + */ + CannyImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default canny_image_processor + * @enum {string} + */ + type?: "canny_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Low Threshold + * @description The low threshold of the Canny pixel gradient (0-255) + * @default 100 + */ + low_threshold?: number; + /** + * High Threshold + * @description The high threshold of the Canny pixel gradient (0-255) + * @default 200 + */ + high_threshold?: number; + }; + /** CkptModelInfo */ + CkptModelInfo: { + /** + * Description + * @description A description of the model + */ + description?: string; + /** + * Model Name + * @description The name of the model + */ + model_name: string; + /** + * Model Type + * @description The type of the model + */ + model_type: string; + /** + * Format + * @default ckpt + * @enum {string} + */ + format?: "ckpt"; + /** + * Config + * @description The path to the model config + */ + config: string; + /** + * Weights + * @description The path to the model weights + */ + weights: string; + /** + * Vae + * @description The path to the model VAE + */ + vae: string; + /** + * Width + * @description The width of the model + */ + width?: number; + /** + * Height + * @description The height of the model + */ + height?: number; + }; + /** ClipField */ + ClipField: { + /** + * Tokenizer + * @description Info to load tokenizer submodel + */ + tokenizer: components["schemas"]["ModelInfo"]; + /** + * Text Encoder + * @description Info to load text_encoder submodel + */ + text_encoder: components["schemas"]["ModelInfo"]; + /** + * Loras + * @description Loras to apply on model loading + */ + loras: (components["schemas"]["LoraInfo"])[]; + }; + /** + * CollectInvocation + * @description Collects values into a collection + */ + CollectInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default collect + * @enum {string} + */ + type?: "collect"; + /** + * Item + * @description The item to collect (all inputs must be of the same type) + */ + item?: unknown; + /** + * Collection + * @description The collection, will be provided on execution + */ + collection?: (unknown)[]; + }; + /** + * CollectInvocationOutput + * @description Base class for all invocation outputs + */ + CollectInvocationOutput: { + /** + * Type + * @default collect_output + * @enum {string} + */ + type: "collect_output"; + /** + * Collection + * @description The collection of input items + */ + collection: (unknown)[]; + }; + /** ColorField */ + ColorField: { + /** + * R + * @description The red component + */ + r: number; + /** + * G + * @description The green component + */ + g: number; + /** + * B + * @description The blue component + */ + b: number; + /** + * A + * @description The alpha component + */ + a: number; + }; + /** + * CompelInvocation + * @description Parse prompt using compel package to conditioning. + */ + CompelInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default compel + * @enum {string} + */ + type?: "compel"; + /** + * Prompt + * @description Prompt + * @default + */ + prompt?: string; + /** + * Clip + * @description Clip to use + */ + clip?: components["schemas"]["ClipField"]; + }; + /** + * CompelOutput + * @description Compel parser output + */ + CompelOutput: { + /** + * Type + * @default compel_output + * @enum {string} + */ + type?: "compel_output"; + /** + * Conditioning + * @description Conditioning + */ + conditioning?: components["schemas"]["ConditioningField"]; + }; + /** ConditioningField */ + ConditioningField: { + /** + * Conditioning Name + * @description The name of conditioning data + */ + conditioning_name: string; + }; + /** + * ContentShuffleImageProcessorInvocation + * @description Applies content shuffle processing to image + */ + ContentShuffleImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default content_shuffle_image_processor + * @enum {string} + */ + type?: "content_shuffle_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + /** + * H + * @description Content shuffle `h` parameter + * @default 512 + */ + h?: number; + /** + * W + * @description Content shuffle `w` parameter + * @default 512 + */ + w?: number; + /** + * F + * @description Content shuffle `f` parameter + * @default 256 + */ + f?: number; + }; + /** ControlField */ + ControlField: { + /** + * Image + * @description The control image + */ + image: components["schemas"]["ImageField"]; + /** + * Control Model + * @description The ControlNet model to use + */ + control_model: string; + /** + * Control Weight + * @description The weight given to the ControlNet + * @default 1 + */ + control_weight: number | (number)[]; + /** + * Begin Step Percent + * @description When the ControlNet is first applied (% of total steps) + * @default 0 + */ + begin_step_percent: number; + /** + * End Step Percent + * @description When the ControlNet is last applied (% of total steps) + * @default 1 + */ + end_step_percent: number; + /** + * Control Mode + * @description The contorl mode to use + * @default balanced + * @enum {string} + */ + control_mode?: "balanced" | "more_prompt" | "more_control" | "unbalanced"; + }; + /** + * ControlNetInvocation + * @description Collects ControlNet info to pass to other nodes + */ + ControlNetInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default controlnet + * @enum {string} + */ + type?: "controlnet"; + /** + * Image + * @description The control image + */ + image?: components["schemas"]["ImageField"]; + /** + * Control Model + * @description control model used + * @default lllyasviel/sd-controlnet-canny + * @enum {string} + */ + control_model?: "lllyasviel/sd-controlnet-canny" | "lllyasviel/sd-controlnet-depth" | "lllyasviel/sd-controlnet-hed" | "lllyasviel/sd-controlnet-seg" | "lllyasviel/sd-controlnet-openpose" | "lllyasviel/sd-controlnet-scribble" | "lllyasviel/sd-controlnet-normal" | "lllyasviel/sd-controlnet-mlsd" | "lllyasviel/control_v11p_sd15_canny" | "lllyasviel/control_v11p_sd15_openpose" | "lllyasviel/control_v11p_sd15_seg" | "lllyasviel/control_v11f1p_sd15_depth" | "lllyasviel/control_v11p_sd15_normalbae" | "lllyasviel/control_v11p_sd15_scribble" | "lllyasviel/control_v11p_sd15_mlsd" | "lllyasviel/control_v11p_sd15_softedge" | "lllyasviel/control_v11p_sd15s2_lineart_anime" | "lllyasviel/control_v11p_sd15_lineart" | "lllyasviel/control_v11p_sd15_inpaint" | "lllyasviel/control_v11e_sd15_shuffle" | "lllyasviel/control_v11e_sd15_ip2p" | "lllyasviel/control_v11f1e_sd15_tile" | "thibaud/controlnet-sd21-openpose-diffusers" | "thibaud/controlnet-sd21-canny-diffusers" | "thibaud/controlnet-sd21-depth-diffusers" | "thibaud/controlnet-sd21-scribble-diffusers" | "thibaud/controlnet-sd21-hed-diffusers" | "thibaud/controlnet-sd21-zoedepth-diffusers" | "thibaud/controlnet-sd21-color-diffusers" | "thibaud/controlnet-sd21-openposev2-diffusers" | "thibaud/controlnet-sd21-lineart-diffusers" | "thibaud/controlnet-sd21-normalbae-diffusers" | "thibaud/controlnet-sd21-ade20k-diffusers" | "CrucibleAI/ControlNetMediaPipeFace,diffusion_sd15" | "CrucibleAI/ControlNetMediaPipeFace"; + /** + * Control Weight + * @description The weight given to the ControlNet + * @default 1 + */ + control_weight?: number | (number)[]; + /** + * Begin Step Percent + * @description When the ControlNet is first applied (% of total steps) + * @default 0 + */ + begin_step_percent?: number; + /** + * End Step Percent + * @description When the ControlNet is last applied (% of total steps) + * @default 1 + */ + end_step_percent?: number; + /** + * Control Mode + * @description The control mode used + * @default balanced + * @enum {string} + */ + control_mode?: "balanced" | "more_prompt" | "more_control" | "unbalanced"; + }; + /** ControlNetModelConfig */ + ControlNetModelConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "controlnet"; + /** Path */ + path: string; + /** Description */ + description?: string; + model_format: components["schemas"]["ControlNetModelFormat"]; + error?: components["schemas"]["ModelError"]; + }; + /** + * ControlNetModelFormat + * @description An enumeration. + * @enum {string} + */ + ControlNetModelFormat: "checkpoint" | "diffusers"; + /** + * ControlOutput + * @description node output for ControlNet info + */ + ControlOutput: { + /** + * Type + * @default control_output + * @enum {string} + */ + type?: "control_output"; + /** + * Control + * @description The control info + */ + control?: components["schemas"]["ControlField"]; + }; + /** CreateModelRequest */ + CreateModelRequest: { + /** + * Name + * @description The name of the model + */ + name: string; + /** + * Info + * @description The model info + */ + info: components["schemas"]["CkptModelInfo"] | components["schemas"]["DiffusersModelInfo"]; + }; + /** + * CvInpaintInvocation + * @description Simple inpaint using opencv. + */ + CvInpaintInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default cv_inpaint + * @enum {string} + */ + type?: "cv_inpaint"; + /** + * Image + * @description The image to inpaint + */ + image?: components["schemas"]["ImageField"]; + /** + * Mask + * @description The mask to use when inpainting + */ + mask?: components["schemas"]["ImageField"]; + }; + /** DiffusersModelInfo */ + DiffusersModelInfo: { + /** + * Description + * @description A description of the model + */ + description?: string; + /** + * Model Name + * @description The name of the model + */ + model_name: string; + /** + * Model Type + * @description The type of the model + */ + model_type: string; + /** + * Format + * @default folder + * @enum {string} + */ + format?: "folder"; + /** + * Vae + * @description The VAE repo to use for this model + */ + vae?: components["schemas"]["VaeRepo"]; + /** + * Repo Id + * @description The repo ID to use for this model + */ + repo_id?: string; + /** + * Path + * @description The path to the model + */ + path?: string; + }; + /** + * DivideInvocation + * @description Divides two numbers + */ + DivideInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default div + * @enum {string} + */ + type?: "div"; + /** + * A + * @description The first number + * @default 0 + */ + a?: number; + /** + * B + * @description The second number + * @default 0 + */ + b?: number; + }; + /** + * DynamicPromptInvocation + * @description Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator + */ + DynamicPromptInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default dynamic_prompt + * @enum {string} + */ + type?: "dynamic_prompt"; + /** + * Prompt + * @description The prompt to parse with dynamicprompts + */ + prompt: string; + /** + * Max Prompts + * @description The number of prompts to generate + * @default 1 + */ + max_prompts?: number; + /** + * Combinatorial + * @description Whether to use the combinatorial generator + * @default false + */ + combinatorial?: boolean; + }; + /** Edge */ + Edge: { + /** + * Source + * @description The connection for the edge's from node and field + */ + source: components["schemas"]["EdgeConnection"]; + /** + * Destination + * @description The connection for the edge's to node and field + */ + destination: components["schemas"]["EdgeConnection"]; + }; + /** EdgeConnection */ + EdgeConnection: { + /** + * Node Id + * @description The id of the node for this edge connection + */ + node_id: string; + /** + * Field + * @description The field for this connection + */ + field: string; + }; + /** + * FloatCollectionOutput + * @description A collection of floats + */ + FloatCollectionOutput: { + /** + * Type + * @default float_collection + * @enum {string} + */ + type?: "float_collection"; + /** + * Collection + * @description The float collection + * @default [] + */ + collection?: (number)[]; + }; + /** + * FloatLinearRangeInvocation + * @description Creates a range + */ + FloatLinearRangeInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default float_range + * @enum {string} + */ + type?: "float_range"; + /** + * Start + * @description The first value of the range + * @default 5 + */ + start?: number; + /** + * Stop + * @description The last value of the range + * @default 10 + */ + stop?: number; + /** + * Steps + * @description number of values to interpolate over (including start and stop) + * @default 30 + */ + steps?: number; + }; + /** + * FloatOutput + * @description A float output + */ + FloatOutput: { + /** + * Type + * @default float_output + * @enum {string} + */ + type?: "float_output"; + /** + * Param + * @description The output float + */ + param?: number; + }; + /** Graph */ + Graph: { + /** + * Id + * @description The id of this graph + */ + id?: string; + /** + * Nodes + * @description The nodes in this graph + */ + nodes?: { + [key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["PipelineModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; + }; + /** + * Edges + * @description The connections between nodes and their fields in this graph + */ + edges?: (components["schemas"]["Edge"])[]; + }; + /** + * GraphExecutionState + * @description Tracks the state of a graph execution + */ + GraphExecutionState: { + /** + * Id + * @description The id of the execution state + */ + id: string; + /** + * Graph + * @description The graph being executed + */ + graph: components["schemas"]["Graph"]; + /** + * Execution Graph + * @description The expanded graph of activated and executed nodes + */ + execution_graph: components["schemas"]["Graph"]; + /** + * Executed + * @description The set of node ids that have been executed + */ + executed: (string)[]; + /** + * Executed History + * @description The list of node ids that have been executed, in order of execution + */ + executed_history: (string)[]; + /** + * Results + * @description The results of node executions + */ + results: { + [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; + }; + /** + * Errors + * @description Errors raised when executing nodes + */ + errors: { + [key: string]: string | undefined; + }; + /** + * Prepared Source Mapping + * @description The map of prepared nodes to original graph nodes + */ + prepared_source_mapping: { + [key: string]: string | undefined; + }; + /** + * Source Prepared Mapping + * @description The map of original graph nodes to prepared nodes + */ + source_prepared_mapping: { + [key: string]: (string)[] | undefined; + }; + }; + /** + * GraphInvocation + * @description Execute a graph + */ + GraphInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default graph + * @enum {string} + */ + type?: "graph"; + /** + * Graph + * @description The graph to run + */ + graph?: components["schemas"]["Graph"]; + }; + /** + * GraphInvocationOutput + * @description Base class for all invocation outputs + */ + GraphInvocationOutput: { + /** + * Type + * @default graph_output + * @enum {string} + */ + type: "graph_output"; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: (components["schemas"]["ValidationError"])[]; + }; + /** + * HedImageProcessorInvocation + * @description Applies HED edge detection to image + */ + HedImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default hed_image_processor + * @enum {string} + */ + type?: "hed_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + /** + * Scribble + * @description Whether to use scribble mode + * @default false + */ + scribble?: boolean; + }; + /** + * ImageBlurInvocation + * @description Blurs an image + */ + ImageBlurInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_blur + * @enum {string} + */ + type?: "img_blur"; + /** + * Image + * @description The image to blur + */ + image?: components["schemas"]["ImageField"]; + /** + * Radius + * @description The blur radius + * @default 8 + */ + radius?: number; + /** + * Blur Type + * @description The type of blur + * @default gaussian + * @enum {string} + */ + blur_type?: "gaussian" | "box"; + }; + /** + * ImageCategory + * @description The category of an image. + * + * - GENERAL: The image is an output, init image, or otherwise an image without a specialized purpose. + * - MASK: The image is a mask image. + * - CONTROL: The image is a ControlNet control image. + * - USER: The image is a user-provide image. + * - OTHER: The image is some other type of image with a specialized purpose. To be used by external nodes. + * @enum {string} + */ + ImageCategory: "general" | "mask" | "control" | "user" | "other"; + /** + * ImageChannelInvocation + * @description Gets a channel from an image. + */ + ImageChannelInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_chan + * @enum {string} + */ + type?: "img_chan"; + /** + * Image + * @description The image to get the channel from + */ + image?: components["schemas"]["ImageField"]; + /** + * Channel + * @description The channel to get + * @default A + * @enum {string} + */ + channel?: "A" | "R" | "G" | "B"; + }; + /** + * ImageConvertInvocation + * @description Converts an image to a different mode. + */ + ImageConvertInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_conv + * @enum {string} + */ + type?: "img_conv"; + /** + * Image + * @description The image to convert + */ + image?: components["schemas"]["ImageField"]; + /** + * Mode + * @description The mode to convert to + * @default L + * @enum {string} + */ + mode?: "L" | "RGB" | "RGBA" | "CMYK" | "YCbCr" | "LAB" | "HSV" | "I" | "F"; + }; + /** + * ImageCropInvocation + * @description Crops an image to a specified box. The box can be outside of the image. + */ + ImageCropInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_crop + * @enum {string} + */ + type?: "img_crop"; + /** + * Image + * @description The image to crop + */ + image?: components["schemas"]["ImageField"]; + /** + * X + * @description The left x coordinate of the crop rectangle + * @default 0 + */ + x?: number; + /** + * Y + * @description The top y coordinate of the crop rectangle + * @default 0 + */ + y?: number; + /** + * Width + * @description The width of the crop rectangle + * @default 512 + */ + width?: number; + /** + * Height + * @description The height of the crop rectangle + * @default 512 + */ + height?: number; + }; + /** + * ImageDTO + * @description Deserialized image record, enriched for the frontend. + */ + ImageDTO: { + /** + * Image Name + * @description The unique name of the image. + */ + image_name: string; + /** + * Image Url + * @description The URL of the image. + */ + image_url: string; + /** + * Thumbnail Url + * @description The URL of the image's thumbnail. + */ + thumbnail_url: string; + /** @description The type of the image. */ + image_origin: components["schemas"]["ResourceOrigin"]; + /** @description The category of the image. */ + image_category: components["schemas"]["ImageCategory"]; + /** + * Width + * @description The width of the image in px. + */ + width: number; + /** + * Height + * @description The height of the image in px. + */ + height: number; + /** + * Created At + * @description The created timestamp of the image. + */ + created_at: string; + /** + * Updated At + * @description The updated timestamp of the image. + */ + updated_at: string; + /** + * Deleted At + * @description The deleted timestamp of the image. + */ + deleted_at?: string; + /** + * Is Intermediate + * @description Whether this is an intermediate image. + */ + is_intermediate: boolean; + /** + * Session Id + * @description The session ID that generated this image, if it is a generated image. + */ + session_id?: string; + /** + * Node Id + * @description The node ID that generated this image, if it is a generated image. + */ + node_id?: string; + /** + * Metadata + * @description A limited subset of the image's generation metadata. Retrieve the image's session for full metadata. + */ + metadata?: components["schemas"]["ImageMetadata"]; + /** + * Board Id + * @description The id of the board the image belongs to, if one exists. + */ + board_id?: string; + }; + /** + * ImageField + * @description An image field used for passing image objects between invocations + */ + ImageField: { + /** + * Image Name + * @description The name of the image + */ + image_name: string; + }; + /** + * ImageInverseLerpInvocation + * @description Inverse linear interpolation of all pixels of an image + */ + ImageInverseLerpInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_ilerp + * @enum {string} + */ + type?: "img_ilerp"; + /** + * Image + * @description The image to lerp + */ + image?: components["schemas"]["ImageField"]; + /** + * Min + * @description The minimum input value + * @default 0 + */ + min?: number; + /** + * Max + * @description The maximum input value + * @default 255 + */ + max?: number; + }; + /** + * ImageLerpInvocation + * @description Linear interpolation of all pixels of an image + */ + ImageLerpInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_lerp + * @enum {string} + */ + type?: "img_lerp"; + /** + * Image + * @description The image to lerp + */ + image?: components["schemas"]["ImageField"]; + /** + * Min + * @description The minimum output value + * @default 0 + */ + min?: number; + /** + * Max + * @description The maximum output value + * @default 255 + */ + max?: number; + }; + /** + * ImageMetadata + * @description Core generation metadata for an image/tensor generated in InvokeAI. + * + * Also includes any metadata from the image's PNG tEXt chunks. + * + * Generated by traversing the execution graph, collecting the parameters of the nearest ancestors + * of a given node. + * + * Full metadata may be accessed by querying for the session in the `graph_executions` table. + */ + ImageMetadata: { + /** + * Type + * @description The type of the ancestor node of the image output node. + */ + type?: string; + /** + * Positive Conditioning + * @description The positive conditioning. + */ + positive_conditioning?: string; + /** + * Negative Conditioning + * @description The negative conditioning. + */ + negative_conditioning?: string; + /** + * Width + * @description Width of the image/latents in pixels. + */ + width?: number; + /** + * Height + * @description Height of the image/latents in pixels. + */ + height?: number; + /** + * Seed + * @description The seed used for noise generation. + */ + seed?: number; + /** + * Cfg Scale + * @description The classifier-free guidance scale. + */ + cfg_scale?: number | (number)[]; + /** + * Steps + * @description The number of steps used for inference. + */ + steps?: number; + /** + * Scheduler + * @description The scheduler used for inference. + */ + scheduler?: string; + /** + * Model + * @description The model used for inference. + */ + model?: string; + /** + * Strength + * @description The strength used for image-to-image/latents-to-latents. + */ + strength?: number; + /** + * Latents + * @description The ID of the initial latents. + */ + latents?: string; + /** + * Vae + * @description The VAE used for decoding. + */ + vae?: string; + /** + * Unet + * @description The UNet used dor inference. + */ + unet?: string; + /** + * Clip + * @description The CLIP Encoder used for conditioning. + */ + clip?: string; + /** + * Extra + * @description Uploaded image metadata, extracted from the PNG tEXt chunk. + */ + extra?: string; + }; + /** + * ImageMultiplyInvocation + * @description Multiplies two images together using `PIL.ImageChops.multiply()`. + */ + ImageMultiplyInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_mul + * @enum {string} + */ + type?: "img_mul"; + /** + * Image1 + * @description The first image to multiply + */ + image1?: components["schemas"]["ImageField"]; + /** + * Image2 + * @description The second image to multiply + */ + image2?: components["schemas"]["ImageField"]; + }; + /** + * ImageOutput + * @description Base class for invocations that output an image + */ + ImageOutput: { + /** + * Type + * @default image_output + * @enum {string} + */ + type: "image_output"; + /** + * Image + * @description The output image + */ + image: components["schemas"]["ImageField"]; + /** + * Width + * @description The width of the image in pixels + */ + width: number; + /** + * Height + * @description The height of the image in pixels + */ + height: number; + }; + /** + * ImagePasteInvocation + * @description Pastes an image into another image. + */ + ImagePasteInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_paste + * @enum {string} + */ + type?: "img_paste"; + /** + * Base Image + * @description The base image + */ + base_image?: components["schemas"]["ImageField"]; + /** + * Image + * @description The image to paste + */ + image?: components["schemas"]["ImageField"]; + /** + * Mask + * @description The mask to use when pasting + */ + mask?: components["schemas"]["ImageField"]; + /** + * X + * @description The left x coordinate at which to paste the image + * @default 0 + */ + x?: number; + /** + * Y + * @description The top y coordinate at which to paste the image + * @default 0 + */ + y?: number; + }; + /** + * ImageProcessorInvocation + * @description Base class for invocations that preprocess images for ControlNet + */ + ImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default image_processor + * @enum {string} + */ + type?: "image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + }; + /** + * ImageRecordChanges + * @description A set of changes to apply to an image record. + * + * Only limited changes are valid: + * - `image_category`: change the category of an image + * - `session_id`: change the session associated with an image + * - `is_intermediate`: change the image's `is_intermediate` flag + */ + ImageRecordChanges: { + /** @description The image's new category. */ + image_category?: components["schemas"]["ImageCategory"]; + /** + * Session Id + * @description The image's new session ID. + */ + session_id?: string; + /** + * Is Intermediate + * @description The image's new `is_intermediate` flag. + */ + is_intermediate?: boolean; + }; + /** + * ImageResizeInvocation + * @description Resizes an image to specific dimensions + */ + ImageResizeInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_resize + * @enum {string} + */ + type?: "img_resize"; + /** + * Image + * @description The image to resize + */ + image?: components["schemas"]["ImageField"]; + /** + * Width + * @description The width to resize to (px) + */ + width: number; + /** + * Height + * @description The height to resize to (px) + */ + height: number; + /** + * Resample Mode + * @description The resampling mode + * @default bicubic + * @enum {string} + */ + resample_mode?: "nearest" | "box" | "bilinear" | "hamming" | "bicubic" | "lanczos"; + }; + /** + * ImageScaleInvocation + * @description Scales an image by a factor + */ + ImageScaleInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_scale + * @enum {string} + */ + type?: "img_scale"; + /** + * Image + * @description The image to scale + */ + image?: components["schemas"]["ImageField"]; + /** + * Scale Factor + * @description The factor by which to scale the image + */ + scale_factor: number; + /** + * Resample Mode + * @description The resampling mode + * @default bicubic + * @enum {string} + */ + resample_mode?: "nearest" | "box" | "bilinear" | "hamming" | "bicubic" | "lanczos"; + }; + /** + * ImageToLatentsInvocation + * @description Encodes an image into latents. + */ + ImageToLatentsInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default i2l + * @enum {string} + */ + type?: "i2l"; + /** + * Image + * @description The image to encode + */ + image?: components["schemas"]["ImageField"]; + /** + * Vae + * @description Vae submodel + */ + vae?: components["schemas"]["VaeField"]; + /** + * Tiled + * @description Encode latents by overlaping tiles(less memory consumption) + * @default false + */ + tiled?: boolean; + }; + /** + * ImageUrlsDTO + * @description The URLs for an image and its thumbnail. + */ + ImageUrlsDTO: { + /** + * Image Name + * @description The unique name of the image. + */ + image_name: string; + /** + * Image Url + * @description The URL of the image. + */ + image_url: string; + /** + * Thumbnail Url + * @description The URL of the image's thumbnail. + */ + thumbnail_url: string; + }; + /** + * InfillColorInvocation + * @description Infills transparent areas of an image with a solid color + */ + InfillColorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default infill_rgba + * @enum {string} + */ + type?: "infill_rgba"; + /** + * Image + * @description The image to infill + */ + image?: components["schemas"]["ImageField"]; + /** + * Color + * @description The color to use to infill + * @default { + * "r": 127, + * "g": 127, + * "b": 127, + * "a": 255 + * } + */ + color?: components["schemas"]["ColorField"]; + }; + /** + * InfillPatchMatchInvocation + * @description Infills transparent areas of an image using the PatchMatch algorithm + */ + InfillPatchMatchInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default infill_patchmatch + * @enum {string} + */ + type?: "infill_patchmatch"; + /** + * Image + * @description The image to infill + */ + image?: components["schemas"]["ImageField"]; + }; + /** + * InfillTileInvocation + * @description Infills transparent areas of an image with tiles of the image + */ + InfillTileInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default infill_tile + * @enum {string} + */ + type?: "infill_tile"; + /** + * Image + * @description The image to infill + */ + image?: components["schemas"]["ImageField"]; + /** + * Tile Size + * @description The tile size (px) + * @default 32 + */ + tile_size?: number; + /** + * Seed + * @description The seed to use for tile generation (omit for random) + */ + seed?: number; + }; + /** + * InpaintInvocation + * @description Generates an image using inpaint. + */ + InpaintInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default inpaint + * @enum {string} + */ + type?: "inpaint"; + /** + * Positive Conditioning + * @description Positive conditioning for generation + */ + positive_conditioning?: components["schemas"]["ConditioningField"]; + /** + * Negative Conditioning + * @description Negative conditioning for generation + */ + negative_conditioning?: components["schemas"]["ConditioningField"]; + /** + * Seed + * @description The seed to use (omit for random) + */ + seed?: number; + /** + * Steps + * @description The number of steps to use to generate the image + * @default 30 + */ + steps?: number; + /** + * Width + * @description The width of the resulting image + * @default 512 + */ + width?: number; + /** + * Height + * @description The height of the resulting image + * @default 512 + */ + height?: number; + /** + * Cfg Scale + * @description The Classifier-Free Guidance, higher values may result in a result closer to the prompt + * @default 7.5 + */ + cfg_scale?: number; + /** + * Scheduler + * @description The scheduler to use + * @default euler + * @enum {string} + */ + scheduler?: "ddim" | "ddpm" | "deis" | "lms" | "lms_k" | "pndm" | "heun" | "heun_k" | "euler" | "euler_k" | "euler_a" | "kdpm_2" | "kdpm_2_a" | "dpmpp_2s" | "dpmpp_2s_k" | "dpmpp_2m" | "dpmpp_2m_k" | "dpmpp_2m_sde" | "dpmpp_2m_sde_k" | "dpmpp_sde" | "dpmpp_sde_k" | "unipc"; + /** + * Unet + * @description UNet model + */ + unet?: components["schemas"]["UNetField"]; + /** + * Vae + * @description Vae model + */ + vae?: components["schemas"]["VaeField"]; + /** + * Image + * @description The input image + */ + image?: components["schemas"]["ImageField"]; + /** + * Strength + * @description The strength of the original image + * @default 0.75 + */ + strength?: number; + /** + * Fit + * @description Whether or not the result should be fit to the aspect ratio of the input image + * @default true + */ + fit?: boolean; + /** + * Mask + * @description The mask + */ + mask?: components["schemas"]["ImageField"]; + /** + * Seam Size + * @description The seam inpaint size (px) + * @default 96 + */ + seam_size?: number; + /** + * Seam Blur + * @description The seam inpaint blur radius (px) + * @default 16 + */ + seam_blur?: number; + /** + * Seam Strength + * @description The seam inpaint strength + * @default 0.75 + */ + seam_strength?: number; + /** + * Seam Steps + * @description The number of steps to use for seam inpaint + * @default 30 + */ + seam_steps?: number; + /** + * Tile Size + * @description The tile infill method size (px) + * @default 32 + */ + tile_size?: number; + /** + * Infill Method + * @description The method used to infill empty regions (px) + * @default patchmatch + * @enum {string} + */ + infill_method?: "patchmatch" | "tile" | "solid"; + /** + * Inpaint Width + * @description The width of the inpaint region (px) + */ + inpaint_width?: number; + /** + * Inpaint Height + * @description The height of the inpaint region (px) + */ + inpaint_height?: number; + /** + * Inpaint Fill + * @description The solid infill method color + * @default { + * "r": 127, + * "g": 127, + * "b": 127, + * "a": 255 + * } + */ + inpaint_fill?: components["schemas"]["ColorField"]; + /** + * Inpaint Replace + * @description The amount by which to replace masked areas with latent noise + * @default 0 + */ + inpaint_replace?: number; + }; + /** + * IntCollectionOutput + * @description A collection of integers + */ + IntCollectionOutput: { + /** + * Type + * @default int_collection + * @enum {string} + */ + type?: "int_collection"; + /** + * Collection + * @description The int collection + * @default [] + */ + collection?: (number)[]; + }; + /** + * IntOutput + * @description An integer output + */ + IntOutput: { + /** + * Type + * @default int_output + * @enum {string} + */ + type?: "int_output"; + /** + * A + * @description The output integer + */ + a?: number; + }; + /** + * IterateInvocation + * @description Iterates over a list of items + */ + IterateInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default iterate + * @enum {string} + */ + type?: "iterate"; + /** + * Collection + * @description The list of items to iterate over + */ + collection?: (unknown)[]; + /** + * Index + * @description The index, will be provided on executed iterators + * @default 0 + */ + index?: number; + }; + /** + * IterateInvocationOutput + * @description Used to connect iteration outputs. Will be expanded to a specific output. + */ + IterateInvocationOutput: { + /** + * Type + * @default iterate_output + * @enum {string} + */ + type: "iterate_output"; + /** + * Item + * @description The item being iterated over + */ + item: unknown; + }; + /** + * LatentsField + * @description A latents field used for passing latents between invocations + */ + LatentsField: { + /** + * Latents Name + * @description The name of the latents + */ + latents_name: string; + }; + /** + * LatentsOutput + * @description Base class for invocations that output latents + */ + LatentsOutput: { + /** + * Type + * @default latents_output + * @enum {string} + */ + type?: "latents_output"; + /** + * Latents + * @description The output latents + */ + latents?: components["schemas"]["LatentsField"]; + /** + * Width + * @description The width of the latents in pixels + */ + width: number; + /** + * Height + * @description The height of the latents in pixels + */ + height: number; + }; + /** + * LatentsToImageInvocation + * @description Generates an image from latents. + */ + LatentsToImageInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default l2i + * @enum {string} + */ + type?: "l2i"; + /** + * Latents + * @description The latents to generate an image from + */ + latents?: components["schemas"]["LatentsField"]; + /** + * Vae + * @description Vae submodel + */ + vae?: components["schemas"]["VaeField"]; + /** + * Tiled + * @description Decode latents by overlaping tiles(less memory consumption) + * @default false + */ + tiled?: boolean; + }; + /** + * LatentsToLatentsInvocation + * @description Generates latents using latents as base image. + */ + LatentsToLatentsInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default l2l + * @enum {string} + */ + type?: "l2l"; + /** + * Positive Conditioning + * @description Positive conditioning for generation + */ + positive_conditioning?: components["schemas"]["ConditioningField"]; + /** + * Negative Conditioning + * @description Negative conditioning for generation + */ + negative_conditioning?: components["schemas"]["ConditioningField"]; + /** + * Noise + * @description The noise to use + */ + noise?: components["schemas"]["LatentsField"]; + /** + * Steps + * @description The number of steps to use to generate the image + * @default 10 + */ + steps?: number; + /** + * Cfg Scale + * @description The Classifier-Free Guidance, higher values may result in a result closer to the prompt + * @default 7.5 + */ + cfg_scale?: number | (number)[]; + /** + * Scheduler + * @description The scheduler to use + * @default euler + * @enum {string} + */ + scheduler?: "ddim" | "ddpm" | "deis" | "lms" | "lms_k" | "pndm" | "heun" | "heun_k" | "euler" | "euler_k" | "euler_a" | "kdpm_2" | "kdpm_2_a" | "dpmpp_2s" | "dpmpp_2s_k" | "dpmpp_2m" | "dpmpp_2m_k" | "dpmpp_2m_sde" | "dpmpp_2m_sde_k" | "dpmpp_sde" | "dpmpp_sde_k" | "unipc"; + /** + * Unet + * @description UNet submodel + */ + unet?: components["schemas"]["UNetField"]; + /** + * Control + * @description The control to use + */ + control?: components["schemas"]["ControlField"] | (components["schemas"]["ControlField"])[]; + /** + * Latents + * @description The latents to use as a base image + */ + latents?: components["schemas"]["LatentsField"]; + /** + * Strength + * @description The strength of the latents to use + * @default 0.7 + */ + strength?: number; + }; + /** + * LineartAnimeImageProcessorInvocation + * @description Applies line art anime processing to image + */ + LineartAnimeImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default lineart_anime_image_processor + * @enum {string} + */ + type?: "lineart_anime_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + }; + /** + * LineartImageProcessorInvocation + * @description Applies line art processing to image + */ + LineartImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default lineart_image_processor + * @enum {string} + */ + type?: "lineart_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + /** + * Coarse + * @description Whether to use coarse mode + * @default false + */ + coarse?: boolean; + }; + /** LoRAModelConfig */ + LoRAModelConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "lora"; + /** Path */ + path: string; + /** Description */ + description?: string; + model_format: components["schemas"]["LoRAModelFormat"]; + error?: components["schemas"]["ModelError"]; + }; + /** + * LoRAModelFormat + * @description An enumeration. + * @enum {string} + */ + LoRAModelFormat: "lycoris" | "diffusers"; + /** + * LoadImageInvocation + * @description Load an image and provide it as output. + */ + LoadImageInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default load_image + * @enum {string} + */ + type?: "load_image"; + /** + * Image + * @description The image to load + */ + image?: components["schemas"]["ImageField"]; + }; + /** LoraInfo */ + LoraInfo: { + /** + * Model Name + * @description Info to load submodel + */ + model_name: string; + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + /** @description Info to load submodel */ + model_type: components["schemas"]["ModelType"]; + /** @description Info to load submodel */ + submodel?: components["schemas"]["SubModelType"]; + /** + * Weight + * @description Lora's weight which to use when apply to model + */ + weight: number; + }; + /** + * LoraLoaderInvocation + * @description Apply selected lora to unet and text_encoder. + */ + LoraLoaderInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default lora_loader + * @enum {string} + */ + type?: "lora_loader"; + /** + * Lora Name + * @description Lora model name + */ + lora_name: string; + /** + * Weight + * @description With what weight to apply lora + * @default 0.75 + */ + weight?: number; + /** + * Unet + * @description UNet model for applying lora + */ + unet?: components["schemas"]["UNetField"]; + /** + * Clip + * @description Clip model for applying lora + */ + clip?: components["schemas"]["ClipField"]; + }; + /** + * LoraLoaderOutput + * @description Model loader output + */ + LoraLoaderOutput: { + /** + * Type + * @default lora_loader_output + * @enum {string} + */ + type?: "lora_loader_output"; + /** + * Unet + * @description UNet submodel + */ + unet?: components["schemas"]["UNetField"]; + /** + * Clip + * @description Tokenizer and text_encoder submodels + */ + clip?: components["schemas"]["ClipField"]; + }; + /** + * MaskFromAlphaInvocation + * @description Extracts the alpha channel of an image as a mask. + */ + MaskFromAlphaInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default tomask + * @enum {string} + */ + type?: "tomask"; + /** + * Image + * @description The image to create the mask from + */ + image?: components["schemas"]["ImageField"]; + /** + * Invert + * @description Whether or not to invert the mask + * @default false + */ + invert?: boolean; + }; + /** + * MaskOutput + * @description Base class for invocations that output a mask + */ + MaskOutput: { + /** + * Type + * @default mask + * @enum {string} + */ + type: "mask"; + /** + * Mask + * @description The output mask + */ + mask: components["schemas"]["ImageField"]; + /** + * Width + * @description The width of the mask in pixels + */ + width?: number; + /** + * Height + * @description The height of the mask in pixels + */ + height?: number; + }; + /** + * MediapipeFaceProcessorInvocation + * @description Applies mediapipe face processing to image + */ + MediapipeFaceProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default mediapipe_face_processor + * @enum {string} + */ + type?: "mediapipe_face_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Max Faces + * @description Maximum number of faces to detect + * @default 1 + */ + max_faces?: number; + /** + * Min Confidence + * @description Minimum confidence for face detection + * @default 0.5 + */ + min_confidence?: number; + }; + /** + * MidasDepthImageProcessorInvocation + * @description Applies Midas depth processing to image + */ + MidasDepthImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default midas_depth_image_processor + * @enum {string} + */ + type?: "midas_depth_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * A Mult + * @description Midas parameter `a_mult` (a = a_mult * PI) + * @default 2 + */ + a_mult?: number; + /** + * Bg Th + * @description Midas parameter `bg_th` + * @default 0.1 + */ + bg_th?: number; + }; + /** + * MlsdImageProcessorInvocation + * @description Applies MLSD processing to image + */ + MlsdImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default mlsd_image_processor + * @enum {string} + */ + type?: "mlsd_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + /** + * Thr V + * @description MLSD parameter `thr_v` + * @default 0.1 + */ + thr_v?: number; + /** + * Thr D + * @description MLSD parameter `thr_d` + * @default 0.1 + */ + thr_d?: number; + }; + /** + * ModelError + * @description An enumeration. + * @enum {string} + */ + ModelError: "not_found"; + /** ModelInfo */ + ModelInfo: { + /** + * Model Name + * @description Info to load submodel + */ + model_name: string; + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + /** @description Info to load submodel */ + model_type: components["schemas"]["ModelType"]; + /** @description Info to load submodel */ + submodel?: components["schemas"]["SubModelType"]; + }; + /** + * ModelLoaderOutput + * @description Model loader output + */ + ModelLoaderOutput: { + /** + * Type + * @default model_loader_output + * @enum {string} + */ + type?: "model_loader_output"; + /** + * Unet + * @description UNet submodel + */ + unet?: components["schemas"]["UNetField"]; + /** + * Clip + * @description Tokenizer and text_encoder submodels + */ + clip?: components["schemas"]["ClipField"]; + /** + * Vae + * @description Vae submodel + */ + vae?: components["schemas"]["VaeField"]; + }; + /** + * ModelType + * @description An enumeration. + * @enum {string} + */ + ModelType: "pipeline" | "vae" | "lora" | "controlnet" | "embedding"; + /** + * ModelVariantType + * @description An enumeration. + * @enum {string} + */ + ModelVariantType: "normal" | "inpaint" | "depth"; + /** ModelsList */ + ModelsList: { + /** Models */ + models: (components["schemas"]["StableDiffusion1ModelCheckpointConfig"] | components["schemas"]["StableDiffusion1ModelDiffusersConfig"] | components["schemas"]["VaeModelConfig"] | components["schemas"]["LoRAModelConfig"] | components["schemas"]["ControlNetModelConfig"] | components["schemas"]["TextualInversionModelConfig"] | components["schemas"]["StableDiffusion2ModelCheckpointConfig"] | components["schemas"]["StableDiffusion2ModelDiffusersConfig"])[]; + }; + /** + * MultiplyInvocation + * @description Multiplies two numbers + */ + MultiplyInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default mul + * @enum {string} + */ + type?: "mul"; + /** + * A + * @description The first number + * @default 0 + */ + a?: number; + /** + * B + * @description The second number + * @default 0 + */ + b?: number; + }; + /** + * NoiseInvocation + * @description Generates latent noise. + */ + NoiseInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default noise + * @enum {string} + */ + type?: "noise"; + /** + * Seed + * @description The seed to use + */ + seed?: number; + /** + * Width + * @description The width of the resulting noise + * @default 512 + */ + width?: number; + /** + * Height + * @description The height of the resulting noise + * @default 512 + */ + height?: number; + /** + * Perlin + * @description The amount of perlin noise to add to the noise + * @default 0 + */ + perlin?: number; + /** + * Use Cpu + * @description Use CPU for noise generation (for reproducible results across platforms) + * @default true + */ + use_cpu?: boolean; + }; + /** + * NoiseOutput + * @description Invocation noise output + */ + NoiseOutput: { + /** + * Type + * @default noise_output + * @enum {string} + */ + type?: "noise_output"; + /** + * Noise + * @description The output noise + */ + noise?: components["schemas"]["LatentsField"]; + /** + * Width + * @description The width of the noise in pixels + */ + width: number; + /** + * Height + * @description The height of the noise in pixels + */ + height: number; + }; + /** + * NormalbaeImageProcessorInvocation + * @description Applies NormalBae processing to image + */ + NormalbaeImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default normalbae_image_processor + * @enum {string} + */ + type?: "normalbae_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + }; + /** + * OffsetPaginatedResults[BoardDTO] + * @description Offset-paginated results + */ + OffsetPaginatedResults_BoardDTO_: { + /** + * Items + * @description Items + */ + items: (components["schemas"]["BoardDTO"])[]; + /** + * Offset + * @description Offset from which to retrieve items + */ + offset: number; + /** + * Limit + * @description Limit of items to get + */ + limit: number; + /** + * Total + * @description Total number of items in result + */ + total: number; + }; + /** + * OffsetPaginatedResults[ImageDTO] + * @description Offset-paginated results + */ + OffsetPaginatedResults_ImageDTO_: { + /** + * Items + * @description Items + */ + items: (components["schemas"]["ImageDTO"])[]; + /** + * Offset + * @description Offset from which to retrieve items + */ + offset: number; + /** + * Limit + * @description Limit of items to get + */ + limit: number; + /** + * Total + * @description Total number of items in result + */ + total: number; + }; + /** + * OpenposeImageProcessorInvocation + * @description Applies Openpose processing to image + */ + OpenposeImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default openpose_image_processor + * @enum {string} + */ + type?: "openpose_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Hand And Face + * @description Whether to use hands and face mode + * @default false + */ + hand_and_face?: boolean; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + }; + /** + * PaginatedResults[GraphExecutionState] + * @description Paginated results + */ + PaginatedResults_GraphExecutionState_: { + /** + * Items + * @description Items + */ + items: (components["schemas"]["GraphExecutionState"])[]; + /** + * Page + * @description Current Page + */ + page: number; + /** + * Pages + * @description Total number of pages + */ + pages: number; + /** + * Per Page + * @description Number of items per page + */ + per_page: number; + /** + * Total + * @description Total number of items in result + */ + total: number; + }; + /** + * ParamFloatInvocation + * @description A float parameter + */ + ParamFloatInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default param_float + * @enum {string} + */ + type?: "param_float"; + /** + * Param + * @description The float value + * @default 0 + */ + param?: number; + }; + /** + * ParamIntInvocation + * @description An integer parameter + */ + ParamIntInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default param_int + * @enum {string} + */ + type?: "param_int"; + /** + * A + * @description The integer value + * @default 0 + */ + a?: number; + }; + /** + * PidiImageProcessorInvocation + * @description Applies PIDI processing to image + */ + PidiImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default pidi_image_processor + * @enum {string} + */ + type?: "pidi_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + /** + * Detect Resolution + * @description The pixel resolution for detection + * @default 512 + */ + detect_resolution?: number; + /** + * Image Resolution + * @description The pixel resolution for the output image + * @default 512 + */ + image_resolution?: number; + /** + * Safe + * @description Whether to use safe mode + * @default false + */ + safe?: boolean; + /** + * Scribble + * @description Whether to use scribble mode + * @default false + */ + scribble?: boolean; + }; + /** + * PipelineModelField + * @description Pipeline model field + */ + PipelineModelField: { + /** + * Model Name + * @description Name of the model + */ + model_name: string; + /** @description Base model */ + base_model: components["schemas"]["BaseModelType"]; + }; + /** + * PipelineModelLoaderInvocation + * @description Loads a pipeline model, outputting its submodels. + */ + PipelineModelLoaderInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default pipeline_model_loader + * @enum {string} + */ + type?: "pipeline_model_loader"; + /** + * Model + * @description The model to load + */ + model: components["schemas"]["PipelineModelField"]; + }; + /** + * PromptCollectionOutput + * @description Base class for invocations that output a collection of prompts + */ + PromptCollectionOutput: { + /** + * Type + * @default prompt_collection_output + * @enum {string} + */ + type: "prompt_collection_output"; + /** + * Prompt Collection + * @description The output prompt collection + */ + prompt_collection: (string)[]; + /** + * Count + * @description The size of the prompt collection + */ + count: number; + }; + /** + * PromptOutput + * @description Base class for invocations that output a prompt + */ + PromptOutput: { + /** + * Type + * @default prompt + * @enum {string} + */ + type: "prompt"; + /** + * Prompt + * @description The output prompt + */ + prompt: string; + }; + /** + * RandomIntInvocation + * @description Outputs a single random integer. + */ + RandomIntInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default rand_int + * @enum {string} + */ + type?: "rand_int"; + /** + * Low + * @description The inclusive low value + * @default 0 + */ + low?: number; + /** + * High + * @description The exclusive high value + * @default 2147483647 + */ + high?: number; + }; + /** + * RandomRangeInvocation + * @description Creates a collection of random numbers + */ + RandomRangeInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default random_range + * @enum {string} + */ + type?: "random_range"; + /** + * Low + * @description The inclusive low value + * @default 0 + */ + low?: number; + /** + * High + * @description The exclusive high value + * @default 2147483647 + */ + high?: number; + /** + * Size + * @description The number of values to generate + * @default 1 + */ + size?: number; + /** + * Seed + * @description The seed for the RNG (omit for random) + */ + seed?: number; + }; + /** + * RangeInvocation + * @description Creates a range of numbers from start to stop with step + */ + RangeInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default range + * @enum {string} + */ + type?: "range"; + /** + * Start + * @description The start of the range + * @default 0 + */ + start?: number; + /** + * Stop + * @description The stop of the range + * @default 10 + */ + stop?: number; + /** + * Step + * @description The step of the range + * @default 1 + */ + step?: number; + }; + /** + * RangeOfSizeInvocation + * @description Creates a range from start to start + size with step + */ + RangeOfSizeInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default range_of_size + * @enum {string} + */ + type?: "range_of_size"; + /** + * Start + * @description The start of the range + * @default 0 + */ + start?: number; + /** + * Size + * @description The number of values + * @default 1 + */ + size?: number; + /** + * Step + * @description The step of the range + * @default 1 + */ + step?: number; + }; + /** + * ResizeLatentsInvocation + * @description Resizes latents to explicit width/height (in pixels). Provided dimensions are floor-divided by 8. + */ + ResizeLatentsInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default lresize + * @enum {string} + */ + type?: "lresize"; + /** + * Latents + * @description The latents to resize + */ + latents?: components["schemas"]["LatentsField"]; + /** + * Width + * @description The width to resize to (px) + */ + width: number; + /** + * Height + * @description The height to resize to (px) + */ + height: number; + /** + * Mode + * @description The interpolation mode + * @default bilinear + * @enum {string} + */ + mode?: "nearest" | "linear" | "bilinear" | "bicubic" | "trilinear" | "area" | "nearest-exact"; + /** + * Antialias + * @description Whether or not to antialias (applied in bilinear and bicubic modes only) + * @default false + */ + antialias?: boolean; + }; + /** + * ResourceOrigin + * @description The origin of a resource (eg image). + * + * - INTERNAL: The resource was created by the application. + * - EXTERNAL: The resource was not created by the application. + * This may be a user-initiated upload, or an internal application upload (eg Canvas init image). + * @enum {string} + */ + ResourceOrigin: "internal" | "external"; + /** + * RestoreFaceInvocation + * @description Restores faces in an image. + */ + RestoreFaceInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default restore_face + * @enum {string} + */ + type?: "restore_face"; + /** + * Image + * @description The input image + */ + image?: components["schemas"]["ImageField"]; + /** + * Strength + * @description The strength of the restoration + * @default 0.75 + */ + strength?: number; + }; + /** + * ScaleLatentsInvocation + * @description Scales latents by a given factor. + */ + ScaleLatentsInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default lscale + * @enum {string} + */ + type?: "lscale"; + /** + * Latents + * @description The latents to scale + */ + latents?: components["schemas"]["LatentsField"]; + /** + * Scale Factor + * @description The factor by which to scale the latents + */ + scale_factor: number; + /** + * Mode + * @description The interpolation mode + * @default bilinear + * @enum {string} + */ + mode?: "nearest" | "linear" | "bilinear" | "bicubic" | "trilinear" | "area" | "nearest-exact"; + /** + * Antialias + * @description Whether or not to antialias (applied in bilinear and bicubic modes only) + * @default false + */ + antialias?: boolean; + }; + /** + * SchedulerPredictionType + * @description An enumeration. + * @enum {string} + */ + SchedulerPredictionType: "epsilon" | "v_prediction" | "sample"; + /** + * ShowImageInvocation + * @description Displays a provided image, and passes it forward in the pipeline. + */ + ShowImageInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default show_image + * @enum {string} + */ + type?: "show_image"; + /** + * Image + * @description The image to show + */ + image?: components["schemas"]["ImageField"]; + }; + /** StableDiffusion1ModelCheckpointConfig */ + StableDiffusion1ModelCheckpointConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "pipeline"; + /** Path */ + path: string; + /** Description */ + description?: string; + /** + * Model Format + * @enum {string} + */ + model_format: "checkpoint"; + error?: components["schemas"]["ModelError"]; + /** Vae */ + vae?: string; + /** Config */ + config?: string; + variant: components["schemas"]["ModelVariantType"]; + }; + /** StableDiffusion1ModelDiffusersConfig */ + StableDiffusion1ModelDiffusersConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "pipeline"; + /** Path */ + path: string; + /** Description */ + description?: string; + /** + * Model Format + * @enum {string} + */ + model_format: "diffusers"; + error?: components["schemas"]["ModelError"]; + /** Vae */ + vae?: string; + variant: components["schemas"]["ModelVariantType"]; + }; + /** StableDiffusion2ModelCheckpointConfig */ + StableDiffusion2ModelCheckpointConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "pipeline"; + /** Path */ + path: string; + /** Description */ + description?: string; + /** + * Model Format + * @enum {string} + */ + model_format: "checkpoint"; + error?: components["schemas"]["ModelError"]; + /** Vae */ + vae?: string; + /** Config */ + config?: string; + variant: components["schemas"]["ModelVariantType"]; + prediction_type: components["schemas"]["SchedulerPredictionType"]; + /** Upcast Attention */ + upcast_attention: boolean; + }; + /** StableDiffusion2ModelDiffusersConfig */ + StableDiffusion2ModelDiffusersConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "pipeline"; + /** Path */ + path: string; + /** Description */ + description?: string; + /** + * Model Format + * @enum {string} + */ + model_format: "diffusers"; + error?: components["schemas"]["ModelError"]; + /** Vae */ + vae?: string; + variant: components["schemas"]["ModelVariantType"]; + prediction_type: components["schemas"]["SchedulerPredictionType"]; + /** Upcast Attention */ + upcast_attention: boolean; + }; + /** + * StepParamEasingInvocation + * @description Experimental per-step parameter easing for denoising steps + */ + StepParamEasingInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default step_param_easing + * @enum {string} + */ + type?: "step_param_easing"; + /** + * Easing + * @description The easing function to use + * @default Linear + * @enum {string} + */ + easing?: "Linear" | "QuadIn" | "QuadOut" | "QuadInOut" | "CubicIn" | "CubicOut" | "CubicInOut" | "QuarticIn" | "QuarticOut" | "QuarticInOut" | "QuinticIn" | "QuinticOut" | "QuinticInOut" | "SineIn" | "SineOut" | "SineInOut" | "CircularIn" | "CircularOut" | "CircularInOut" | "ExponentialIn" | "ExponentialOut" | "ExponentialInOut" | "ElasticIn" | "ElasticOut" | "ElasticInOut" | "BackIn" | "BackOut" | "BackInOut" | "BounceIn" | "BounceOut" | "BounceInOut"; + /** + * Num Steps + * @description number of denoising steps + * @default 20 + */ + num_steps?: number; + /** + * Start Value + * @description easing starting value + * @default 0 + */ + start_value?: number; + /** + * End Value + * @description easing ending value + * @default 1 + */ + end_value?: number; + /** + * Start Step Percent + * @description fraction of steps at which to start easing + * @default 0 + */ + start_step_percent?: number; + /** + * End Step Percent + * @description fraction of steps after which to end easing + * @default 1 + */ + end_step_percent?: number; + /** + * Pre Start Value + * @description value before easing start + */ + pre_start_value?: number; + /** + * Post End Value + * @description value after easing end + */ + post_end_value?: number; + /** + * Mirror + * @description include mirror of easing function + * @default false + */ + mirror?: boolean; + /** + * Show Easing Plot + * @description show easing plot + * @default false + */ + show_easing_plot?: boolean; + }; + /** + * SubModelType + * @description An enumeration. + * @enum {string} + */ + SubModelType: "unet" | "text_encoder" | "tokenizer" | "vae" | "scheduler" | "safety_checker"; + /** + * SubtractInvocation + * @description Subtracts two numbers + */ + SubtractInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default sub + * @enum {string} + */ + type?: "sub"; + /** + * A + * @description The first number + * @default 0 + */ + a?: number; + /** + * B + * @description The second number + * @default 0 + */ + b?: number; + }; + /** + * TextToLatentsInvocation + * @description Generates latents from conditionings. + */ + TextToLatentsInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default t2l + * @enum {string} + */ + type?: "t2l"; + /** + * Positive Conditioning + * @description Positive conditioning for generation + */ + positive_conditioning?: components["schemas"]["ConditioningField"]; + /** + * Negative Conditioning + * @description Negative conditioning for generation + */ + negative_conditioning?: components["schemas"]["ConditioningField"]; + /** + * Noise + * @description The noise to use + */ + noise?: components["schemas"]["LatentsField"]; + /** + * Steps + * @description The number of steps to use to generate the image + * @default 10 + */ + steps?: number; + /** + * Cfg Scale + * @description The Classifier-Free Guidance, higher values may result in a result closer to the prompt + * @default 7.5 + */ + cfg_scale?: number | (number)[]; + /** + * Scheduler + * @description The scheduler to use + * @default euler + * @enum {string} + */ + scheduler?: "ddim" | "ddpm" | "deis" | "lms" | "lms_k" | "pndm" | "heun" | "heun_k" | "euler" | "euler_k" | "euler_a" | "kdpm_2" | "kdpm_2_a" | "dpmpp_2s" | "dpmpp_2s_k" | "dpmpp_2m" | "dpmpp_2m_k" | "dpmpp_2m_sde" | "dpmpp_2m_sde_k" | "dpmpp_sde" | "dpmpp_sde_k" | "unipc"; + /** + * Unet + * @description UNet submodel + */ + unet?: components["schemas"]["UNetField"]; + /** + * Control + * @description The control to use + */ + control?: components["schemas"]["ControlField"] | (components["schemas"]["ControlField"])[]; + }; + /** TextualInversionModelConfig */ + TextualInversionModelConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "embedding"; + /** Path */ + path: string; + /** Description */ + description?: string; + /** Model Format */ + model_format: null; + error?: components["schemas"]["ModelError"]; + }; + /** UNetField */ + UNetField: { + /** + * Unet + * @description Info to load unet submodel + */ + unet: components["schemas"]["ModelInfo"]; + /** + * Scheduler + * @description Info to load scheduler submodel + */ + scheduler: components["schemas"]["ModelInfo"]; + /** + * Loras + * @description Loras to apply on model loading + */ + loras: (components["schemas"]["LoraInfo"])[]; + }; + /** + * UpscaleInvocation + * @description Upscales an image. + */ + UpscaleInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default upscale + * @enum {string} + */ + type?: "upscale"; + /** + * Image + * @description The input image + */ + image?: components["schemas"]["ImageField"]; + /** + * Strength + * @description The strength + * @default 0.75 + */ + strength?: number; + /** + * Level + * @description The upscale level + * @default 2 + * @enum {integer} + */ + level?: 2 | 4; + }; + /** VaeField */ + VaeField: { + /** + * Vae + * @description Info to load vae submodel + */ + vae: components["schemas"]["ModelInfo"]; + }; + /** VaeModelConfig */ + VaeModelConfig: { + /** Name */ + name: string; + base_model: components["schemas"]["BaseModelType"]; + /** + * Type + * @enum {string} + */ + type: "vae"; + /** Path */ + path: string; + /** Description */ + description?: string; + model_format: components["schemas"]["VaeModelFormat"]; + error?: components["schemas"]["ModelError"]; + }; + /** + * VaeModelFormat + * @description An enumeration. + * @enum {string} + */ + VaeModelFormat: "checkpoint" | "diffusers"; + /** VaeRepo */ + VaeRepo: { + /** + * Repo Id + * @description The repo ID to use for this VAE + */ + repo_id: string; + /** + * Path + * @description The path to the VAE + */ + path?: string; + /** + * Subfolder + * @description The subfolder to use for this VAE + */ + subfolder?: string; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + /** + * ZoeDepthImageProcessorInvocation + * @description Applies Zoe depth processing to image + */ + ZoeDepthImageProcessorInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default zoe_depth_image_processor + * @enum {string} + */ + type?: "zoe_depth_image_processor"; + /** + * Image + * @description The image to process + */ + image?: components["schemas"]["ImageField"]; + }; + /** + * StableDiffusion2ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusion1ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +}; + +export type external = Record; + +export type operations = { + + /** + * List Sessions + * @description Gets a list of sessions, optionally searching + */ + list_sessions: { + parameters: { + query?: { + /** @description The page of results to get */ + page?: number; + /** @description The number of results per page */ + per_page?: number; + /** @description The query string to search for */ + query?: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["PaginatedResults_GraphExecutionState_"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Create Session + * @description Creates a new session, optionally initializing it with an invocation graph + */ + create_session: { + requestBody?: { + content: { + "application/json": components["schemas"]["Graph"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["GraphExecutionState"]; + }; + }; + /** @description Invalid json */ + 400: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Session + * @description Gets a session + */ + get_session: { + parameters: { + path: { + /** @description The id of the session to get */ + session_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["GraphExecutionState"]; + }; + }; + /** @description Session not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Add Node + * @description Adds a node to the graph + */ + add_node: { + parameters: { + path: { + /** @description The id of the session */ + session_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["PipelineModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": string; + }; + }; + /** @description Invalid node or link */ + 400: never; + /** @description Session not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Update Node + * @description Updates a node in the graph and removes all linked edges + */ + update_node: { + parameters: { + path: { + /** @description The id of the session */ + session_id: string; + /** @description The path to the node in the graph */ + node_path: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["PipelineModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["GraphExecutionState"]; + }; + }; + /** @description Invalid node or link */ + 400: never; + /** @description Session not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Delete Node + * @description Deletes a node in the graph and removes all linked edges + */ + delete_node: { + parameters: { + path: { + /** @description The id of the session */ + session_id: string; + /** @description The path to the node to delete */ + node_path: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["GraphExecutionState"]; + }; + }; + /** @description Invalid node or link */ + 400: never; + /** @description Session not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Add Edge + * @description Adds an edge to the graph + */ + add_edge: { + parameters: { + path: { + /** @description The id of the session */ + session_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Edge"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["GraphExecutionState"]; + }; + }; + /** @description Invalid node or link */ + 400: never; + /** @description Session not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Delete Edge + * @description Deletes an edge from the graph + */ + delete_edge: { + parameters: { + path: { + /** @description The id of the session */ + session_id: string; + /** @description The id of the node the edge is coming from */ + from_node_id: string; + /** @description The field of the node the edge is coming from */ + from_field: string; + /** @description The id of the node the edge is going to */ + to_node_id: string; + /** @description The field of the node the edge is going to */ + to_field: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["GraphExecutionState"]; + }; + }; + /** @description Invalid node or link */ + 400: never; + /** @description Session not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Invoke Session + * @description Invokes a session + */ + invoke_session: { + parameters: { + query?: { + /** @description Whether or not to invoke all remaining invocations */ + all?: boolean; + }; + path: { + /** @description The id of the session to invoke */ + session_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description The invocation is queued */ + 202: never; + /** @description The session has no invocations ready to invoke */ + 400: never; + /** @description Session not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Cancel Session Invoke + * @description Invokes a session + */ + cancel_session_invoke: { + parameters: { + path: { + /** @description The id of the session to cancel */ + session_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description The invocation is canceled */ + 202: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * List Models + * @description Gets a list of models + */ + list_models: { + parameters: { + query?: { + /** @description Base model */ + base_model?: components["schemas"]["BaseModelType"]; + /** @description The type of model to get */ + model_type?: components["schemas"]["ModelType"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["ModelsList"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Update Model + * @description Add Model + */ + update_model: { + requestBody: { + content: { + "application/json": components["schemas"]["CreateModelRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Delete Model + * @description Delete Model + */ + del_model: { + parameters: { + path: { + model_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Model deleted successfully */ + 204: never; + /** @description Model not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * List Images With Metadata + * @description Gets a list of images + */ + list_images_with_metadata: { + parameters: { + query?: { + /** @description The origin of images to list */ + image_origin?: components["schemas"]["ResourceOrigin"]; + /** @description The categories of image to include */ + categories?: (components["schemas"]["ImageCategory"])[]; + /** @description Whether to list intermediate images */ + is_intermediate?: boolean; + /** @description The board id to filter by */ + board_id?: string; + /** @description The page offset */ + offset?: number; + /** @description The number of images per page */ + limit?: number; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["OffsetPaginatedResults_ImageDTO_"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Upload Image + * @description Uploads an image + */ + upload_image: { + parameters: { + query: { + /** @description The category of the image */ + image_category: components["schemas"]["ImageCategory"]; + /** @description Whether this is an intermediate image */ + is_intermediate: boolean; + /** @description The session ID associated with this upload, if any */ + session_id?: string; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_image"]; + }; + }; + responses: { + /** @description The image was uploaded successfully */ + 201: { + content: { + "application/json": components["schemas"]["ImageDTO"]; + }; + }; + /** @description Image upload failed */ + 415: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Image Full + * @description Gets a full-resolution image file + */ + get_image_full: { + parameters: { + path: { + /** @description The name of full-resolution image file to get */ + image_name: string; + }; + }; + responses: { + /** @description Return the full-resolution image */ + 200: { + content: { + "image/png": unknown; + }; + }; + /** @description Image not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Delete Image + * @description Deletes an image + */ + delete_image: { + parameters: { + path: { + /** @description The name of the image to delete */ + image_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Update Image + * @description Updates an image + */ + update_image: { + parameters: { + path: { + /** @description The name of the image to update */ + image_name: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ImageRecordChanges"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["ImageDTO"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Image Metadata + * @description Gets an image's metadata + */ + get_image_metadata: { + parameters: { + path: { + /** @description The name of image to get */ + image_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["ImageDTO"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Image Thumbnail + * @description Gets a thumbnail image file + */ + get_image_thumbnail: { + parameters: { + path: { + /** @description The name of thumbnail image file to get */ + image_name: string; + }; + }; + responses: { + /** @description Return the image thumbnail */ + 200: { + content: { + "image/webp": unknown; + }; + }; + /** @description Image not found */ + 404: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Image Urls + * @description Gets an image and thumbnail URL + */ + get_image_urls: { + parameters: { + path: { + /** @description The name of the image whose URL to get */ + image_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["ImageUrlsDTO"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * List Boards + * @description Gets a list of boards + */ + list_boards: { + parameters: { + query?: { + /** @description Whether to list all boards */ + all?: boolean; + /** @description The page offset */ + offset?: number; + /** @description The number of boards per page */ + limit?: number; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["OffsetPaginatedResults_BoardDTO_"] | (components["schemas"]["BoardDTO"])[]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Create Board + * @description Creates a board + */ + create_board: { + parameters: { + query: { + /** @description The name of the board to create */ + board_name: string; + }; + }; + responses: { + /** @description The board was created successfully */ + 201: { + content: { + "application/json": components["schemas"]["BoardDTO"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Board + * @description Gets a board + */ + get_board: { + parameters: { + path: { + /** @description The id of board to get */ + board_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BoardDTO"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Delete Board + * @description Deletes a board + */ + delete_board: { + parameters: { + path: { + /** @description The id of board to delete */ + board_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Update Board + * @description Updates a board + */ + update_board: { + parameters: { + path: { + /** @description The id of board to update */ + board_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BoardChanges"]; + }; + }; + responses: { + /** @description The board was updated successfully */ + 201: { + content: { + "application/json": components["schemas"]["BoardDTO"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Create Board Image + * @description Creates a board_image + */ + create_board_image: { + requestBody: { + content: { + "application/json": components["schemas"]["Body_create_board_image"]; + }; + }; + responses: { + /** @description The image was added to a board successfully */ + 201: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Remove Board Image + * @description Deletes a board_image + */ + remove_board_image: { + requestBody: { + content: { + "application/json": components["schemas"]["Body_remove_board_image"]; + }; + }; + responses: { + /** @description The image was removed from the board successfully */ + 201: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * List Board Images + * @description Gets a list of images for a board + */ + list_board_images: { + parameters: { + query?: { + /** @description The page offset */ + offset?: number; + /** @description The number of boards per page */ + limit?: number; + }; + path: { + /** @description The id of the board */ + board_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["OffsetPaginatedResults_ImageDTO_"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; +}; diff --git a/invokeai/frontend/web/src/services/api/schemas/$CannyImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$CannyImageProcessorInvocation.ts deleted file mode 100644 index e2f1bc2111..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$CannyImageProcessorInvocation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $CannyImageProcessorInvocation = { - description: `Canny edge detection for ControlNet`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - low_threshold: { - type: 'number', - description: `low threshold of Canny pixel gradient`, - }, - high_threshold: { - type: 'number', - description: `high threshold of Canny pixel gradient`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$ContentShuffleImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$ContentShuffleImageProcessorInvocation.ts deleted file mode 100644 index 9c51fdecc0..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$ContentShuffleImageProcessorInvocation.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $ContentShuffleImageProcessorInvocation = { - description: `Applies content shuffle processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - 'h': { - type: 'number', - description: `content shuffle h parameter`, - }, - 'w': { - type: 'number', - description: `content shuffle w parameter`, - }, - 'f': { - type: 'number', - description: `cont`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$ControlField.ts b/invokeai/frontend/web/src/services/api/schemas/$ControlField.ts deleted file mode 100644 index 81292b8638..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$ControlField.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $ControlField = { - properties: { - image: { - type: 'all-of', - description: `processed image`, - contains: [{ - type: 'ImageField', - }], - isRequired: true, - }, - control_model: { - type: 'string', - description: `control model used`, - isRequired: true, - }, - control_weight: { - type: 'number', - description: `weight given to controlnet`, - isRequired: true, - }, - begin_step_percent: { - type: 'number', - description: `% of total steps at which controlnet is first applied`, - isRequired: true, - maximum: 1, - }, - end_step_percent: { - type: 'number', - description: `% of total steps at which controlnet is last applied`, - isRequired: true, - maximum: 1, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$ControlNetInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$ControlNetInvocation.ts deleted file mode 100644 index 29ff507e66..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$ControlNetInvocation.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $ControlNetInvocation = { - description: `Collects ControlNet info to pass to other nodes`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - control_model: { - type: 'Enum', - }, - control_weight: { - type: 'number', - description: `weight given to controlnet`, - maximum: 1, - }, - begin_step_percent: { - type: 'number', - description: `% of total steps at which controlnet is first applied`, - maximum: 1, - }, - end_step_percent: { - type: 'number', - description: `% of total steps at which controlnet is last applied`, - maximum: 1, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$ControlOutput.ts b/invokeai/frontend/web/src/services/api/schemas/$ControlOutput.ts deleted file mode 100644 index d94d633fca..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$ControlOutput.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $ControlOutput = { - description: `node output for ControlNet info`, - properties: { - type: { - type: 'Enum', - }, - control: { - type: 'all-of', - description: `The control info dict`, - contains: [{ - type: 'ControlField', - }], - }, - width: { - type: 'number', - description: `The width of the noise in pixels`, - isRequired: true, - }, - height: { - type: 'number', - description: `The height of the noise in pixels`, - isRequired: true, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$HedImageprocessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$HedImageprocessorInvocation.ts deleted file mode 100644 index 3cffa008f5..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$HedImageprocessorInvocation.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $HedImageprocessorInvocation = { - description: `Applies HED edge detection to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - scribble: { - type: 'boolean', - description: `whether to use scribble mode`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$ImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$ImageProcessorInvocation.ts deleted file mode 100644 index 36748982c5..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$ImageProcessorInvocation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $ImageProcessorInvocation = { - description: `Base class for invocations that preprocess images for ControlNet`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$LineartAnimeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$LineartAnimeImageProcessorInvocation.ts deleted file mode 100644 index 63a9c8158c..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$LineartAnimeImageProcessorInvocation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $LineartAnimeImageProcessorInvocation = { - description: `Applies line art anime processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$LineartImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$LineartImageProcessorInvocation.ts deleted file mode 100644 index 6ba4064823..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$LineartImageProcessorInvocation.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $LineartImageProcessorInvocation = { - description: `Applies line art processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - coarse: { - type: 'boolean', - description: `whether to use coarse mode`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$MidasDepthImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$MidasDepthImageProcessorInvocation.ts deleted file mode 100644 index ea0b2b0099..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$MidasDepthImageProcessorInvocation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $MidasDepthImageProcessorInvocation = { - description: `Applies Midas depth processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - a_mult: { - type: 'number', - description: `Midas parameter a = amult * PI`, - }, - bg_th: { - type: 'number', - description: `Midas parameter bg_th`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$MlsdImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$MlsdImageProcessorInvocation.ts deleted file mode 100644 index 1bff7579cc..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$MlsdImageProcessorInvocation.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $MlsdImageProcessorInvocation = { - description: `Applies MLSD processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - thr_v: { - type: 'number', - description: `MLSD parameter thr_v`, - }, - thr_d: { - type: 'number', - description: `MLSD parameter thr_d`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$NormalbaeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$NormalbaeImageProcessorInvocation.ts deleted file mode 100644 index 7cdfe6f3ae..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$NormalbaeImageProcessorInvocation.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $NormalbaeImageProcessorInvocation = { - description: `Applies NormalBae processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$OpenposeImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$OpenposeImageProcessorInvocation.ts deleted file mode 100644 index 2a187e9cf2..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$OpenposeImageProcessorInvocation.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $OpenposeImageProcessorInvocation = { - description: `Applies Openpose processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - hand_and_face: { - type: 'boolean', - description: `whether to use hands and face mode`, - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$PidiImageProcessorInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$PidiImageProcessorInvocation.ts deleted file mode 100644 index 0fd53967c2..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$PidiImageProcessorInvocation.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $PidiImageProcessorInvocation = { - description: `Applies PIDI processing to image`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - image: { - type: 'all-of', - description: `image to process`, - contains: [{ - type: 'ImageField', - }], - }, - detect_resolution: { - type: 'number', - description: `pixel resolution for edge detection`, - }, - image_resolution: { - type: 'number', - description: `pixel resolution for output image`, - }, - safe: { - type: 'boolean', - description: `whether to use safe mode`, - }, - scribble: { - type: 'boolean', - description: `whether to use scribble mode`, - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/schemas/$RandomIntInvocation.ts b/invokeai/frontend/web/src/services/api/schemas/$RandomIntInvocation.ts deleted file mode 100644 index e5b0387d5a..0000000000 --- a/invokeai/frontend/web/src/services/api/schemas/$RandomIntInvocation.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export const $RandomIntInvocation = { - description: `Outputs a single random integer.`, - properties: { - id: { - type: 'string', - description: `The id of this node. Must be unique among all nodes.`, - isRequired: true, - }, - type: { - type: 'Enum', - }, - }, -} as const; diff --git a/invokeai/frontend/web/src/services/api/services/ImagesService.ts b/invokeai/frontend/web/src/services/api/services/ImagesService.ts deleted file mode 100644 index d01a97a45e..0000000000 --- a/invokeai/frontend/web/src/services/api/services/ImagesService.ts +++ /dev/null @@ -1,303 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { Body_upload_image } from '../models/Body_upload_image'; -import type { ImageCategory } from '../models/ImageCategory'; -import type { ImageDTO } from '../models/ImageDTO'; -import type { ImageRecordChanges } from '../models/ImageRecordChanges'; -import type { ImageType } from '../models/ImageType'; -import type { ImageUrlsDTO } from '../models/ImageUrlsDTO'; -import type { PaginatedResults_ImageDTO_ } from '../models/PaginatedResults_ImageDTO_'; - -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; - -export class ImagesService { - - /** - * List Images With Metadata - * Gets a list of images with metadata - * @returns PaginatedResults_ImageDTO_ Successful Response - * @throws ApiError - */ - public static listImagesWithMetadata({ - imageType, - imageCategory, - page, - perPage = 10, - }: { - /** - * The type of images to list - */ - imageType: ImageType, - /** - * The kind of images to list - */ - imageCategory: ImageCategory, - /** - * The page of image metadata to get - */ - page?: number, - /** - * The number of image metadata per page - */ - perPage?: number, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/images/', - query: { - 'image_type': imageType, - 'image_category': imageCategory, - 'page': page, - 'per_page': perPage, - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Upload Image - * Uploads an image - * @returns ImageDTO The image was uploaded successfully - * @throws ApiError - */ - public static uploadImage({ - formData, - imageCategory, - isIntermediate = false, - sessionId, - }: { - formData: Body_upload_image, - /** - * The category of the image - */ - imageCategory?: ImageCategory, - /** - * Whether this is an intermediate image - */ - isIntermediate?: boolean, - /** - * The session ID associated with this upload, if any - */ - sessionId?: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/images/', - query: { - 'image_category': imageCategory, - 'is_intermediate': isIntermediate, - 'session_id': sessionId, - }, - formData: formData, - mediaType: 'multipart/form-data', - errors: { - 415: `Image upload failed`, - 422: `Validation Error`, - }, - }); - } - - /** - * Get Image Full - * Gets a full-resolution image file - * @returns any Return the full-resolution image - * @throws ApiError - */ - public static getImageFull({ - imageType, - imageName, - }: { - /** - * The type of full-resolution image file to get - */ - imageType: ImageType, - /** - * The name of full-resolution image file to get - */ - imageName: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/images/{image_type}/{image_name}', - path: { - 'image_type': imageType, - 'image_name': imageName, - }, - errors: { - 404: `Image not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Delete Image - * Deletes an image - * @returns any Successful Response - * @throws ApiError - */ - public static deleteImage({ - imageType, - imageName, - }: { - /** - * The type of image to delete - */ - imageType: ImageType, - /** - * The name of the image to delete - */ - imageName: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/images/{image_type}/{image_name}', - path: { - 'image_type': imageType, - 'image_name': imageName, - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Update Image - * Updates an image - * @returns ImageDTO Successful Response - * @throws ApiError - */ - public static updateImage({ - imageType, - imageName, - requestBody, - }: { - /** - * The type of image to update - */ - imageType: ImageType, - /** - * The name of the image to update - */ - imageName: string, - requestBody: ImageRecordChanges, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'PATCH', - url: '/api/v1/images/{image_type}/{image_name}', - path: { - 'image_type': imageType, - 'image_name': imageName, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Get Image Metadata - * Gets an image's metadata - * @returns ImageDTO Successful Response - * @throws ApiError - */ - public static getImageMetadata({ - imageType, - imageName, - }: { - /** - * The type of image to get - */ - imageType: ImageType, - /** - * The name of image to get - */ - imageName: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/images/{image_type}/{image_name}/metadata', - path: { - 'image_type': imageType, - 'image_name': imageName, - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Get Image Thumbnail - * Gets a thumbnail image file - * @returns any Return the image thumbnail - * @throws ApiError - */ - public static getImageThumbnail({ - imageType, - imageName, - }: { - /** - * The type of thumbnail image file to get - */ - imageType: ImageType, - /** - * The name of thumbnail image file to get - */ - imageName: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/images/{image_type}/{image_name}/thumbnail', - path: { - 'image_type': imageType, - 'image_name': imageName, - }, - errors: { - 404: `Image not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Get Image Urls - * Gets an image and thumbnail URL - * @returns ImageUrlsDTO Successful Response - * @throws ApiError - */ - public static getImageUrls({ - imageType, - imageName, - }: { - /** - * The type of the image whose URL to get - */ - imageType: ImageType, - /** - * The name of the image whose URL to get - */ - imageName: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/images/{image_type}/{image_name}/urls', - path: { - 'image_type': imageType, - 'image_name': imageName, - }, - errors: { - 422: `Validation Error`, - }, - }); - } - -} diff --git a/invokeai/frontend/web/src/services/api/services/ModelsService.ts b/invokeai/frontend/web/src/services/api/services/ModelsService.ts deleted file mode 100644 index 3f8ae6bf7b..0000000000 --- a/invokeai/frontend/web/src/services/api/services/ModelsService.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { CreateModelRequest } from '../models/CreateModelRequest'; -import type { ModelsList } from '../models/ModelsList'; - -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; - -export class ModelsService { - - /** - * List Models - * Gets a list of models - * @returns ModelsList Successful Response - * @throws ApiError - */ - public static listModels(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/models/', - }); - } - - /** - * Update Model - * Add Model - * @returns any Successful Response - * @throws ApiError - */ - public static updateModel({ - requestBody, - }: { - requestBody: CreateModelRequest, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/models/', - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Delete Model - * Delete Model - * @returns any Successful Response - * @throws ApiError - */ - public static delModel({ - modelName, - }: { - modelName: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/models/{model_name}', - path: { - 'model_name': modelName, - }, - errors: { - 404: `Model not found`, - 422: `Validation Error`, - }, - }); - } - -} diff --git a/invokeai/frontend/web/src/services/api/services/SessionsService.ts b/invokeai/frontend/web/src/services/api/services/SessionsService.ts deleted file mode 100644 index 1c55d36502..0000000000 --- a/invokeai/frontend/web/src/services/api/services/SessionsService.ts +++ /dev/null @@ -1,393 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { AddInvocation } from '../models/AddInvocation'; -import type { CollectInvocation } from '../models/CollectInvocation'; -import type { CompelInvocation } from '../models/CompelInvocation'; -import type { CvInpaintInvocation } from '../models/CvInpaintInvocation'; -import type { DivideInvocation } from '../models/DivideInvocation'; -import type { Edge } from '../models/Edge'; -import type { Graph } from '../models/Graph'; -import type { GraphExecutionState } from '../models/GraphExecutionState'; -import type { GraphInvocation } from '../models/GraphInvocation'; -import type { ImageBlurInvocation } from '../models/ImageBlurInvocation'; -import type { ImageChannelInvocation } from '../models/ImageChannelInvocation'; -import type { ImageConvertInvocation } from '../models/ImageConvertInvocation'; -import type { ImageCropInvocation } from '../models/ImageCropInvocation'; -import type { ImageInverseLerpInvocation } from '../models/ImageInverseLerpInvocation'; -import type { ImageLerpInvocation } from '../models/ImageLerpInvocation'; -import type { ImageMultiplyInvocation } from '../models/ImageMultiplyInvocation'; -import type { ImagePasteInvocation } from '../models/ImagePasteInvocation'; -import type { ImageToImageInvocation } from '../models/ImageToImageInvocation'; -import type { ImageToLatentsInvocation } from '../models/ImageToLatentsInvocation'; -import type { InfillColorInvocation } from '../models/InfillColorInvocation'; -import type { InfillPatchMatchInvocation } from '../models/InfillPatchMatchInvocation'; -import type { InfillTileInvocation } from '../models/InfillTileInvocation'; -import type { InpaintInvocation } from '../models/InpaintInvocation'; -import type { IterateInvocation } from '../models/IterateInvocation'; -import type { LatentsToImageInvocation } from '../models/LatentsToImageInvocation'; -import type { LatentsToLatentsInvocation } from '../models/LatentsToLatentsInvocation'; -import type { LoadImageInvocation } from '../models/LoadImageInvocation'; -import type { MaskFromAlphaInvocation } from '../models/MaskFromAlphaInvocation'; -import type { MultiplyInvocation } from '../models/MultiplyInvocation'; -import type { NoiseInvocation } from '../models/NoiseInvocation'; -import type { PaginatedResults_GraphExecutionState_ } from '../models/PaginatedResults_GraphExecutionState_'; -import type { ParamIntInvocation } from '../models/ParamIntInvocation'; -import type { RandomIntInvocation } from '../models/RandomIntInvocation'; -import type { RandomRangeInvocation } from '../models/RandomRangeInvocation'; -import type { RangeInvocation } from '../models/RangeInvocation'; -import type { RangeOfSizeInvocation } from '../models/RangeOfSizeInvocation'; -import type { ResizeLatentsInvocation } from '../models/ResizeLatentsInvocation'; -import type { RestoreFaceInvocation } from '../models/RestoreFaceInvocation'; -import type { ScaleLatentsInvocation } from '../models/ScaleLatentsInvocation'; -import type { ShowImageInvocation } from '../models/ShowImageInvocation'; -import type { SubtractInvocation } from '../models/SubtractInvocation'; -import type { TextToImageInvocation } from '../models/TextToImageInvocation'; -import type { TextToLatentsInvocation } from '../models/TextToLatentsInvocation'; -import type { UpscaleInvocation } from '../models/UpscaleInvocation'; - -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; - -export class SessionsService { - - /** - * List Sessions - * Gets a list of sessions, optionally searching - * @returns PaginatedResults_GraphExecutionState_ Successful Response - * @throws ApiError - */ - public static listSessions({ - page, - perPage = 10, - query = '', - }: { - /** - * The page of results to get - */ - page?: number, - /** - * The number of results per page - */ - perPage?: number, - /** - * The query string to search for - */ - query?: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/sessions/', - query: { - 'page': page, - 'per_page': perPage, - 'query': query, - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Create Session - * Creates a new session, optionally initializing it with an invocation graph - * @returns GraphExecutionState Successful Response - * @throws ApiError - */ - public static createSession({ - requestBody, - }: { - requestBody?: Graph, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/sessions/', - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Invalid json`, - 422: `Validation Error`, - }, - }); - } - - /** - * Get Session - * Gets a session - * @returns GraphExecutionState Successful Response - * @throws ApiError - */ - public static getSession({ - sessionId, - }: { - /** - * The id of the session to get - */ - sessionId: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/sessions/{session_id}', - path: { - 'session_id': sessionId, - }, - errors: { - 404: `Session not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Add Node - * Adds a node to the graph - * @returns string Successful Response - * @throws ApiError - */ - public static addNode({ - sessionId, - requestBody, - }: { - /** - * The id of the session - */ - sessionId: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/sessions/{session_id}/nodes', - path: { - 'session_id': sessionId, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Invalid node or link`, - 404: `Session not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Update Node - * Updates a node in the graph and removes all linked edges - * @returns GraphExecutionState Successful Response - * @throws ApiError - */ - public static updateNode({ - sessionId, - nodePath, - requestBody, - }: { - /** - * The id of the session - */ - sessionId: string, - /** - * The path to the node in the graph - */ - nodePath: string, - requestBody: (LoadImageInvocation | ShowImageInvocation | ImageCropInvocation | ImagePasteInvocation | MaskFromAlphaInvocation | ImageMultiplyInvocation | ImageChannelInvocation | ImageConvertInvocation | ImageBlurInvocation | ImageLerpInvocation | ImageInverseLerpInvocation | CompelInvocation | AddInvocation | SubtractInvocation | MultiplyInvocation | DivideInvocation | RandomIntInvocation | ParamIntInvocation | NoiseInvocation | TextToLatentsInvocation | LatentsToImageInvocation | ResizeLatentsInvocation | ScaleLatentsInvocation | ImageToLatentsInvocation | CvInpaintInvocation | RangeInvocation | RangeOfSizeInvocation | RandomRangeInvocation | UpscaleInvocation | RestoreFaceInvocation | TextToImageInvocation | InfillColorInvocation | InfillTileInvocation | InfillPatchMatchInvocation | GraphInvocation | IterateInvocation | CollectInvocation | LatentsToLatentsInvocation | ImageToImageInvocation | InpaintInvocation), - }): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/api/v1/sessions/{session_id}/nodes/{node_path}', - path: { - 'session_id': sessionId, - 'node_path': nodePath, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Invalid node or link`, - 404: `Session not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Delete Node - * Deletes a node in the graph and removes all linked edges - * @returns GraphExecutionState Successful Response - * @throws ApiError - */ - public static deleteNode({ - sessionId, - nodePath, - }: { - /** - * The id of the session - */ - sessionId: string, - /** - * The path to the node to delete - */ - nodePath: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/sessions/{session_id}/nodes/{node_path}', - path: { - 'session_id': sessionId, - 'node_path': nodePath, - }, - errors: { - 400: `Invalid node or link`, - 404: `Session not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Add Edge - * Adds an edge to the graph - * @returns GraphExecutionState Successful Response - * @throws ApiError - */ - public static addEdge({ - sessionId, - requestBody, - }: { - /** - * The id of the session - */ - sessionId: string, - requestBody: Edge, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/sessions/{session_id}/edges', - path: { - 'session_id': sessionId, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Invalid node or link`, - 404: `Session not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Delete Edge - * Deletes an edge from the graph - * @returns GraphExecutionState Successful Response - * @throws ApiError - */ - public static deleteEdge({ - sessionId, - fromNodeId, - fromField, - toNodeId, - toField, - }: { - /** - * The id of the session - */ - sessionId: string, - /** - * The id of the node the edge is coming from - */ - fromNodeId: string, - /** - * The field of the node the edge is coming from - */ - fromField: string, - /** - * The id of the node the edge is going to - */ - toNodeId: string, - /** - * The field of the node the edge is going to - */ - toField: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/sessions/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}', - path: { - 'session_id': sessionId, - 'from_node_id': fromNodeId, - 'from_field': fromField, - 'to_node_id': toNodeId, - 'to_field': toField, - }, - errors: { - 400: `Invalid node or link`, - 404: `Session not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Invoke Session - * Invokes a session - * @returns any Successful Response - * @throws ApiError - */ - public static invokeSession({ - sessionId, - all = false, - }: { - /** - * The id of the session to invoke - */ - sessionId: string, - /** - * Whether or not to invoke all remaining invocations - */ - all?: boolean, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/api/v1/sessions/{session_id}/invoke', - path: { - 'session_id': sessionId, - }, - query: { - 'all': all, - }, - errors: { - 400: `The session has no invocations ready to invoke`, - 404: `Session not found`, - 422: `Validation Error`, - }, - }); - } - - /** - * Cancel Session Invoke - * Invokes a session - * @returns any Successful Response - * @throws ApiError - */ - public static cancelSessionInvoke({ - sessionId, - }: { - /** - * The id of the session to cancel - */ - sessionId: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/sessions/{session_id}/invoke', - path: { - 'session_id': sessionId, - }, - errors: { - 422: `Validation Error`, - }, - }); - } - -} diff --git a/invokeai/frontend/web/src/services/api/thunks/image.ts b/invokeai/frontend/web/src/services/api/thunks/image.ts new file mode 100644 index 0000000000..a8b3dec5a7 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/thunks/image.ts @@ -0,0 +1,326 @@ +import queryString from 'query-string'; +import { createAppAsyncThunk } from 'app/store/storeUtils'; +import { selectImagesAll } from 'features/gallery/store/imagesSlice'; +import { size } from 'lodash-es'; +import { paths } from 'services/api/schema'; +import { $client } from 'services/api/client'; + +type GetImageUrlsArg = + paths['/api/v1/images/{image_name}/urls']['get']['parameters']['path']; + +type GetImageUrlsResponse = + paths['/api/v1/images/{image_name}/urls']['get']['responses']['200']['content']['application/json']; + +type GetImageUrlsThunkConfig = { + rejectValue: { + arg: GetImageUrlsArg; + error: unknown; + }; +}; +/** + * Thunk to get image URLs + */ +export const imageUrlsReceived = createAppAsyncThunk< + GetImageUrlsResponse, + GetImageUrlsArg, + GetImageUrlsThunkConfig +>('api/imageUrlsReceived', async (arg, { rejectWithValue }) => { + const { image_name } = arg; + const { get } = $client.get(); + const { data, error, response } = await get( + '/api/v1/images/{image_name}/urls', + { + params: { + path: { + image_name, + }, + }, + } + ); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); + +type GetImageMetadataArg = + paths['/api/v1/images/{image_name}/metadata']['get']['parameters']['path']; + +type GetImageMetadataResponse = + paths['/api/v1/images/{image_name}/metadata']['get']['responses']['200']['content']['application/json']; + +type GetImageMetadataThunkConfig = { + rejectValue: { + arg: GetImageMetadataArg; + error: unknown; + }; +}; + +export const imageMetadataReceived = createAppAsyncThunk< + GetImageMetadataResponse, + GetImageMetadataArg, + GetImageMetadataThunkConfig +>('api/imageMetadataReceived', async (arg, { rejectWithValue }) => { + const { image_name } = arg; + const { get } = $client.get(); + const { data, error, response } = await get( + '/api/v1/images/{image_name}/metadata', + { + params: { + path: { image_name }, + }, + } + ); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); + +type ControlNetAction = { + type: 'SET_CONTROLNET_IMAGE'; + controlNetId: string; +}; + +type InitialImageAction = { + type: 'SET_INITIAL_IMAGE'; +}; + +type NodesAction = { + type: 'SET_NODES_IMAGE'; + nodeId: string; + fieldName: string; +}; + +type CanvasInitialImageAction = { + type: 'SET_CANVAS_INITIAL_IMAGE'; +}; + +type CanvasMergedAction = { + type: 'TOAST_CANVAS_MERGED'; +}; + +type CanvasSavedToGalleryAction = { + type: 'TOAST_CANVAS_SAVED_TO_GALLERY'; +}; + +type UploadedToastAction = { + type: 'TOAST_UPLOADED'; +}; + +export type PostUploadAction = + | ControlNetAction + | InitialImageAction + | NodesAction + | CanvasInitialImageAction + | CanvasMergedAction + | CanvasSavedToGalleryAction + | UploadedToastAction; + +type UploadImageArg = + paths['/api/v1/images/']['post']['parameters']['query'] & { + file: File; + // file: paths['/api/v1/images/']['post']['requestBody']['content']['multipart/form-data']['file']; + postUploadAction?: PostUploadAction; + }; + +type UploadImageResponse = + paths['/api/v1/images/']['post']['responses']['201']['content']['application/json']; + +type UploadImageThunkConfig = { + rejectValue: { + arg: UploadImageArg; + error: unknown; + }; +}; +/** + * `ImagesService.uploadImage()` thunk + */ +export const imageUploaded = createAppAsyncThunk< + UploadImageResponse, + UploadImageArg, + UploadImageThunkConfig +>('api/imageUploaded', async (arg, { rejectWithValue }) => { + const { + postUploadAction, + file, + image_category, + is_intermediate, + session_id, + } = arg; + const { post } = $client.get(); + const formData = new FormData(); + formData.append('file', file); + const { data, error, response } = await post('/api/v1/images/', { + params: { + query: { + image_category, + is_intermediate, + session_id, + }, + }, + // TODO: Proper handling of `multipart/form-data` is coming soon, will fix type issues + // https://github.com/drwpow/openapi-typescript/issues/1123 + // @ts-ignore + body: formData, + }); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); + +type DeleteImageArg = + paths['/api/v1/images/{image_name}']['delete']['parameters']['path']; + +type DeleteImageResponse = + paths['/api/v1/images/{image_name}']['delete']['responses']['200']['content']['application/json']; + +type DeleteImageThunkConfig = { + rejectValue: { + arg: DeleteImageArg; + error: unknown; + }; +}; +/** + * `ImagesService.deleteImage()` thunk + */ +export const imageDeleted = createAppAsyncThunk< + DeleteImageResponse, + DeleteImageArg, + DeleteImageThunkConfig +>('api/imageDeleted', async (arg, { rejectWithValue }) => { + const { image_name } = arg; + const { del } = $client.get(); + const { data, error, response } = await del('/api/v1/images/{image_name}', { + params: { + path: { + image_name, + }, + }, + }); + + if (error) { + return rejectWithValue({ arg, error }); + } +}); + +type UpdateImageArg = + paths['/api/v1/images/{image_name}']['patch']['requestBody']['content']['application/json'] & + paths['/api/v1/images/{image_name}']['patch']['parameters']['path']; + +type UpdateImageResponse = + paths['/api/v1/images/{image_name}']['patch']['responses']['200']['content']['application/json']; + +type UpdateImageThunkConfig = { + rejectValue: { + arg: UpdateImageArg; + error: unknown; + }; +}; +/** + * `ImagesService.updateImage()` thunk + */ +export const imageUpdated = createAppAsyncThunk< + UpdateImageResponse, + UpdateImageArg, + UpdateImageThunkConfig +>('api/imageUpdated', async (arg, { rejectWithValue }) => { + const { image_name, image_category, is_intermediate, session_id } = arg; + const { patch } = $client.get(); + const { data, error, response } = await patch('/api/v1/images/{image_name}', { + params: { + path: { + image_name, + }, + }, + body: { + image_category, + is_intermediate, + session_id, + }, + }); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); + +export const IMAGES_PER_PAGE = 20; + +const DEFAULT_IMAGES_LISTED_ARG = { + limit: IMAGES_PER_PAGE, +}; + +type ListImagesArg = NonNullable< + paths['/api/v1/images/']['get']['parameters']['query'] +>; + +type ListImagesResponse = + paths['/api/v1/images/']['get']['responses']['200']['content']['application/json']; + +type ListImagesThunkConfig = { + rejectValue: { + arg: ListImagesArg; + error: unknown; + }; +}; +/** + * `ImagesService.listImagesWithMetadata()` thunk + */ +export const receivedPageOfImages = createAppAsyncThunk< + ListImagesResponse, + ListImagesArg, + ListImagesThunkConfig +>('api/receivedPageOfImages', async (arg, { getState, rejectWithValue }) => { + const { get } = $client.get(); + + const state = getState(); + const { categories } = state.images; + const { selectedBoardId } = state.boards; + + const images = selectImagesAll(state).filter((i) => { + const isInCategory = categories.includes(i.image_category); + const isInSelectedBoard = selectedBoardId + ? i.board_id === selectedBoardId + : true; + return isInCategory && isInSelectedBoard; + }); + + let query: ListImagesArg = {}; + + if (size(arg)) { + query = { + ...DEFAULT_IMAGES_LISTED_ARG, + offset: images.length, + ...arg, + }; + } else { + query = { + ...DEFAULT_IMAGES_LISTED_ARG, + categories, + offset: images.length, + }; + } + + const { data, error, response } = await get('/api/v1/images/', { + params: { + query, + }, + querySerializer: (q) => queryString.stringify(q, { arrayFormat: 'none' }), + }); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); diff --git a/invokeai/frontend/web/src/services/api/thunks/schema.ts b/invokeai/frontend/web/src/services/api/thunks/schema.ts new file mode 100644 index 0000000000..86d9f06eb4 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/thunks/schema.ts @@ -0,0 +1,44 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { log } from 'app/logging/useLogger'; + +const schemaLog = log.child({ namespace: 'schema' }); + +function getCircularReplacer() { + const ancestors: Record[] = []; + return function (key: string, value: any) { + if (typeof value !== 'object' || value === null) { + return value; + } + // `this` is the object that value is contained in, + // i.e., its direct parent. + // @ts-ignore + while (ancestors.length > 0 && ancestors.at(-1) !== this) { + ancestors.pop(); + } + if (ancestors.includes(value)) { + return '[Circular]'; + } + ancestors.push(value); + return value; + }; +} + +export const receivedOpenAPISchema = createAsyncThunk( + 'nodes/receivedOpenAPISchema', + async (_, { dispatch, rejectWithValue }) => { + try { + const response = await fetch(`openapi.json`); + const openAPISchema = await response.json(); + + schemaLog.info({ openAPISchema }, 'Received OpenAPI schema'); + + const schemaJSON = JSON.parse( + JSON.stringify(openAPISchema, getCircularReplacer()) + ); + + return schemaJSON; + } catch (error) { + return rejectWithValue({ error }); + } + } +); diff --git a/invokeai/frontend/web/src/services/api/thunks/session.ts b/invokeai/frontend/web/src/services/api/thunks/session.ts new file mode 100644 index 0000000000..ac2b0a6c2a --- /dev/null +++ b/invokeai/frontend/web/src/services/api/thunks/session.ts @@ -0,0 +1,168 @@ +import { createAppAsyncThunk } from 'app/store/storeUtils'; +import { log } from 'app/logging/useLogger'; +import { isObject } from 'lodash-es'; +import { isAnyOf } from '@reduxjs/toolkit'; +import { paths } from 'services/api/schema'; +import { $client } from 'services/api/client'; +import { O } from 'ts-toolbelt'; + +const sessionLog = log.child({ namespace: 'session' }); + +type CreateSessionArg = { + graph: NonNullable< + paths['/api/v1/sessions/']['post']['requestBody'] + >['content']['application/json']; +}; + +type CreateSessionResponse = O.Required< + NonNullable< + paths['/api/v1/sessions/']['post']['requestBody'] + >['content']['application/json'], + 'id' +>; + +type CreateSessionThunkConfig = { + rejectValue: { arg: CreateSessionArg; error: unknown }; +}; + +/** + * `SessionsService.createSession()` thunk + */ +export const sessionCreated = createAppAsyncThunk< + CreateSessionResponse, + CreateSessionArg, + CreateSessionThunkConfig +>('api/sessionCreated', async (arg, { rejectWithValue }) => { + const { graph } = arg; + const { post } = $client.get(); + const { data, error, response } = await post('/api/v1/sessions/', { + body: graph, + }); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); + +type InvokedSessionArg = { + session_id: paths['/api/v1/sessions/{session_id}/invoke']['put']['parameters']['path']['session_id']; +}; + +type InvokedSessionResponse = + paths['/api/v1/sessions/{session_id}/invoke']['put']['responses']['200']['content']['application/json']; + +type InvokedSessionThunkConfig = { + rejectValue: { + arg: InvokedSessionArg; + error: unknown; + }; +}; + +const isErrorWithStatus = (error: unknown): error is { status: number } => + isObject(error) && 'status' in error; + +/** + * `SessionsService.invokeSession()` thunk + */ +export const sessionInvoked = createAppAsyncThunk< + InvokedSessionResponse, + InvokedSessionArg, + InvokedSessionThunkConfig +>('api/sessionInvoked', async (arg, { rejectWithValue }) => { + const { session_id } = arg; + const { put } = $client.get(); + const { data, error, response } = await put( + '/api/v1/sessions/{session_id}/invoke', + { + params: { query: { all: true }, path: { session_id } }, + } + ); + + if (error) { + if (isErrorWithStatus(error) && error.status === 403) { + return rejectWithValue({ arg, error: (error as any).body.detail }); + } + return rejectWithValue({ arg, error }); + } +}); + +type CancelSessionArg = + paths['/api/v1/sessions/{session_id}/invoke']['delete']['parameters']['path']; + +type CancelSessionResponse = + paths['/api/v1/sessions/{session_id}/invoke']['delete']['responses']['200']['content']['application/json']; + +type CancelSessionThunkConfig = { + rejectValue: { + arg: CancelSessionArg; + error: unknown; + }; +}; + +/** + * `SessionsService.cancelSession()` thunk + */ +export const sessionCanceled = createAppAsyncThunk< + CancelSessionResponse, + CancelSessionArg, + CancelSessionThunkConfig +>('api/sessionCanceled', async (arg, { rejectWithValue }) => { + const { session_id } = arg; + const { del } = $client.get(); + const { data, error, response } = await del( + '/api/v1/sessions/{session_id}/invoke', + { + params: { + path: { session_id }, + }, + } + ); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); + +type ListSessionsArg = { + params: paths['/api/v1/sessions/']['get']['parameters']; +}; + +type ListSessionsResponse = + paths['/api/v1/sessions/']['get']['responses']['200']['content']['application/json']; + +type ListSessionsThunkConfig = { + rejectValue: { + arg: ListSessionsArg; + error: unknown; + }; +}; + +/** + * `SessionsService.listSessions()` thunk + */ +export const listedSessions = createAppAsyncThunk< + ListSessionsResponse, + ListSessionsArg, + ListSessionsThunkConfig +>('api/listSessions', async (arg, { rejectWithValue }) => { + const { params } = arg; + const { get } = $client.get(); + const { data, error, response } = await get('/api/v1/sessions/', { + params, + }); + + if (error) { + return rejectWithValue({ arg, error }); + } + + return data; +}); + +export const isAnySessionRejected = isAnyOf( + sessionCreated.rejected, + sessionInvoked.rejected +); diff --git a/invokeai/frontend/web/src/services/api/types.d.ts b/invokeai/frontend/web/src/services/api/types.d.ts new file mode 100644 index 0000000000..2a2f90f434 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/types.d.ts @@ -0,0 +1,92 @@ +import { O } from 'ts-toolbelt'; +import { components } from './schema'; + +type schemas = components['schemas']; + +/** + * Extracts the schema type from the schema. + */ +type S = components['schemas'][T]; + +/** + * Extracts the node type from the schema. + * Also flags the `type` property as required. + */ +type N = O.Required< + components['schemas'][T], + 'type' +>; + +// Images +export type ImageDTO = S<'ImageDTO'>; +export type BoardDTO = S<'BoardDTO'>; +export type BoardChanges = S<'BoardChanges'>; +export type ImageChanges = S<'ImageRecordChanges'>; +export type ImageCategory = S<'ImageCategory'>; +export type ResourceOrigin = S<'ResourceOrigin'>; +export type ImageField = S<'ImageField'>; +export type OffsetPaginatedResults_BoardDTO_ = + S<'OffsetPaginatedResults_BoardDTO_'>; +export type OffsetPaginatedResults_ImageDTO_ = + S<'OffsetPaginatedResults_ImageDTO_'>; + +// Models +export type ModelType = S<'ModelType'>; +export type BaseModelType = S<'BaseModelType'>; +export type PipelineModelField = S<'PipelineModelField'>; +export type ModelsList = S<'ModelsList'>; + +// Graphs +export type Graph = S<'Graph'>; +export type Edge = S<'Edge'>; +export type GraphExecutionState = S<'GraphExecutionState'>; + +// General nodes +export type CollectInvocation = N<'CollectInvocation'>; +export type IterateInvocation = N<'IterateInvocation'>; +export type RangeInvocation = N<'RangeInvocation'>; +export type RandomRangeInvocation = N<'RandomRangeInvocation'>; +export type RangeOfSizeInvocation = N<'RangeOfSizeInvocation'>; +export type InpaintInvocation = N<'InpaintInvocation'>; +export type ImageResizeInvocation = N<'ImageResizeInvocation'>; +export type RandomIntInvocation = N<'RandomIntInvocation'>; +export type CompelInvocation = N<'CompelInvocation'>; +export type DynamicPromptInvocation = N<'DynamicPromptInvocation'>; +export type NoiseInvocation = N<'NoiseInvocation'>; +export type TextToLatentsInvocation = N<'TextToLatentsInvocation'>; +export type LatentsToLatentsInvocation = N<'LatentsToLatentsInvocation'>; +export type ImageToLatentsInvocation = N<'ImageToLatentsInvocation'>; +export type LatentsToImageInvocation = N<'LatentsToImageInvocation'>; +export type PipelineModelLoaderInvocation = N<'PipelineModelLoaderInvocation'>; + +// ControlNet Nodes +export type ControlNetInvocation = N<'ControlNetInvocation'>; +export type CannyImageProcessorInvocation = N<'CannyImageProcessorInvocation'>; +export type ContentShuffleImageProcessorInvocation = + N<'ContentShuffleImageProcessorInvocation'>; +export type HedImageProcessorInvocation = N<'HedImageProcessorInvocation'>; +export type LineartAnimeImageProcessorInvocation = + N<'LineartAnimeImageProcessorInvocation'>; +export type LineartImageProcessorInvocation = + N<'LineartImageProcessorInvocation'>; +export type MediapipeFaceProcessorInvocation = + N<'MediapipeFaceProcessorInvocation'>; +export type MidasDepthImageProcessorInvocation = + N<'MidasDepthImageProcessorInvocation'>; +export type MlsdImageProcessorInvocation = N<'MlsdImageProcessorInvocation'>; +export type NormalbaeImageProcessorInvocation = + N<'NormalbaeImageProcessorInvocation'>; +export type OpenposeImageProcessorInvocation = + N<'OpenposeImageProcessorInvocation'>; +export type PidiImageProcessorInvocation = N<'PidiImageProcessorInvocation'>; +export type ZoeDepthImageProcessorInvocation = + N<'ZoeDepthImageProcessorInvocation'>; + +// Node Outputs +export type ImageOutput = S<'ImageOutput'>; +export type MaskOutput = S<'MaskOutput'>; +export type PromptOutput = S<'PromptOutput'>; +export type IterateInvocationOutput = S<'IterateInvocationOutput'>; +export type CollectInvocationOutput = S<'CollectInvocationOutput'>; +export type LatentsOutput = S<'LatentsOutput'>; +export type GraphInvocationOutput = S<'GraphInvocationOutput'>; diff --git a/invokeai/frontend/web/src/services/events/actions.ts b/invokeai/frontend/web/src/services/events/actions.ts index 76bffeaa49..ed154b9cd8 100644 --- a/invokeai/frontend/web/src/services/events/actions.ts +++ b/invokeai/frontend/web/src/services/events/actions.ts @@ -12,46 +12,153 @@ type BaseSocketPayload = { timestamp: string; }; -// Create actions for each socket event +// Create actions for each socket // Middleware and redux can then respond to them as needed +/** + * Socket.IO Connected + * + * Do not use. Only for use in middleware. + */ export const socketConnected = createAction( 'socket/socketConnected' ); +/** + * App-level Socket.IO Connected + */ +export const appSocketConnected = createAction( + 'socket/appSocketConnected' +); + +/** + * Socket.IO Disconnect + * + * Do not use. Only for use in middleware. + */ export const socketDisconnected = createAction( 'socket/socketDisconnected' ); +/** + * App-level Socket.IO Disconnected + */ +export const appSocketDisconnected = createAction( + 'socket/appSocketDisconnected' +); + +/** + * Socket.IO Subscribed + * + * Do not use. Only for use in middleware. + */ export const socketSubscribed = createAction< - BaseSocketPayload & { sessionId: string } + BaseSocketPayload & { sessionId: string; boardId: string | undefined } >('socket/socketSubscribed'); +/** + * App-level Socket.IO Subscribed + */ +export const appSocketSubscribed = createAction< + BaseSocketPayload & { sessionId: string; boardId: string | undefined } +>('socket/appSocketSubscribed'); + +/** + * Socket.IO Unsubscribed + * + * Do not use. Only for use in middleware. + */ export const socketUnsubscribed = createAction< BaseSocketPayload & { sessionId: string } >('socket/socketUnsubscribed'); -export const invocationStarted = createAction< - BaseSocketPayload & { data: InvocationStartedEvent } ->('socket/invocationStarted'); +/** + * App-level Socket.IO Unsubscribed + */ +export const appSocketUnsubscribed = createAction< + BaseSocketPayload & { sessionId: string } +>('socket/appSocketUnsubscribed'); -export const invocationComplete = createAction< +/** + * Socket.IO Invocation Started + * + * Do not use. Only for use in middleware. + */ +export const socketInvocationStarted = createAction< + BaseSocketPayload & { data: InvocationStartedEvent } +>('socket/socketInvocationStarted'); + +/** + * App-level Socket.IO Invocation Started + */ +export const appSocketInvocationStarted = createAction< + BaseSocketPayload & { data: InvocationStartedEvent } +>('socket/appSocketInvocationStarted'); + +/** + * Socket.IO Invocation Complete + * + * Do not use. Only for use in middleware. + */ +export const socketInvocationComplete = createAction< BaseSocketPayload & { data: InvocationCompleteEvent; } ->('socket/invocationComplete'); +>('socket/socketInvocationComplete'); -export const invocationError = createAction< +/** + * App-level Socket.IO Invocation Complete + */ +export const appSocketInvocationComplete = createAction< + BaseSocketPayload & { + data: InvocationCompleteEvent; + } +>('socket/appSocketInvocationComplete'); + +/** + * Socket.IO Invocation Error + * + * Do not use. Only for use in middleware. + */ +export const socketInvocationError = createAction< BaseSocketPayload & { data: InvocationErrorEvent } ->('socket/invocationError'); +>('socket/socketInvocationError'); -export const graphExecutionStateComplete = createAction< +/** + * App-level Socket.IO Invocation Error + */ +export const appSocketInvocationError = createAction< + BaseSocketPayload & { data: InvocationErrorEvent } +>('socket/appSocketInvocationError'); + +/** + * Socket.IO Graph Execution State Complete + * + * Do not use. Only for use in middleware. + */ +export const socketGraphExecutionStateComplete = createAction< BaseSocketPayload & { data: GraphExecutionStateCompleteEvent } ->('socket/graphExecutionStateComplete'); +>('socket/socketGraphExecutionStateComplete'); -export const generatorProgress = createAction< +/** + * App-level Socket.IO Graph Execution State Complete + */ +export const appSocketGraphExecutionStateComplete = createAction< + BaseSocketPayload & { data: GraphExecutionStateCompleteEvent } +>('socket/appSocketGraphExecutionStateComplete'); + +/** + * Socket.IO Generator Progress + * + * Do not use. Only for use in middleware. + */ +export const socketGeneratorProgress = createAction< BaseSocketPayload & { data: GeneratorProgressEvent } ->('socket/generatorProgress'); +>('socket/socketGeneratorProgress'); -// dispatch this when we need to fully reset the socket connection -export const socketReset = createAction('socket/socketReset'); +/** + * App-level Socket.IO Generator Progress + */ +export const appSocketGeneratorProgress = createAction< + BaseSocketPayload & { data: GeneratorProgressEvent } +>('socket/appSocketGeneratorProgress'); diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index f1eb844f2c..85641b88a0 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -8,10 +8,11 @@ import { import { socketSubscribed, socketUnsubscribed } from './actions'; import { AppThunkDispatch, RootState } from 'app/store/store'; import { getTimestamp } from 'common/util/getTimestamp'; -import { sessionCreated } from 'services/thunks/session'; -import { OpenAPI } from 'services/api'; +import { sessionCreated } from 'services/api/thunks/session'; +// import { OpenAPI } from 'services/api/types'; import { setEventListeners } from 'services/events/util/setEventListeners'; import { log } from 'app/logging/useLogger'; +import { $authToken, $baseUrl } from 'services/api/client'; const socketioLog = log.child({ namespace: 'socketio' }); @@ -28,14 +29,16 @@ export const socketMiddleware = () => { // if building in package mode, replace socket url with open api base url minus the http protocol if (['nodes', 'package'].includes(import.meta.env.MODE)) { - if (OpenAPI.BASE) { + const baseUrl = $baseUrl.get(); + if (baseUrl) { //eslint-disable-next-line - socketUrl = OpenAPI.BASE.replace(/^https?\:\/\//i, ''); + socketUrl = baseUrl.replace(/^https?\:\/\//i, ''); } - if (OpenAPI.TOKEN) { + const authToken = $authToken.get(); + if (authToken) { // TODO: handle providing jwt to socket.io - socketOptions.auth = { token: OpenAPI.TOKEN }; + socketOptions.auth = { token: authToken }; } socketOptions.transports = ['websocket', 'polling']; @@ -85,6 +88,7 @@ export const socketMiddleware = () => { socketSubscribed({ sessionId: sessionId, timestamp: getTimestamp(), + boardId: getState().boards.selectedBoardId, }) ); } diff --git a/invokeai/frontend/web/src/services/events/types.ts b/invokeai/frontend/web/src/services/events/types.ts index 2577b7fe92..f589fdd6cc 100644 --- a/invokeai/frontend/web/src/services/events/types.ts +++ b/invokeai/frontend/web/src/services/events/types.ts @@ -1,4 +1,5 @@ -import { Graph, GraphExecutionState, InvokeAIMetadata } from '../api'; +import { O } from 'ts-toolbelt'; +import { Graph, GraphExecutionState } from '../api/types'; /** * A progress image, we get one for each step in the generation @@ -9,18 +10,19 @@ export type ProgressImage = { height: number; }; -export type AnyInvocationType = NonNullable< - NonNullable[string]['type'] ->; +export type AnyInvocationType = O.Required< + NonNullable[string]>, + 'type' +>['type']; -export type AnyInvocation = NonNullable[string]; +export type AnyInvocation = NonNullable[string]>; -export type AnyResult = GraphExecutionState['results'][string]; +export type AnyResult = NonNullable; export type BaseNode = { id: string; type: string; - [key: string]: NonNullable[string]; + [key: string]: AnyInvocation[keyof AnyInvocation]; }; /** diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index 5262b26d1e..62b5864185 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -3,11 +3,11 @@ import { AppDispatch, RootState } from 'app/store/store'; import { getTimestamp } from 'common/util/getTimestamp'; import { Socket } from 'socket.io-client'; import { - generatorProgress, - graphExecutionStateComplete, - invocationComplete, - invocationError, - invocationStarted, + socketGeneratorProgress, + socketGraphExecutionStateComplete, + socketInvocationComplete, + socketInvocationError, + socketInvocationStarted, socketConnected, socketDisconnected, socketSubscribed, @@ -44,6 +44,7 @@ export const setEventListeners = (arg: SetEventListenersArg) => { socketSubscribed({ sessionId, timestamp: getTimestamp(), + boardId: getState().boards.selectedBoardId, }) ); } @@ -77,21 +78,21 @@ export const setEventListeners = (arg: SetEventListenersArg) => { * Invocation started */ socket.on('invocation_started', (data) => { - dispatch(invocationStarted({ data, timestamp: getTimestamp() })); + dispatch(socketInvocationStarted({ data, timestamp: getTimestamp() })); }); /** * Generator progress */ socket.on('generator_progress', (data) => { - dispatch(generatorProgress({ data, timestamp: getTimestamp() })); + dispatch(socketGeneratorProgress({ data, timestamp: getTimestamp() })); }); /** * Invocation error */ socket.on('invocation_error', (data) => { - dispatch(invocationError({ data, timestamp: getTimestamp() })); + dispatch(socketInvocationError({ data, timestamp: getTimestamp() })); }); /** @@ -99,7 +100,7 @@ export const setEventListeners = (arg: SetEventListenersArg) => { */ socket.on('invocation_complete', (data) => { dispatch( - invocationComplete({ + socketInvocationComplete({ data, timestamp: getTimestamp(), }) @@ -110,6 +111,11 @@ export const setEventListeners = (arg: SetEventListenersArg) => { * Graph complete */ socket.on('graph_execution_state_complete', (data) => { - dispatch(graphExecutionStateComplete({ data, timestamp: getTimestamp() })); + dispatch( + socketGraphExecutionStateComplete({ + data, + timestamp: getTimestamp(), + }) + ); }); }; diff --git a/invokeai/frontend/web/src/services/fixtures/openapi.json b/invokeai/frontend/web/src/services/fixtures/openapi.json deleted file mode 100644 index fb2cddf3e7..0000000000 --- a/invokeai/frontend/web/src/services/fixtures/openapi.json +++ /dev/null @@ -1 +0,0 @@ -{"openapi":"3.0.2","info":{"title":"Invoke AI","description":"An API for invoking AI image operations","version":"1.0.0"},"paths":{"/api/v1/sessions/":{"get":{"tags":["sessions"],"summary":"List Sessions","description":"Gets a list of sessions, optionally searching","operationId":"list_sessions","parameters":[{"description":"The page of results to get","required":false,"schema":{"title":"Page","type":"integer","description":"The page of results to get","default":0},"name":"page","in":"query"},{"description":"The number of results per page","required":false,"schema":{"title":"Per Page","type":"integer","description":"The number of results per page","default":10},"name":"per_page","in":"query"},{"description":"The query string to search for","required":false,"schema":{"title":"Query","type":"string","description":"The query string to search for","default":""},"name":"query","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResults_GraphExecutionState_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["sessions"],"summary":"Create Session","description":"Creates a new session, optionally initializing it with an invocation graph","operationId":"create_session","requestBody":{"content":{"application/json":{"schema":{"title":"Graph","allOf":[{"$ref":"#/components/schemas/Graph"}],"description":"The graph to initialize the session with"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphExecutionState"}}}},"400":{"description":"Invalid json"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sessions/{session_id}":{"get":{"tags":["sessions"],"summary":"Get Session","description":"Gets a session","operationId":"get_session","parameters":[{"description":"The id of the session to get","required":true,"schema":{"title":"Session Id","type":"string","description":"The id of the session to get"},"name":"session_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphExecutionState"}}}},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sessions/{session_id}/nodes":{"post":{"tags":["sessions"],"summary":"Add Node","description":"Adds a node to the graph","operationId":"add_node","parameters":[{"description":"The id of the session","required":true,"schema":{"title":"Session Id","type":"string","description":"The id of the session"},"name":"session_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Node","anyOf":[{"$ref":"#/components/schemas/LoadImageInvocation"},{"$ref":"#/components/schemas/ShowImageInvocation"},{"$ref":"#/components/schemas/CropImageInvocation"},{"$ref":"#/components/schemas/PasteImageInvocation"},{"$ref":"#/components/schemas/MaskFromAlphaInvocation"},{"$ref":"#/components/schemas/BlurInvocation"},{"$ref":"#/components/schemas/LerpInvocation"},{"$ref":"#/components/schemas/InverseLerpInvocation"},{"$ref":"#/components/schemas/CvInpaintInvocation"},{"$ref":"#/components/schemas/UpscaleInvocation"},{"$ref":"#/components/schemas/RestoreFaceInvocation"},{"$ref":"#/components/schemas/TextToImageInvocation"},{"$ref":"#/components/schemas/GraphInvocation"},{"$ref":"#/components/schemas/IterateInvocation"},{"$ref":"#/components/schemas/CollectInvocation"},{"$ref":"#/components/schemas/ImageToImageInvocation"},{"$ref":"#/components/schemas/InpaintInvocation"}],"description":"The node to add"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response 200 Add Node","type":"string"}}}},"400":{"description":"Invalid node or link"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sessions/{session_id}/nodes/{node_path}":{"put":{"tags":["sessions"],"summary":"Update Node","description":"Updates a node in the graph and removes all linked edges","operationId":"update_node","parameters":[{"description":"The id of the session","required":true,"schema":{"title":"Session Id","type":"string","description":"The id of the session"},"name":"session_id","in":"path"},{"description":"The path to the node in the graph","required":true,"schema":{"title":"Node Path","type":"string","description":"The path to the node in the graph"},"name":"node_path","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Node","anyOf":[{"$ref":"#/components/schemas/LoadImageInvocation"},{"$ref":"#/components/schemas/ShowImageInvocation"},{"$ref":"#/components/schemas/CropImageInvocation"},{"$ref":"#/components/schemas/PasteImageInvocation"},{"$ref":"#/components/schemas/MaskFromAlphaInvocation"},{"$ref":"#/components/schemas/BlurInvocation"},{"$ref":"#/components/schemas/LerpInvocation"},{"$ref":"#/components/schemas/InverseLerpInvocation"},{"$ref":"#/components/schemas/CvInpaintInvocation"},{"$ref":"#/components/schemas/UpscaleInvocation"},{"$ref":"#/components/schemas/RestoreFaceInvocation"},{"$ref":"#/components/schemas/TextToImageInvocation"},{"$ref":"#/components/schemas/GraphInvocation"},{"$ref":"#/components/schemas/IterateInvocation"},{"$ref":"#/components/schemas/CollectInvocation"},{"$ref":"#/components/schemas/ImageToImageInvocation"},{"$ref":"#/components/schemas/InpaintInvocation"}],"description":"The new node"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphExecutionState"}}}},"400":{"description":"Invalid node or link"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["sessions"],"summary":"Delete Node","description":"Deletes a node in the graph and removes all linked edges","operationId":"delete_node","parameters":[{"description":"The id of the session","required":true,"schema":{"title":"Session Id","type":"string","description":"The id of the session"},"name":"session_id","in":"path"},{"description":"The path to the node to delete","required":true,"schema":{"title":"Node Path","type":"string","description":"The path to the node to delete"},"name":"node_path","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphExecutionState"}}}},"400":{"description":"Invalid node or link"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sessions/{session_id}/edges":{"post":{"tags":["sessions"],"summary":"Add Edge","description":"Adds an edge to the graph","operationId":"add_edge","parameters":[{"description":"The id of the session","required":true,"schema":{"title":"Session Id","type":"string","description":"The id of the session"},"name":"session_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"title":"Edge","allOf":[{"$ref":"#/components/schemas/Edge"}],"description":"The edge to add"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphExecutionState"}}}},"400":{"description":"Invalid node or link"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sessions/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}":{"delete":{"tags":["sessions"],"summary":"Delete Edge","description":"Deletes an edge from the graph","operationId":"delete_edge","parameters":[{"description":"The id of the session","required":true,"schema":{"title":"Session Id","type":"string","description":"The id of the session"},"name":"session_id","in":"path"},{"description":"The id of the node the edge is coming from","required":true,"schema":{"title":"From Node Id","type":"string","description":"The id of the node the edge is coming from"},"name":"from_node_id","in":"path"},{"description":"The field of the node the edge is coming from","required":true,"schema":{"title":"From Field","type":"string","description":"The field of the node the edge is coming from"},"name":"from_field","in":"path"},{"description":"The id of the node the edge is going to","required":true,"schema":{"title":"To Node Id","type":"string","description":"The id of the node the edge is going to"},"name":"to_node_id","in":"path"},{"description":"The field of the node the edge is going to","required":true,"schema":{"title":"To Field","type":"string","description":"The field of the node the edge is going to"},"name":"to_field","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphExecutionState"}}}},"400":{"description":"Invalid node or link"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sessions/{session_id}/invoke":{"put":{"tags":["sessions"],"summary":"Invoke Session","description":"Invokes a session","operationId":"invoke_session","parameters":[{"description":"The id of the session to invoke","required":true,"schema":{"title":"Session Id","type":"string","description":"The id of the session to invoke"},"name":"session_id","in":"path"},{"description":"Whether or not to invoke all remaining invocations","required":false,"schema":{"title":"All","type":"boolean","description":"Whether or not to invoke all remaining invocations","default":false},"name":"all","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"202":{"description":"The invocation is queued"},"400":{"description":"The session has no invocations ready to invoke"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/images/{image_type}/{image_name}":{"get":{"tags":["images"],"summary":"Get Image","description":"Gets a result","operationId":"get_image","parameters":[{"description":"The type of image to get","required":true,"schema":{"allOf":[{"$ref":"#/components/schemas/ImageType"}],"description":"The type of image to get"},"name":"image_type","in":"path"},{"description":"The name of the image to get","required":true,"schema":{"title":"Image Name","type":"string","description":"The name of the image to get"},"name":"image_name","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/images/uploads/":{"post":{"tags":["images"],"summary":"Upload Image","operationId":"upload_image","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_image"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"201":{"description":"The image was uploaded successfully"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"BlurInvocation":{"title":"BlurInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["blur"],"type":"string","default":"blur"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to blur"},"radius":{"title":"Radius","minimum":0.0,"type":"number","description":"The blur radius","default":8.0},"blur_type":{"title":"Blur Type","enum":["gaussian","box"],"type":"string","description":"The type of blur","default":"gaussian"}},"description":"Blurs an image","output":{"$ref":"#/components/schemas/ImageOutput"}},"Body_upload_image":{"title":"Body_upload_image","required":["file"],"type":"object","properties":{"file":{"title":"File","type":"string","format":"binary"}}},"CollectInvocation":{"title":"CollectInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["collect"],"type":"string","default":"collect"},"item":{"title":"Item","description":"The item to collect (all inputs must be of the same type)"},"collection":{"title":"Collection","type":"array","items":{},"description":"The collection, will be provided on execution"}},"description":"Collects values into a collection","output":{"$ref":"#/components/schemas/CollectInvocationOutput"}},"CollectInvocationOutput":{"title":"CollectInvocationOutput","description":"Base class for all invocation outputs","type":"object","properties":{"type":{"title":"Type","default":"collect_output","enum":["collect_output"],"type":"string"},"collection":{"title":"Collection","description":"The collection of input items","type":"array","items":{}}},"required":["collection"]},"CropImageInvocation":{"title":"CropImageInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["crop"],"type":"string","default":"crop"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to crop"},"x":{"title":"X","type":"integer","description":"The left x coordinate of the crop rectangle","default":0},"y":{"title":"Y","type":"integer","description":"The top y coordinate of the crop rectangle","default":0},"width":{"title":"Width","exclusiveMinimum":0.0,"type":"integer","description":"The width of the crop rectangle","default":512},"height":{"title":"Height","exclusiveMinimum":0.0,"type":"integer","description":"The height of the crop rectangle","default":512}},"description":"Crops an image to a specified box. The box can be outside of the image.","output":{"$ref":"#/components/schemas/ImageOutput"}},"CvInpaintInvocation":{"title":"CvInpaintInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["cv_inpaint"],"type":"string","default":"cv_inpaint"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to inpaint"},"mask":{"title":"Mask","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The mask to use when inpainting"}},"description":"Simple inpaint using opencv.","output":{"$ref":"#/components/schemas/ImageOutput"}},"Edge":{"title":"Edge","required":["source","destination"],"type":"object","properties":{"source":{"title":"Source","allOf":[{"$ref":"#/components/schemas/EdgeConnection"}],"description":"The connection for the edge's from node and field"},"destination":{"title":"Destination","allOf":[{"$ref":"#/components/schemas/EdgeConnection"}],"description":"The connection for the edge's to node and field"}}},"EdgeConnection":{"title":"EdgeConnection","required":["node_id","field"],"type":"object","properties":{"node_id":{"title":"Node Id","type":"string","description":"The id of the node for this edge connection"},"field":{"title":"Field","type":"string","description":"The field for this connection"}}},"Graph":{"title":"Graph","type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this graph"},"nodes":{"title":"Nodes","type":"object","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/LoadImageInvocation"},{"$ref":"#/components/schemas/ShowImageInvocation"},{"$ref":"#/components/schemas/CropImageInvocation"},{"$ref":"#/components/schemas/PasteImageInvocation"},{"$ref":"#/components/schemas/MaskFromAlphaInvocation"},{"$ref":"#/components/schemas/BlurInvocation"},{"$ref":"#/components/schemas/LerpInvocation"},{"$ref":"#/components/schemas/InverseLerpInvocation"},{"$ref":"#/components/schemas/CvInpaintInvocation"},{"$ref":"#/components/schemas/UpscaleInvocation"},{"$ref":"#/components/schemas/RestoreFaceInvocation"},{"$ref":"#/components/schemas/TextToImageInvocation"},{"$ref":"#/components/schemas/GraphInvocation"},{"$ref":"#/components/schemas/IterateInvocation"},{"$ref":"#/components/schemas/CollectInvocation"},{"$ref":"#/components/schemas/ImageToImageInvocation"},{"$ref":"#/components/schemas/InpaintInvocation"}],"discriminator":{"propertyName":"type","mapping":{"load_image":"#/components/schemas/LoadImageInvocation","show_image":"#/components/schemas/ShowImageInvocation","crop":"#/components/schemas/CropImageInvocation","paste":"#/components/schemas/PasteImageInvocation","tomask":"#/components/schemas/MaskFromAlphaInvocation","blur":"#/components/schemas/BlurInvocation","lerp":"#/components/schemas/LerpInvocation","ilerp":"#/components/schemas/InverseLerpInvocation","cv_inpaint":"#/components/schemas/CvInpaintInvocation","upscale":"#/components/schemas/UpscaleInvocation","restore_face":"#/components/schemas/RestoreFaceInvocation","txt2img":"#/components/schemas/TextToImageInvocation","graph":"#/components/schemas/GraphInvocation","iterate":"#/components/schemas/IterateInvocation","collect":"#/components/schemas/CollectInvocation","img2img":"#/components/schemas/ImageToImageInvocation","inpaint":"#/components/schemas/InpaintInvocation"}}},"description":"The nodes in this graph"},"edges":{"title":"Edges","type":"array","items":{"$ref":"#/components/schemas/Edge"},"description":"The connections between nodes and their fields in this graph"}}},"GraphExecutionState":{"title":"GraphExecutionState","required":["graph"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of the execution state"},"graph":{"title":"Graph","allOf":[{"$ref":"#/components/schemas/Graph"}],"description":"The graph being executed"},"execution_graph":{"title":"Execution Graph","allOf":[{"$ref":"#/components/schemas/Graph"}],"description":"The expanded graph of activated and executed nodes"},"executed":{"title":"Executed","uniqueItems":true,"type":"array","items":{"type":"string"},"description":"The set of node ids that have been executed"},"executed_history":{"title":"Executed History","type":"array","items":{"type":"string"},"description":"The list of node ids that have been executed, in order of execution"},"results":{"title":"Results","type":"object","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/ImageOutput"},{"$ref":"#/components/schemas/MaskOutput"},{"$ref":"#/components/schemas/PromptOutput"},{"$ref":"#/components/schemas/GraphInvocationOutput"},{"$ref":"#/components/schemas/IterateInvocationOutput"},{"$ref":"#/components/schemas/CollectInvocationOutput"}],"discriminator":{"propertyName":"type","mapping":{"image":"#/components/schemas/ImageOutput","mask":"#/components/schemas/MaskOutput","prompt":"#/components/schemas/PromptOutput","graph_output":"#/components/schemas/GraphInvocationOutput","iterate_output":"#/components/schemas/IterateInvocationOutput","collect_output":"#/components/schemas/CollectInvocationOutput"}}},"description":"The results of node executions"},"errors":{"title":"Errors","type":"object","additionalProperties":{"type":"string"},"description":"Errors raised when executing nodes"},"prepared_source_mapping":{"title":"Prepared Source Mapping","type":"object","additionalProperties":{"type":"string"},"description":"The map of prepared nodes to original graph nodes"},"source_prepared_mapping":{"title":"Source Prepared Mapping","type":"object","additionalProperties":{"uniqueItems":true,"type":"array","items":{"type":"string"}},"description":"The map of original graph nodes to prepared nodes"}},"description":"Tracks the state of a graph execution"},"GraphInvocation":{"title":"GraphInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["graph"],"type":"string","default":"graph"},"graph":{"title":"Graph","allOf":[{"$ref":"#/components/schemas/Graph"}],"description":"The graph to run"}},"description":"A node to process inputs and produce outputs.\nMay use dependency injection in __init__ to receive providers.","output":{"$ref":"#/components/schemas/GraphInvocationOutput"}},"GraphInvocationOutput":{"title":"GraphInvocationOutput","description":"Base class for all invocation outputs","type":"object","properties":{"type":{"title":"Type","default":"graph_output","enum":["graph_output"],"type":"string"}}},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"ImageField":{"title":"ImageField","description":"An image field used for passing image objects between invocations","type":"object","properties":{"image_type":{"title":"Image Type","description":"The type of the image","default":"results","type":"string"},"image_name":{"title":"Image Name","description":"The name of the image","type":"string"}}},"ImageOutput":{"title":"ImageOutput","description":"Base class for invocations that output an image","type":"object","properties":{"type":{"title":"Type","default":"image","enum":["image"],"type":"string"},"image":{"title":"Image","description":"The output image","allOf":[{"$ref":"#/components/schemas/ImageField"}]}}},"ImageToImageInvocation":{"title":"ImageToImageInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["img2img"],"type":"string","default":"img2img"},"prompt":{"title":"Prompt","type":"string","description":"The prompt to generate an image from"},"seed":{"title":"Seed","maximum":4294967295.0,"minimum":-1.0,"type":"integer","description":"The seed to use (-1 for a random seed)","default":-1},"steps":{"title":"Steps","exclusiveMinimum":0.0,"type":"integer","description":"The number of steps to use to generate the image","default":10},"width":{"title":"Width","multipleOf":64.0,"exclusiveMinimum":0.0,"type":"integer","description":"The width of the resulting image","default":512},"height":{"title":"Height","multipleOf":64.0,"exclusiveMinimum":0.0,"type":"integer","description":"The height of the resulting image","default":512},"cfg_scale":{"title":"Cfg Scale","exclusiveMinimum":0.0,"type":"number","description":"The Classifier-Free Guidance, higher values may result in a result closer to the prompt","default":7.5},"sampler_name":{"title":"Sampler Name","enum":["ddim","dpmpp_2","k_dpm_2","k_dpm_2_a","k_dpmpp_2","k_euler","k_euler_a","k_heun","k_lms","plms"],"type":"string","description":"The sampler to use","default":"k_lms"},"seamless":{"title":"Seamless","type":"boolean","description":"Whether or not to generate an image that can tile without seams","default":false},"model":{"title":"Model","type":"string","description":"The model to use (currently ignored)","default":""},"progress_images":{"title":"Progress Images","type":"boolean","description":"Whether or not to produce progress images during generation","default":false},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The input image"},"strength":{"title":"Strength","maximum":1.0,"exclusiveMinimum":0.0,"type":"number","description":"The strength of the original image","default":0.75},"fit":{"title":"Fit","type":"boolean","description":"Whether or not the result should be fit to the aspect ratio of the input image","default":true}},"description":"Generates an image using img2img.","output":{"$ref":"#/components/schemas/ImageOutput"}},"ImageType":{"title":"ImageType","enum":["results","intermediates","uploads"],"type":"string","description":"An enumeration."},"InpaintInvocation":{"title":"InpaintInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["inpaint"],"type":"string","default":"inpaint"},"prompt":{"title":"Prompt","type":"string","description":"The prompt to generate an image from"},"seed":{"title":"Seed","maximum":4294967295.0,"minimum":-1.0,"type":"integer","description":"The seed to use (-1 for a random seed)","default":-1},"steps":{"title":"Steps","exclusiveMinimum":0.0,"type":"integer","description":"The number of steps to use to generate the image","default":10},"width":{"title":"Width","multipleOf":64.0,"exclusiveMinimum":0.0,"type":"integer","description":"The width of the resulting image","default":512},"height":{"title":"Height","multipleOf":64.0,"exclusiveMinimum":0.0,"type":"integer","description":"The height of the resulting image","default":512},"cfg_scale":{"title":"Cfg Scale","exclusiveMinimum":0.0,"type":"number","description":"The Classifier-Free Guidance, higher values may result in a result closer to the prompt","default":7.5},"sampler_name":{"title":"Sampler Name","enum":["ddim","dpmpp_2","k_dpm_2","k_dpm_2_a","k_dpmpp_2","k_euler","k_euler_a","k_heun","k_lms","plms"],"type":"string","description":"The sampler to use","default":"k_lms"},"seamless":{"title":"Seamless","type":"boolean","description":"Whether or not to generate an image that can tile without seams","default":false},"model":{"title":"Model","type":"string","description":"The model to use (currently ignored)","default":""},"progress_images":{"title":"Progress Images","type":"boolean","description":"Whether or not to produce progress images during generation","default":false},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The input image"},"strength":{"title":"Strength","maximum":1.0,"exclusiveMinimum":0.0,"type":"number","description":"The strength of the original image","default":0.75},"fit":{"title":"Fit","type":"boolean","description":"Whether or not the result should be fit to the aspect ratio of the input image","default":true},"mask":{"title":"Mask","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The mask"},"inpaint_replace":{"title":"Inpaint Replace","maximum":1.0,"minimum":0.0,"type":"number","description":"The amount by which to replace masked areas with latent noise","default":0.0}},"description":"Generates an image using inpaint.","output":{"$ref":"#/components/schemas/ImageOutput"}},"InverseLerpInvocation":{"title":"InverseLerpInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["ilerp"],"type":"string","default":"ilerp"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to lerp"},"min":{"title":"Min","maximum":255.0,"minimum":0.0,"type":"integer","description":"The minimum input value","default":0},"max":{"title":"Max","maximum":255.0,"minimum":0.0,"type":"integer","description":"The maximum input value","default":255}},"description":"Inverse linear interpolation of all pixels of an image","output":{"$ref":"#/components/schemas/ImageOutput"}},"IterateInvocation":{"title":"IterateInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["iterate"],"type":"string","default":"iterate"},"collection":{"title":"Collection","type":"array","items":{},"description":"The list of items to iterate over"},"index":{"title":"Index","type":"integer","description":"The index, will be provided on executed iterators","default":0}},"description":"A node to process inputs and produce outputs.\nMay use dependency injection in __init__ to receive providers.","output":{"$ref":"#/components/schemas/IterateInvocationOutput"}},"IterateInvocationOutput":{"title":"IterateInvocationOutput","description":"Used to connect iteration outputs. Will be expanded to a specific output.","type":"object","properties":{"type":{"title":"Type","default":"iterate_output","enum":["iterate_output"],"type":"string"},"item":{"title":"Item","description":"The item being iterated over"}}},"LerpInvocation":{"title":"LerpInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["lerp"],"type":"string","default":"lerp"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to lerp"},"min":{"title":"Min","maximum":255.0,"minimum":0.0,"type":"integer","description":"The minimum output value","default":0},"max":{"title":"Max","maximum":255.0,"minimum":0.0,"type":"integer","description":"The maximum output value","default":255}},"description":"Linear interpolation of all pixels of an image","output":{"$ref":"#/components/schemas/ImageOutput"}},"LoadImageInvocation":{"title":"LoadImageInvocation","required":["id","image_type","image_name"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["load_image"],"type":"string","default":"load_image"},"image_type":{"allOf":[{"$ref":"#/components/schemas/ImageType"}],"description":"The type of the image"},"image_name":{"title":"Image Name","type":"string","description":"The name of the image"}},"description":"Load an image from a filename and provide it as output.","output":{"$ref":"#/components/schemas/ImageOutput"}},"MaskFromAlphaInvocation":{"title":"MaskFromAlphaInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["tomask"],"type":"string","default":"tomask"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to create the mask from"},"invert":{"title":"Invert","type":"boolean","description":"Whether or not to invert the mask","default":false}},"description":"Extracts the alpha channel of an image as a mask.","output":{"$ref":"#/components/schemas/MaskOutput"}},"MaskOutput":{"title":"MaskOutput","description":"Base class for invocations that output a mask","type":"object","properties":{"type":{"title":"Type","default":"mask","enum":["mask"],"type":"string"},"mask":{"title":"Mask","description":"The output mask","allOf":[{"$ref":"#/components/schemas/ImageField"}]}}},"PaginatedResults_GraphExecutionState_":{"title":"PaginatedResults[GraphExecutionState]","required":["items","page","pages","per_page","total"],"type":"object","properties":{"items":{"title":"Items","type":"array","items":{"$ref":"#/components/schemas/GraphExecutionState"},"description":"Items"},"page":{"title":"Page","type":"integer","description":"Current Page"},"pages":{"title":"Pages","type":"integer","description":"Total number of pages"},"per_page":{"title":"Per Page","type":"integer","description":"Number of items per page"},"total":{"title":"Total","type":"integer","description":"Total number of items in result"}},"description":"Paginated results"},"PasteImageInvocation":{"title":"PasteImageInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["paste"],"type":"string","default":"paste"},"base_image":{"title":"Base Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The base image"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to paste"},"mask":{"title":"Mask","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The mask to use when pasting"},"x":{"title":"X","type":"integer","description":"The left x coordinate at which to paste the image","default":0},"y":{"title":"Y","type":"integer","description":"The top y coordinate at which to paste the image","default":0}},"description":"Pastes an image into another image.","output":{"$ref":"#/components/schemas/ImageOutput"}},"PromptOutput":{"title":"PromptOutput","type":"object","properties":{"type":{"title":"Type","enum":["prompt"],"type":"string","default":"prompt"},"prompt":{"title":"Prompt","type":"string","description":"The output prompt"}},"description":"Base class for invocations that output a prompt"},"RestoreFaceInvocation":{"title":"RestoreFaceInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["restore_face"],"type":"string","default":"restore_face"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The input image"},"strength":{"title":"Strength","maximum":1.0,"exclusiveMinimum":0.0,"type":"number","description":"The strength of the restoration","default":0.75}},"description":"Restores faces in an image.","output":{"$ref":"#/components/schemas/ImageOutput"}},"ShowImageInvocation":{"title":"ShowImageInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["show_image"],"type":"string","default":"show_image"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The image to show"}},"description":"Displays a provided image, and passes it forward in the pipeline.","output":{"$ref":"#/components/schemas/ImageOutput"}},"TextToImageInvocation":{"title":"TextToImageInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["txt2img"],"type":"string","default":"txt2img"},"prompt":{"title":"Prompt","type":"string","description":"The prompt to generate an image from"},"seed":{"title":"Seed","maximum":4294967295.0,"minimum":-1.0,"type":"integer","description":"The seed to use (-1 for a random seed)","default":-1},"steps":{"title":"Steps","exclusiveMinimum":0.0,"type":"integer","description":"The number of steps to use to generate the image","default":10},"width":{"title":"Width","multipleOf":64.0,"exclusiveMinimum":0.0,"type":"integer","description":"The width of the resulting image","default":512},"height":{"title":"Height","multipleOf":64.0,"exclusiveMinimum":0.0,"type":"integer","description":"The height of the resulting image","default":512},"cfg_scale":{"title":"Cfg Scale","exclusiveMinimum":0.0,"type":"number","description":"The Classifier-Free Guidance, higher values may result in a result closer to the prompt","default":7.5},"sampler_name":{"title":"Sampler Name","enum":["ddim","dpmpp_2","k_dpm_2","k_dpm_2_a","k_dpmpp_2","k_euler","k_euler_a","k_heun","k_lms","plms"],"type":"string","description":"The sampler to use","default":"k_lms"},"seamless":{"title":"Seamless","type":"boolean","description":"Whether or not to generate an image that can tile without seams","default":false},"model":{"title":"Model","type":"string","description":"The model to use (currently ignored)","default":""},"progress_images":{"title":"Progress Images","type":"boolean","description":"Whether or not to produce progress images during generation","default":false}},"description":"Generates an image using text2img.","output":{"$ref":"#/components/schemas/ImageOutput"}},"UpscaleInvocation":{"title":"UpscaleInvocation","required":["id"],"type":"object","properties":{"id":{"title":"Id","type":"string","description":"The id of this node. Must be unique among all nodes."},"type":{"title":"Type","enum":["upscale"],"type":"string","default":"upscale"},"image":{"title":"Image","allOf":[{"$ref":"#/components/schemas/ImageField"}],"description":"The input image"},"strength":{"title":"Strength","maximum":1.0,"exclusiveMinimum":0.0,"type":"number","description":"The strength","default":0.75},"level":{"title":"Level","enum":[2,4],"type":"integer","description":"The upscale level","default":2}},"description":"Upscales an image.","output":{"$ref":"#/components/schemas/ImageOutput"}},"ValidationError":{"title":"ValidationError","required":["loc","msg","type"],"type":"object","properties":{"loc":{"title":"Location","type":"array","items":{"anyOf":[{"type":"string"},{"type":"integer"}]}},"msg":{"title":"Message","type":"string"},"type":{"title":"Error Type","type":"string"}}}}}} \ No newline at end of file diff --git a/invokeai/frontend/web/src/services/fixtures/request.ts b/invokeai/frontend/web/src/services/fixtures/request.ts deleted file mode 100644 index 51c5f0f708..0000000000 --- a/invokeai/frontend/web/src/services/fixtures/request.ts +++ /dev/null @@ -1,353 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * DO NOT DELETE EVEN THOUGH IT IS NOT USED! - * - * Custom `request.ts` file for OpenAPI code generator. - * - * Patches the request logic in such a way that we can extract headers from requests. - * - * Copied from https://github.com/ferdikoomen/openapi-typescript-codegen/issues/829#issuecomment-1228224477 - * - * This file should be excluded in `tsconfig.json` and ignored by prettier/eslint! - */ - -import axios from 'axios'; -import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; -import FormData from 'form-data'; - -import { ApiError } from './ApiError'; -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; -import { CancelablePromise } from './CancelablePromise'; -import type { OnCancel } from './CancelablePromise'; -import type { OpenAPIConfig } from './OpenAPI'; - -export const HEADERS = Symbol('HEADERS'); - -const isDefined = ( - value: T | null | undefined -): value is Exclude => { - return value !== undefined && value !== null; -}; - -const isString = (value: any): value is string => { - return typeof value === 'string'; -}; - -const isStringWithValue = (value: any): value is string => { - return isString(value) && value !== ''; -}; - -const isBlob = (value: any): value is Blob => { - return ( - typeof value === 'object' && - typeof value.type === 'string' && - typeof value.stream === 'function' && - typeof value.arrayBuffer === 'function' && - typeof value.constructor === 'function' && - typeof value.constructor.name === 'string' && - /^(Blob|File)$/.test(value.constructor.name) && - /^(Blob|File)$/.test(value[Symbol.toStringTag]) - ); -}; - -const isFormData = (value: any): value is FormData => { - return value instanceof FormData; -}; - -const isSuccess = (status: number): boolean => { - return status >= 200 && status < 300; -}; - -const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString('base64'); - } -}; - -const getQueryString = (params: Record): string => { - const qs: string[] = []; - - const append = (key: string, value: any) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; - - const process = (key: string, value: any) => { - if (isDefined(value)) { - if (Array.isArray(value)) { - value.forEach((v) => { - process(key, v); - }); - } else if (typeof value === 'object') { - Object.entries(value).forEach(([k, v]) => { - process(`${key}[${k}]`, v); - }); - } else { - append(key, value); - } - } - }; - - Object.entries(params).forEach(([key, value]) => { - process(key, value); - }); - - if (qs.length > 0) { - return `?${qs.join('&')}`; - } - - return ''; -}; - -const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace('{api-version}', config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); - - const url = `${config.BASE}${path}`; - if (options.query) { - return `${url}${getQueryString(options.query)}`; - } - return url; -}; - -const getFormData = (options: ApiRequestOptions): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); - - const process = (key: string, value: any) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; - - Object.entries(options.formData) - .filter(([_, value]) => isDefined(value)) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach((v) => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; -}; - -type Resolver = (options: ApiRequestOptions) => Promise; - -const resolve = async ( - options: ApiRequestOptions, - resolver?: T | Resolver -): Promise => { - if (typeof resolver === 'function') { - return (resolver as Resolver)(options); - } - return resolver; -}; - -const getHeaders = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - formData?: FormData -): Promise> => { - const token = await resolve(options, config.TOKEN); - const username = await resolve(options, config.USERNAME); - const password = await resolve(options, config.PASSWORD); - const additionalHeaders = await resolve(options, config.HEADERS); - const formHeaders = - (typeof formData?.getHeaders === 'function' && formData?.getHeaders()) || - {}; - - const headers = Object.entries({ - Accept: 'application/json', - ...additionalHeaders, - ...options.headers, - ...formHeaders, - }) - .filter(([_, value]) => isDefined(value)) - .reduce( - (headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), - {} as Record - ); - - if (isStringWithValue(token)) { - headers['Authorization'] = `Bearer ${token}`; - } - - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers['Authorization'] = `Basic ${credentials}`; - } - - if (options.body) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } else if (isBlob(options.body)) { - headers['Content-Type'] = options.body.type || 'application/octet-stream'; - } else if (isString(options.body)) { - headers['Content-Type'] = 'text/plain'; - } else if (!isFormData(options.body)) { - headers['Content-Type'] = 'application/json'; - } - } - - return headers; -}; - -const getRequestBody = (options: ApiRequestOptions): any => { - if (options.body) { - return options.body; - } - return undefined; -}; - -const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: any, - formData: FormData | undefined, - headers: Record, - onCancel: OnCancel -): Promise> => { - const source = axios.CancelToken.source(); - - const requestConfig: AxiosRequestConfig = { - url, - headers, - data: body ?? formData, - method: options.method, - withCredentials: config.WITH_CREDENTIALS, - cancelToken: source.token, - }; - - onCancel(() => source.cancel('The user aborted a request.')); - - try { - return await axios.request(requestConfig); - } catch (error) { - const axiosError = error as AxiosError; - if (axiosError.response) { - return axiosError.response; - } - throw error; - } -}; - -const getResponseHeader = ( - response: AxiosResponse, - responseHeader?: string -): string | undefined => { - if (responseHeader) { - const content = response.headers[responseHeader]; - if (isString(content)) { - return content; - } - } - return undefined; -}; - -const getResponseBody = (response: AxiosResponse): any => { - if (response.status !== 204) { - return response.data; - } - return undefined; -}; - -const catchErrorCodes = ( - options: ApiRequestOptions, - result: ApiResult -): void => { - const errors: Record = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Forbidden', - 404: 'Not Found', - 500: 'Internal Server Error', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - ...options.errors, - }; - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - throw new ApiError(options, result, 'Generic Error'); - } -}; - -/** - * Request method - * @param config The OpenAPI configuration object - * @param options The request options from the service - * @returns CancelablePromise - * @throws ApiError - */ -export const request = ( - config: OpenAPIConfig, - options: ApiRequestOptions -): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options, formData); - - if (!onCancel.isCancelled) { - const response = await sendRequest( - config, - options, - url, - body, - formData, - headers, - onCancel - ); - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader( - response, - options.responseHeader - ); - - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader ?? responseBody, - }; - - catchErrorCodes(options, result); - - resolve({ ...result.body, [HEADERS]: response.headers }); - } - } catch (error) { - reject(error); - } - }); -}; diff --git a/invokeai/frontend/web/src/services/thunks/gallery.ts b/invokeai/frontend/web/src/services/thunks/gallery.ts deleted file mode 100644 index 11960e00d2..0000000000 --- a/invokeai/frontend/web/src/services/thunks/gallery.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { ImagesService, PaginatedResults_ImageDTO_ } from 'services/api'; - -export const IMAGES_PER_PAGE = 20; - -type ReceivedResultImagesPageThunkConfig = { - rejectValue: { - error: unknown; - }; -}; - -export const receivedResultImagesPage = createAppAsyncThunk< - PaginatedResults_ImageDTO_, - void, - ReceivedResultImagesPageThunkConfig ->( - 'results/receivedResultImagesPage', - async (_arg, { getState, rejectWithValue }) => { - const { page, pages, nextPage, upsertedImageCount } = getState().results; - - // If many images have been upserted, we need to offset the page number - // TODO: add an offset param to the list images endpoint - const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE); - - const response = await ImagesService.listImagesWithMetadata({ - imageType: 'results', - imageCategory: 'general', - page: nextPage + pageOffset, - perPage: IMAGES_PER_PAGE, - }); - - return response; - } -); - -type ReceivedUploadImagesPageThunkConfig = { - rejectValue: { - error: unknown; - }; -}; - -export const receivedUploadImagesPage = createAppAsyncThunk< - PaginatedResults_ImageDTO_, - void, - ReceivedUploadImagesPageThunkConfig ->( - 'uploads/receivedUploadImagesPage', - async (_arg, { getState, rejectWithValue }) => { - const { page, pages, nextPage, upsertedImageCount } = getState().uploads; - - // If many images have been upserted, we need to offset the page number - // TODO: add an offset param to the list images endpoint - const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE); - - const response = await ImagesService.listImagesWithMetadata({ - imageType: 'uploads', - imageCategory: 'general', - page: nextPage + pageOffset, - perPage: IMAGES_PER_PAGE, - }); - - return response; - } -); diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts deleted file mode 100644 index f0c0456202..0000000000 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { InvokeTabName } from 'features/ui/store/tabMap'; -import { ImagesService } from 'services/api'; - -type imageUrlsReceivedArg = Parameters< - (typeof ImagesService)['getImageUrls'] ->[0]; - -/** - * `ImagesService.getImageUrls()` thunk - */ -export const imageUrlsReceived = createAppAsyncThunk( - 'api/imageUrlsReceived', - async (arg: imageUrlsReceivedArg) => { - const response = await ImagesService.getImageUrls(arg); - return response; - } -); - -type imageMetadataReceivedArg = Parameters< - (typeof ImagesService)['getImageMetadata'] ->[0]; - -/** - * `ImagesService.getImageUrls()` thunk - */ -export const imageMetadataReceived = createAppAsyncThunk( - 'api/imageMetadataReceived', - async (arg: imageMetadataReceivedArg) => { - const response = await ImagesService.getImageMetadata(arg); - return response; - } -); - -type ImageUploadedArg = Parameters<(typeof ImagesService)['uploadImage']>[0] & { - // extra arg to determine post-upload actions - we check for this when the image is uploaded - // to determine if we should set the init image - activeTabName?: InvokeTabName; -}; - -/** - * `ImagesService.uploadImage()` thunk - */ -export const imageUploaded = createAppAsyncThunk( - 'api/imageUploaded', - async (arg: ImageUploadedArg) => { - // strip out `activeTabName` from arg - the route does not need it - const { activeTabName, ...rest } = arg; - const response = await ImagesService.uploadImage(rest); - return response; - } -); - -type ImageDeletedArg = Parameters<(typeof ImagesService)['deleteImage']>[0]; - -/** - * `ImagesService.deleteImage()` thunk - */ -export const imageDeleted = createAppAsyncThunk( - 'api/imageDeleted', - async (arg: ImageDeletedArg) => { - const response = await ImagesService.deleteImage(arg); - return response; - } -); - -type ImageUpdatedArg = Parameters<(typeof ImagesService)['updateImage']>[0]; - -/** - * `ImagesService.updateImage()` thunk - */ -export const imageUpdated = createAppAsyncThunk( - 'api/imageUpdated', - async (arg: ImageUpdatedArg) => { - const response = await ImagesService.updateImage(arg); - return response; - } -); diff --git a/invokeai/frontend/web/src/services/thunks/model.ts b/invokeai/frontend/web/src/services/thunks/model.ts deleted file mode 100644 index 84f7a24e81..0000000000 --- a/invokeai/frontend/web/src/services/thunks/model.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { log } from 'app/logging/useLogger'; -import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { Model } from 'features/system/store/modelSlice'; -import { reduce, size } from 'lodash-es'; -import { ModelsService } from 'services/api'; - -const models = log.child({ namespace: 'model' }); - -export const IMAGES_PER_PAGE = 20; - -export const receivedModels = createAppAsyncThunk( - 'models/receivedModels', - async (_) => { - const response = await ModelsService.listModels(); - - const deserializedModels = reduce( - response.models, - (modelsAccumulator, model, modelName) => { - modelsAccumulator[modelName] = { ...model, name: modelName }; - - return modelsAccumulator; - }, - {} as Record - ); - - models.info({ response }, `Received ${size(response.models)} models`); - - return deserializedModels; - } -); diff --git a/invokeai/frontend/web/src/services/thunks/schema.ts b/invokeai/frontend/web/src/services/thunks/schema.ts deleted file mode 100644 index bc93fa0fae..0000000000 --- a/invokeai/frontend/web/src/services/thunks/schema.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import { log } from 'app/logging/useLogger'; -import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; -import { OpenAPIV3 } from 'openapi-types'; - -const schemaLog = log.child({ namespace: 'schema' }); - -export const receivedOpenAPISchema = createAsyncThunk( - 'nodes/receivedOpenAPISchema', - async (_, { dispatch }): Promise => { - const response = await fetch(`openapi.json`); - const openAPISchema = await response.json(); - - schemaLog.info({ openAPISchema }, 'Received OpenAPI schema'); - - dispatch(parsedOpenAPISchema(openAPISchema as OpenAPIV3.Document)); - - return openAPISchema; - } -); diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts deleted file mode 100644 index cf87fb30f5..0000000000 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { GraphExecutionState, SessionsService } from 'services/api'; -import { log } from 'app/logging/useLogger'; -import { isObject } from 'lodash-es'; - -const sessionLog = log.child({ namespace: 'session' }); - -type SessionCreatedArg = { - graph: Parameters< - (typeof SessionsService)['createSession'] - >[0]['requestBody']; -}; - -type SessionCreatedThunkConfig = { - rejectValue: { arg: SessionCreatedArg; error: unknown }; -}; - -/** - * `SessionsService.createSession()` thunk - */ -export const sessionCreated = createAppAsyncThunk< - GraphExecutionState, - SessionCreatedArg, - SessionCreatedThunkConfig ->('api/sessionCreated', async (arg, { rejectWithValue }) => { - try { - const response = await SessionsService.createSession({ - requestBody: arg.graph, - }); - return response; - } catch (error) { - return rejectWithValue({ arg, error }); - } -}); - -type SessionInvokedArg = { sessionId: string }; - -type SessionInvokedThunkConfig = { - rejectValue: { - arg: SessionInvokedArg; - error: unknown; - }; -}; - -const isErrorWithStatus = (error: unknown): error is { status: number } => - isObject(error) && 'status' in error; - -/** - * `SessionsService.invokeSession()` thunk - */ -export const sessionInvoked = createAppAsyncThunk< - void, - SessionInvokedArg, - SessionInvokedThunkConfig ->('api/sessionInvoked', async (arg, { rejectWithValue }) => { - const { sessionId } = arg; - - try { - const response = await SessionsService.invokeSession({ - sessionId, - all: true, - }); - return response; - } catch (error) { - if (isErrorWithStatus(error) && error.status === 403) { - return rejectWithValue({ arg, error: (error as any).body.detail }); - } - return rejectWithValue({ arg, error }); - } -}); - -type SessionCanceledArg = Parameters< - (typeof SessionsService)['cancelSessionInvoke'] ->[0]; -type SessionCanceledThunkConfig = { - rejectValue: { - arg: SessionCanceledArg; - error: unknown; - }; -}; -/** - * `SessionsService.cancelSession()` thunk - */ -export const sessionCanceled = createAppAsyncThunk< - void, - SessionCanceledArg, - SessionCanceledThunkConfig ->('api/sessionCanceled', async (arg: SessionCanceledArg, _thunkApi) => { - const { sessionId } = arg; - - const response = await SessionsService.cancelSessionInvoke({ - sessionId, - }); - - return response; -}); - -type SessionsListedArg = Parameters< - (typeof SessionsService)['listSessions'] ->[0]; - -/** - * `SessionsService.listSessions()` thunk - */ -export const listedSessions = createAppAsyncThunk( - 'api/listSessions', - async (arg: SessionsListedArg, _thunkApi) => { - const response = await SessionsService.listSessions(arg); - - sessionLog.info( - { arg, response }, - `Sessions listed (${response.items.length})` - ); - - return response; - } -); diff --git a/invokeai/frontend/web/src/services/types/guards.ts b/invokeai/frontend/web/src/services/types/guards.ts deleted file mode 100644 index 266e991f4d..0000000000 --- a/invokeai/frontend/web/src/services/types/guards.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ResultsImageDTO } from 'features/gallery/store/resultsSlice'; -import { UploadsImageDTO } from 'features/gallery/store/uploadsSlice'; -import { get, isObject, isString } from 'lodash-es'; -import { - GraphExecutionState, - GraphInvocationOutput, - ImageOutput, - MaskOutput, - PromptOutput, - IterateInvocationOutput, - CollectInvocationOutput, - ImageType, - ImageField, - LatentsOutput, - ImageDTO, -} from 'services/api'; - -export const isUploadsImageDTO = (image: ImageDTO): image is UploadsImageDTO => - image.image_type === 'uploads'; - -export const isResultsImageDTO = (image: ImageDTO): image is ResultsImageDTO => - image.image_type === 'results'; - -export const isImageOutput = ( - output: GraphExecutionState['results'][string] -): output is ImageOutput => output.type === 'image_output'; - -export const isLatentsOutput = ( - output: GraphExecutionState['results'][string] -): output is LatentsOutput => output.type === 'latents_output'; - -export const isMaskOutput = ( - output: GraphExecutionState['results'][string] -): output is MaskOutput => output.type === 'mask'; - -export const isPromptOutput = ( - output: GraphExecutionState['results'][string] -): output is PromptOutput => output.type === 'prompt'; - -export const isGraphOutput = ( - output: GraphExecutionState['results'][string] -): output is GraphInvocationOutput => output.type === 'graph_output'; - -export const isIterateOutput = ( - output: GraphExecutionState['results'][string] -): output is IterateInvocationOutput => output.type === 'iterate_output'; - -export const isCollectOutput = ( - output: GraphExecutionState['results'][string] -): output is CollectInvocationOutput => output.type === 'collect_output'; - -export const isImageType = (t: unknown): t is ImageType => - isString(t) && ['results', 'uploads', 'intermediates'].includes(t); - -export const isImageField = (imageField: unknown): imageField is ImageField => - isObject(imageField) && - isString(get(imageField, 'image_name')) && - isImageType(get(imageField, 'image_type')); diff --git a/invokeai/frontend/web/src/services/util/getHeaders.ts b/invokeai/frontend/web/src/services/util/getHeaders.ts deleted file mode 100644 index 510ba35770..0000000000 --- a/invokeai/frontend/web/src/services/util/getHeaders.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { HEADERS } from '../api/core/request'; - -/** - * Returns the response headers of the response received by the generated API client. - */ -export const getHeaders = (response: any): Record => { - if (!(HEADERS in response)) { - throw new Error('Response does not have headers'); - } - - return response[HEADERS]; -}; diff --git a/invokeai/frontend/web/src/theme/colors/colors.ts b/invokeai/frontend/web/src/theme/colors/colors.ts new file mode 100644 index 0000000000..bcb2e43c0b --- /dev/null +++ b/invokeai/frontend/web/src/theme/colors/colors.ts @@ -0,0 +1,24 @@ +import { InvokeAIThemeColors } from 'theme/themeTypes'; +import { generateColorPalette } from 'theme/util/generateColorPalette'; + +const BASE = { H: 220, S: 16 }; +const ACCENT = { H: 250, S: 52 }; +const WORKING = { H: 47, S: 50 }; +const WARNING = { H: 28, S: 50 }; +const OK = { H: 113, S: 50 }; +const ERROR = { H: 0, S: 50 }; + +export const InvokeAIColors: InvokeAIThemeColors = { + base: generateColorPalette(BASE.H, BASE.S), + baseAlpha: generateColorPalette(BASE.H, BASE.S, true), + accent: generateColorPalette(ACCENT.H, ACCENT.S), + accentAlpha: generateColorPalette(ACCENT.H, ACCENT.S, true), + working: generateColorPalette(WORKING.H, WORKING.S), + workingAlpha: generateColorPalette(WORKING.H, WORKING.S, true), + warning: generateColorPalette(WARNING.H, WARNING.S), + warningAlpha: generateColorPalette(WARNING.H, WARNING.S, true), + ok: generateColorPalette(OK.H, OK.S), + okAlpha: generateColorPalette(OK.H, OK.S, true), + error: generateColorPalette(ERROR.H, ERROR.S), + errorAlpha: generateColorPalette(ERROR.H, ERROR.S, true), +}; diff --git a/invokeai/frontend/web/src/theme/colors/greenTea.ts b/invokeai/frontend/web/src/theme/colors/greenTea.ts deleted file mode 100644 index 06476c0513..0000000000 --- a/invokeai/frontend/web/src/theme/colors/greenTea.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { InvokeAIThemeColors } from 'theme/themeTypes'; -import { generateColorPalette } from '../util/generateColorPalette'; - -export const greenTeaThemeColors: InvokeAIThemeColors = { - base: generateColorPalette(223, 10), - accent: generateColorPalette(155, 80), - working: generateColorPalette(47, 68), - warning: generateColorPalette(28, 75), - ok: generateColorPalette(122, 49), - error: generateColorPalette(0, 50), - gridLineColor: 'rgba(255, 255, 255, 0.2)', -}; diff --git a/invokeai/frontend/web/src/theme/colors/invokeAI.ts b/invokeai/frontend/web/src/theme/colors/invokeAI.ts deleted file mode 100644 index a523ae38c8..0000000000 --- a/invokeai/frontend/web/src/theme/colors/invokeAI.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { InvokeAIThemeColors } from 'theme/themeTypes'; -import { generateColorPalette } from 'theme/util/generateColorPalette'; - -export const invokeAIThemeColors: InvokeAIThemeColors = { - base: generateColorPalette(225, 15), - accent: generateColorPalette(250, 50), - working: generateColorPalette(47, 67), - warning: generateColorPalette(28, 75), - ok: generateColorPalette(113, 70), - error: generateColorPalette(0, 76), - gridLineColor: 'rgba(255, 255, 255, 0.2)', -}; diff --git a/invokeai/frontend/web/src/theme/colors/lightTheme.ts b/invokeai/frontend/web/src/theme/colors/lightTheme.ts deleted file mode 100644 index 8fdf199bb8..0000000000 --- a/invokeai/frontend/web/src/theme/colors/lightTheme.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { InvokeAIThemeColors } from 'theme/themeTypes'; -import { generateColorPalette } from '../util/generateColorPalette'; - -export const lightThemeColors: InvokeAIThemeColors = { - base: generateColorPalette(223, 10, true), - accent: generateColorPalette(40, 80, true), - working: generateColorPalette(47, 68, true), - warning: generateColorPalette(28, 75, true), - ok: generateColorPalette(122, 49, true), - error: generateColorPalette(0, 50, true), - gridLineColor: 'rgba(0, 0, 0, 0.2)', -}; diff --git a/invokeai/frontend/web/src/theme/colors/oceanBlue.ts b/invokeai/frontend/web/src/theme/colors/oceanBlue.ts deleted file mode 100644 index 3462459c1c..0000000000 --- a/invokeai/frontend/web/src/theme/colors/oceanBlue.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { InvokeAIThemeColors } from 'theme/themeTypes'; -import { generateColorPalette } from '../util/generateColorPalette'; - -export const oceanBlueColors: InvokeAIThemeColors = { - base: generateColorPalette(220, 30), - accent: generateColorPalette(210, 80), - working: generateColorPalette(47, 68), - warning: generateColorPalette(28, 75), - ok: generateColorPalette(122, 49), - error: generateColorPalette(0, 100), - gridLineColor: 'rgba(136, 148, 184, 0.2)', -}; diff --git a/invokeai/frontend/web/src/theme/components/accordion.ts b/invokeai/frontend/web/src/theme/components/accordion.ts index 3477548c70..912e55d35b 100644 --- a/invokeai/frontend/web/src/theme/components/accordion.ts +++ b/invokeai/frontend/web/src/theme/components/accordion.ts @@ -3,6 +3,7 @@ import { createMultiStyleConfigHelpers, defineStyle, } from '@chakra-ui/styled-system'; +import { mode } from '@chakra-ui/theme-tools'; const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(parts.keys); @@ -18,16 +19,16 @@ const invokeAIButton = defineStyle((props) => { fontSize: 'sm', border: 'none', borderRadius: 'base', - bg: `${c}.800`, - color: 'base.100', + bg: mode(`${c}.200`, `${c}.700`)(props), + color: mode(`${c}.900`, `${c}.100`)(props), _hover: { - bg: `${c}.700`, + bg: mode(`${c}.250`, `${c}.650`)(props), }, _expanded: { - bg: `${c}.750`, + bg: mode(`${c}.250`, `${c}.650`)(props), borderBottomRadius: 'none', _hover: { - bg: `${c}.700`, + bg: mode(`${c}.300`, `${c}.600`)(props), }, }, }; @@ -36,7 +37,7 @@ const invokeAIButton = defineStyle((props) => { const invokeAIPanel = defineStyle((props) => { const { colorScheme: c } = props; return { - bg: `${c}.800`, + bg: mode(`${c}.100`, `${c}.800`)(props), borderRadius: 'base', borderTopRadius: 'none', }; diff --git a/invokeai/frontend/web/src/theme/components/button.ts b/invokeai/frontend/web/src/theme/components/button.ts index 879b3dba96..75662f7d42 100644 --- a/invokeai/frontend/web/src/theme/components/button.ts +++ b/invokeai/frontend/web/src/theme/components/button.ts @@ -1,44 +1,117 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/react'; +import { mode } from '@chakra-ui/theme-tools'; const invokeAI = defineStyle((props) => { const { colorScheme: c } = props; // must specify `_disabled` colors if we override `_hover`, else hover on disabled has no styles + + if (c === 'base') { + const _disabled = { + bg: mode('base.200', 'base.700')(props), + color: mode('base.500', 'base.150')(props), + svg: { + fill: mode('base.500', 'base.150')(props), + }, + opacity: 1, + }; + + return { + bg: mode('base.200', 'base.600')(props), + color: mode('base.850', 'base.100')(props), + borderRadius: 'base', + textShadow: mode( + '0 0 0.3rem var(--invokeai-colors-base-50)', + '0 0 0.3rem var(--invokeai-colors-base-900)' + )(props), + svg: { + fill: mode('base.850', 'base.100')(props), + filter: mode( + 'drop-shadow(0px 0px 0.3rem var(--invokeai-colors-base-100))', + 'drop-shadow(0px 0px 0.3rem var(--invokeai-colors-base-800))' + )(props), + }, + _disabled, + _hover: { + bg: mode('base.300', 'base.500')(props), + color: mode('base.900', 'base.50')(props), + svg: { + fill: mode('base.900', 'base.50')(props), + }, + _disabled, + }, + _checked: { + bg: mode('accent.400', 'accent.600')(props), + color: mode('base.50', 'base.100')(props), + svg: { + fill: mode(`${c}.50`, `${c}.100`)(props), + filter: mode( + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`, + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))` + )(props), + }, + _disabled, + _hover: { + bg: mode('accent.500', 'accent.500')(props), + color: mode('white', 'base.50')(props), + svg: { + fill: mode('white', 'base.50')(props), + }, + _disabled, + }, + }, + }; + } + const _disabled = { - bg: `${c}.600`, - color: `${c}.100`, + bg: mode(`${c}.200`, `${c}.700`)(props), + color: mode(`${c}.100`, `${c}.150`)(props), svg: { - fill: `${c}.100`, + fill: mode(`${c}.100`, `${c}.150`)(props), }, + opacity: 1, + filter: mode(undefined, 'saturate(65%)')(props), }; return { - bg: `${c}.700`, - color: `${c}.100`, + bg: mode(`${c}.400`, `${c}.600`)(props), + color: mode(`base.50`, `base.100`)(props), borderRadius: 'base', + textShadow: mode( + `0 0 0.3rem var(--invokeai-colors-${c}-600)`, + `0 0 0.3rem var(--invokeai-colors-${c}-900)` + )(props), svg: { - fill: `${c}.100`, + fill: mode(`base.50`, `base.100`)(props), + filter: mode( + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`, + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))` + )(props), }, _disabled, _hover: { - bg: `${c}.650`, - color: `${c}.50`, + bg: mode(`${c}.500`, `${c}.500`)(props), + color: mode('white', `base.50`)(props), svg: { - fill: `${c}.50`, + fill: mode('white', `base.50`)(props), }, _disabled, }, _checked: { - bg: 'accent.700', - color: 'accent.100', + bg: mode('accent.400', 'accent.600')(props), + color: mode('base.50', 'base.100')(props), svg: { - fill: 'accent.100', + fill: mode(`base.50`, `base.100`)(props), + filter: mode( + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`, + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))` + )(props), }, _disabled, _hover: { - bg: 'accent.600', - color: 'accent.50', + bg: mode('accent.500', 'accent.500')(props), + color: mode('white', 'base.50')(props), svg: { - fill: 'accent.50', + fill: mode('white', 'base.50')(props), }, _disabled, }, diff --git a/invokeai/frontend/web/src/theme/components/checkbox.ts b/invokeai/frontend/web/src/theme/components/checkbox.ts index ec61706715..58871237e5 100644 --- a/invokeai/frontend/web/src/theme/components/checkbox.ts +++ b/invokeai/frontend/web/src/theme/components/checkbox.ts @@ -3,6 +3,7 @@ import { createMultiStyleConfigHelpers, defineStyle, } from '@chakra-ui/styled-system'; +import { mode } from '@chakra-ui/theme-tools'; const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(parts.keys); @@ -11,14 +12,18 @@ const invokeAIControl = defineStyle((props) => { const { colorScheme: c } = props; return { + bg: mode('base.200', 'base.700')(props), + borderColor: mode('base.200', 'base.700')(props), + color: mode('base.900', 'base.100')(props), + _checked: { - bg: `${c}.200`, - borderColor: `${c}.200`, - color: 'base.900', + bg: mode(`${c}.300`, `${c}.600`)(props), + borderColor: mode(`${c}.300`, `${c}.600`)(props), + color: mode(`${c}.900`, `${c}.100`)(props), _hover: { - bg: `${c}.300`, - borderColor: `${c}.300`, + bg: mode(`${c}.400`, `${c}.500`)(props), + borderColor: mode(`${c}.400`, `${c}.500`)(props), }, _disabled: { @@ -29,9 +34,9 @@ const invokeAIControl = defineStyle((props) => { }, _indeterminate: { - bg: `${c}.200`, - borderColor: `${c}.200`, - color: 'base.900', + bg: mode(`${c}.300`, `${c}.600`)(props), + borderColor: mode(`${c}.300`, `${c}.600`)(props), + color: mode(`${c}.900`, `${c}.100`)(props), }, _disabled: { @@ -44,7 +49,7 @@ const invokeAIControl = defineStyle((props) => { }, _invalid: { - borderColor: 'red.300', + borderColor: mode('error.600', 'error.300')(props), }, }; }); diff --git a/invokeai/frontend/web/src/theme/components/formLabel.ts b/invokeai/frontend/web/src/theme/components/formLabel.ts index 0ff7c6cdea..866bb7beb1 100644 --- a/invokeai/frontend/web/src/theme/components/formLabel.ts +++ b/invokeai/frontend/web/src/theme/components/formLabel.ts @@ -1,6 +1,7 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; +import { mode } from '@chakra-ui/theme-tools'; -const invokeAI = defineStyle((_props) => { +const invokeAI = defineStyle((props) => { return { fontSize: 'sm', marginEnd: 0, @@ -12,7 +13,7 @@ const invokeAI = defineStyle((_props) => { _disabled: { opacity: 0.4, }, - color: 'base.300', + color: mode('base.700', 'base.300')(props), }; }); diff --git a/invokeai/frontend/web/src/theme/components/menu.ts b/invokeai/frontend/web/src/theme/components/menu.ts index 597e989953..02f75087ed 100644 --- a/invokeai/frontend/web/src/theme/components/menu.ts +++ b/invokeai/frontend/web/src/theme/components/menu.ts @@ -1,38 +1,40 @@ import { menuAnatomy } from '@chakra-ui/anatomy'; import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; +import { mode } from '@chakra-ui/theme-tools'; const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(menuAnatomy.keys); // define the base component styles -const invokeAI = definePartsStyle({ +const invokeAI = definePartsStyle((props) => ({ // define the part you're going to style button: { // this will style the MenuButton component - fontWeight: '600', - bg: 'base.500', - color: 'base.200', + fontWeight: 500, + bg: mode('base.300', 'base.500')(props), + color: mode('base.900', 'base.100')(props), _hover: { - bg: 'base.600', - color: 'white', + bg: mode('base.400', 'base.600')(props), + color: mode('base.900', 'base.50')(props), + fontWeight: 600, }, }, list: { zIndex: 9999, - bg: 'base.800', + bg: mode('base.200', 'base.800')(props), }, item: { // this will style the MenuItem and MenuItemOption components fontSize: 'sm', - bg: 'base.800', + bg: mode('base.200', 'base.800')(props), _hover: { - bg: 'base.750', + bg: mode('base.300', 'base.700')(props), }, _focus: { - bg: 'base.700', + bg: mode('base.400', 'base.600')(props), }, }, -}); +})); export const menuTheme = defineMultiStyleConfig({ variants: { diff --git a/invokeai/frontend/web/src/theme/components/modal.ts b/invokeai/frontend/web/src/theme/components/modal.ts index 1f6900278a..8310d9d46c 100644 --- a/invokeai/frontend/web/src/theme/components/modal.ts +++ b/invokeai/frontend/web/src/theme/components/modal.ts @@ -3,28 +3,31 @@ import { createMultiStyleConfigHelpers, defineStyle, } from '@chakra-ui/styled-system'; +import { mode } from '@chakra-ui/theme-tools'; const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(parts.keys); -const invokeAIOverlay = defineStyle({ - bg: 'blackAlpha.600', -}); +const invokeAIOverlay = defineStyle((props) => ({ + bg: mode('blackAlpha.700', 'blackAlpha.700')(props), +})); const invokeAIDialogContainer = defineStyle({}); -const invokeAIDialog = defineStyle((_props) => { +const invokeAIDialog = defineStyle((props) => { return { - bg: 'base.850', + layerStyle: 'first', maxH: '80vh', }; }); -const invokeAIHeader = defineStyle((_props) => { +const invokeAIHeader = defineStyle((props) => { return { fontWeight: '600', fontSize: 'lg', - color: 'base.200', + layerStyle: 'first', + borderTopRadius: 'base', + borderInlineEndRadius: 'base', }; }); @@ -37,7 +40,7 @@ const invokeAIBody = defineStyle({ const invokeAIFooter = defineStyle({}); export const invokeAI = definePartsStyle((props) => ({ - overlay: invokeAIOverlay, + overlay: invokeAIOverlay(props), dialogContainer: invokeAIDialogContainer, dialog: invokeAIDialog(props), header: invokeAIHeader(props), diff --git a/invokeai/frontend/web/src/theme/components/numberInput.ts b/invokeai/frontend/web/src/theme/components/numberInput.ts index 935f21077b..681fa6f566 100644 --- a/invokeai/frontend/web/src/theme/components/numberInput.ts +++ b/invokeai/frontend/web/src/theme/components/numberInput.ts @@ -5,6 +5,7 @@ import { } from '@chakra-ui/styled-system'; import { getInputOutlineStyles } from '../util/getInputOutlineStyles'; +import { mode } from '@chakra-ui/theme-tools'; const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(parts.keys); @@ -33,7 +34,7 @@ const invokeAIStepperGroup = defineStyle((_props) => { }; }); -const invokeAIStepper = defineStyle((_props) => { +const invokeAIStepper = defineStyle((props) => { return { border: 'none', // expand arrow hitbox @@ -43,11 +44,11 @@ const invokeAIStepper = defineStyle((_props) => { my: 0, svg: { - color: 'base.300', + color: mode('base.700', 'base.300')(props), width: 2.5, height: 2.5, _hover: { - color: 'base.50', + color: mode('base.900', 'base.100')(props), }, }, }; diff --git a/invokeai/frontend/web/src/theme/components/popover.ts b/invokeai/frontend/web/src/theme/components/popover.ts index c8d6ae20d8..a28e2bfbc4 100644 --- a/invokeai/frontend/web/src/theme/components/popover.ts +++ b/invokeai/frontend/web/src/theme/components/popover.ts @@ -3,7 +3,7 @@ import { createMultiStyleConfigHelpers, defineStyle, } from '@chakra-ui/styled-system'; -import { cssVar } from '@chakra-ui/theme-tools'; +import { cssVar, mode } from '@chakra-ui/theme-tools'; const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(parts.keys); @@ -12,15 +12,20 @@ const $popperBg = cssVar('popper-bg'); const $arrowBg = cssVar('popper-arrow-bg'); const $arrowShadowColor = cssVar('popper-arrow-shadow-color'); -const invokeAIContent = defineStyle((_props) => { +const invokeAIContent = defineStyle((props) => { return { - [$arrowBg.variable]: `colors.base.800`, - [$popperBg.variable]: `colors.base.800`, - [$arrowShadowColor.variable]: `colors.base.600`, + [$arrowBg.variable]: mode('colors.base.100', 'colors.base.800')(props), + [$popperBg.variable]: mode('colors.base.100', 'colors.base.800')(props), + [$arrowShadowColor.variable]: mode( + 'colors.base.400', + 'colors.base.600' + )(props), minW: 'unset', width: 'unset', p: 4, - bg: 'base.800', + bg: mode('base.100', 'base.800')(props), + border: 'none', + shadow: 'dark-lg', }; }); diff --git a/invokeai/frontend/web/src/theme/components/progress.ts b/invokeai/frontend/web/src/theme/components/progress.ts index fa6b5b57c5..87b6b7af01 100644 --- a/invokeai/frontend/web/src/theme/components/progress.ts +++ b/invokeai/frontend/web/src/theme/components/progress.ts @@ -20,7 +20,7 @@ const invokeAIFilledTrack = defineStyle((_props) => ({ const invokeAITrack = defineStyle((_props) => { return { - bg: 'base.800', + bg: 'none', }; }); diff --git a/invokeai/frontend/web/src/theme/components/select.ts b/invokeai/frontend/web/src/theme/components/select.ts index 2fad17e1b4..72bb896238 100644 --- a/invokeai/frontend/web/src/theme/components/select.ts +++ b/invokeai/frontend/web/src/theme/components/select.ts @@ -1,13 +1,14 @@ import { selectAnatomy as parts } from '@chakra-ui/anatomy'; import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react'; import { getInputOutlineStyles } from '../util/getInputOutlineStyles'; +import { mode } from '@chakra-ui/theme-tools'; const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(parts.keys); -const invokeAIIcon = defineStyle((_props) => { +const invokeAIIcon = defineStyle((props) => { return { - color: 'base.300', + color: mode('base.200', 'base.300')(props), }; }); diff --git a/invokeai/frontend/web/src/theme/components/slider.ts b/invokeai/frontend/web/src/theme/components/slider.ts index ef3d84196e..397dea786a 100644 --- a/invokeai/frontend/web/src/theme/components/slider.ts +++ b/invokeai/frontend/web/src/theme/components/slider.ts @@ -1,12 +1,13 @@ import { sliderAnatomy as parts } from '@chakra-ui/anatomy'; import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react'; +import { mode } from '@chakra-ui/theme-tools'; const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(parts.keys); -const invokeAITrack = defineStyle((_props) => { +const invokeAITrack = defineStyle((props) => { return { - bg: 'base.400', + bg: mode('base.400', 'base.600')(props), h: 1.5, }; }); @@ -14,23 +15,24 @@ const invokeAITrack = defineStyle((_props) => { const invokeAIFilledTrack = defineStyle((props) => { const { colorScheme: c } = props; return { - bg: `${c}.600`, + bg: mode(`${c}.400`, `${c}.600`)(props), h: 1.5, }; }); -const invokeAIThumb = defineStyle((_props) => { +const invokeAIThumb = defineStyle((props) => { return { w: 2, h: 4, + bg: mode('base.50', 'base.100')(props), }; }); -const invokeAIMark = defineStyle((_props) => { +const invokeAIMark = defineStyle((props) => { return { fontSize: 'xs', fontWeight: '500', - color: 'base.200', + color: mode('base.700', 'base.400')(props), mt: 2, insetInlineStart: 'unset', }; diff --git a/invokeai/frontend/web/src/theme/components/switch.ts b/invokeai/frontend/web/src/theme/components/switch.ts index 6f13888442..b803e58b3b 100644 --- a/invokeai/frontend/web/src/theme/components/switch.ts +++ b/invokeai/frontend/web/src/theme/components/switch.ts @@ -3,6 +3,7 @@ import { createMultiStyleConfigHelpers, defineStyle, } from '@chakra-ui/styled-system'; +import { mode } from '@chakra-ui/theme-tools'; const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(parts.keys); @@ -11,13 +12,13 @@ const invokeAITrack = defineStyle((props) => { const { colorScheme: c } = props; return { - bg: 'base.600', + bg: mode('base.300', 'base.600')(props), _focusVisible: { boxShadow: 'none', }, _checked: { - bg: `${c}.600`, + bg: mode(`${c}.400`, `${c}.500`)(props), }, }; }); @@ -26,7 +27,7 @@ const invokeAIThumb = defineStyle((props) => { const { colorScheme: c } = props; return { - bg: `${c}.50`, + bg: mode(`${c}.50`, `${c}.50`)(props), }; }); diff --git a/invokeai/frontend/web/src/theme/components/tabs.ts b/invokeai/frontend/web/src/theme/components/tabs.ts index 5eb1a36013..adcce73bbc 100644 --- a/invokeai/frontend/web/src/theme/components/tabs.ts +++ b/invokeai/frontend/web/src/theme/components/tabs.ts @@ -3,6 +3,7 @@ import { createMultiStyleConfigHelpers, defineStyle, } from '@chakra-ui/styled-system'; +import { mode } from '@chakra-ui/theme-tools'; const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(parts.keys); @@ -16,29 +17,53 @@ const invokeAIRoot = defineStyle((_props) => { const invokeAITab = defineStyle((_props) => ({})); -const invokeAITablist = defineStyle((_props) => ({ - display: 'flex', - flexDirection: 'column', - gap: 1, - color: 'base.700', - button: { - fontSize: 'sm', - padding: 2, - borderRadius: 'base', - _selected: { - bg: 'accent.700', - color: 'accent.100', +const invokeAITablist = defineStyle((props) => { + const { colorScheme: c } = props; + + return { + display: 'flex', + flexDirection: 'column', + gap: 1, + color: mode('base.700', 'base.400')(props), + button: { + fontSize: 'sm', + padding: 2, + borderRadius: 'base', + textShadow: mode( + `0 0 0.3rem var(--invokeai-colors-accent-100)`, + `0 0 0.3rem var(--invokeai-colors-accent-900)` + )(props), + svg: { + fill: mode('base.700', 'base.300')(props), + }, + _selected: { + bg: mode('accent.400', 'accent.600')(props), + color: mode('base.50', 'base.100')(props), + svg: { + fill: mode(`base.50`, `base.100`)(props), + filter: mode( + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`, + `drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))` + )(props), + }, + _hover: { + bg: mode('accent.500', 'accent.500')(props), + color: mode('white', 'base.50')(props), + svg: { + fill: mode('white', 'base.50')(props), + }, + }, + }, _hover: { - bg: 'accent.600', - color: 'accent.50', + bg: mode('base.100', 'base.800')(props), + color: mode('base.900', 'base.50')(props), + svg: { + fill: mode(`base.800`, `base.100`)(props), + }, }, }, - _hover: { - bg: 'base.600', - color: 'base.50', - }, - }, -})); + }; +}); const invokeAITabpanel = defineStyle((_props) => ({ padding: 0, @@ -58,5 +83,6 @@ export const tabsTheme = defineMultiStyleConfig({ }, defaultProps: { variant: 'invokeAI', + colorScheme: 'accent', }, }); diff --git a/invokeai/frontend/web/src/theme/components/text.ts b/invokeai/frontend/web/src/theme/components/text.ts index 88f5f23f7e..2404bf0594 100644 --- a/invokeai/frontend/web/src/theme/components/text.ts +++ b/invokeai/frontend/web/src/theme/components/text.ts @@ -1,7 +1,8 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/react'; +import { mode } from '@chakra-ui/theme-tools'; -const subtext = defineStyle((_props) => ({ - color: 'base.400', +const subtext = defineStyle((props) => ({ + color: mode('colors.base.500', 'colors.base.400')(props), })); export const textTheme = defineStyleConfig({ diff --git a/invokeai/frontend/web/src/theme/components/tooltip.ts b/invokeai/frontend/web/src/theme/components/tooltip.ts new file mode 100644 index 0000000000..511e0fecf0 --- /dev/null +++ b/invokeai/frontend/web/src/theme/components/tooltip.ts @@ -0,0 +1,17 @@ +import { defineStyle, defineStyleConfig } from '@chakra-ui/react'; +import { mode } from '@chakra-ui/theme-tools'; +import { cssVar } from '@chakra-ui/theme-tools'; + +const $arrowBg = cssVar('popper-arrow-bg'); + +// define the base component styles +const baseStyle = defineStyle((props) => ({ + borderRadius: 'base', + shadow: 'dark-lg', + bg: mode('base.700', 'base.200')(props), + [$arrowBg.variable]: mode('colors.base.700', 'colors.base.200')(props), + pb: 1.5, +})); + +// export the component theme +export const tooltipTheme = defineStyleConfig({ baseStyle }); diff --git a/invokeai/frontend/web/src/theme/css/overlayscrollbars.css b/invokeai/frontend/web/src/theme/css/overlayscrollbars.css index b5acaca75d..8f6f267095 100644 --- a/invokeai/frontend/web/src/theme/css/overlayscrollbars.css +++ b/invokeai/frontend/web/src/theme/css/overlayscrollbars.css @@ -8,11 +8,11 @@ /* The border radius of the scrollbar track */ /* --os-track-border-radius: 0; */ /* The background of the scrollbar track */ - --os-track-bg: rgba(0, 0, 0, 0.3); + /* --os-track-bg: rgba(0, 0, 0, 0.3); */ /* The :hover background of the scrollbar track */ - --os-track-bg-hover: rgba(0, 0, 0, 0.3); + /* --os-track-bg-hover: rgba(0, 0, 0, 0.3); */ /* The :active background of the scrollbar track */ - --os-track-bg-active: rgba(0, 0, 0, 0.3); + /* --os-track-bg-active: rgba(0, 0, 0, 0.3); */ /* The border of the scrollbar track */ /* --os-track-border: none; */ /* The :hover background of the scrollbar track */ @@ -22,11 +22,11 @@ /* The border radius of the scrollbar handle */ /* --os-handle-border-radius: 0; */ /* The background of the scrollbar handle */ - --os-handle-bg: var(--invokeai-colors-accent-500); + --os-handle-bg: var(--invokeai-colors-accentAlpha-500); /* The :hover background of the scrollbar handle */ - --os-handle-bg-hover: var(--invokeai-colors-accent-450); + --os-handle-bg-hover: var(--invokeai-colors-accentAlpha-700); /* The :active background of the scrollbar handle */ - --os-handle-bg-active: var(--invokeai-colors-accent-400); + --os-handle-bg-active: var(--invokeai-colors-accentAlpha-800); /* The border of the scrollbar handle */ /* --os-handle-border: none; */ /* The :hover border of the scrollbar handle */ diff --git a/invokeai/frontend/web/src/theme/theme.ts b/invokeai/frontend/web/src/theme/theme.ts index 90f26387ab..76b4aaaacc 100644 --- a/invokeai/frontend/web/src/theme/theme.ts +++ b/invokeai/frontend/web/src/theme/theme.ts @@ -1,7 +1,6 @@ import { ThemeOverride } from '@chakra-ui/react'; -import type { StyleFunctionProps } from '@chakra-ui/styled-system'; -import { invokeAIThemeColors } from 'theme/colors/invokeAI'; +import { InvokeAIColors } from './colors/colors'; import { accordionTheme } from './components/accordion'; import { buttonTheme } from './components/button'; import { checkboxTheme } from './components/checkbox'; @@ -12,13 +11,14 @@ import { modalTheme } from './components/modal'; import { numberInputTheme } from './components/numberInput'; import { popoverTheme } from './components/popover'; import { progressTheme } from './components/progress'; -import { no_scrollbar, scrollbar as _scrollbar } from './components/scrollbar'; +import { no_scrollbar } from './components/scrollbar'; import { selectTheme } from './components/select'; import { sliderTheme } from './components/slider'; import { switchTheme } from './components/switch'; import { tabsTheme } from './components/tabs'; import { textTheme } from './components/text'; import { textareaTheme } from './components/textarea'; +import { tooltipTheme } from './components/tooltip'; export const theme: ThemeOverride = { config: { @@ -26,30 +26,32 @@ export const theme: ThemeOverride = { initialColorMode: 'dark', useSystemColorMode: false, }, + layerStyles: { + body: { + bg: 'base.50', + color: 'base.900', + '.chakra-ui-dark &': { bg: 'base.900', color: 'base.50' }, + }, + first: { + bg: 'base.100', + color: 'base.900', + '.chakra-ui-dark &': { bg: 'base.850', color: 'base.100' }, + }, + second: { + bg: 'base.200', + color: 'base.900', + '.chakra-ui-dark &': { bg: 'base.800', color: 'base.100' }, + }, + }, styles: { - global: (_props: StyleFunctionProps) => ({ - body: { - bg: 'base.900', - color: 'base.50', - overflow: { - base: 'scroll', - xl: 'hidden', - }, - }, + global: () => ({ + layerStyle: 'body', '*': { ...no_scrollbar }, }), }, direction: 'ltr', fonts: { - body: `'InterVariable', sans-serif`, - }, - breakpoints: { - base: '0em', // 0px and onwards - sm: '30em', // 480px and onwards - md: '48em', // 768px and onwards - lg: '62em', // 992px and onwards - xl: '80em', // 1280px and onwards - '2xl': '96em', // 1536px and onwards + body: `'Inter Variable', sans-serif`, }, shadows: { light: { @@ -68,9 +70,7 @@ export const theme: ThemeOverride = { }, nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-base-500)`, }, - colors: { - ...invokeAIThemeColors, - }, + colors: InvokeAIColors, components: { Button: buttonTheme, // Button and IconButton Input: inputTheme, @@ -88,5 +88,6 @@ export const theme: ThemeOverride = { Checkbox: checkboxTheme, Menu: menuTheme, Text: textTheme, + Tooltip: tooltipTheme, }, }; diff --git a/invokeai/frontend/web/src/theme/themeTypes.d.ts b/invokeai/frontend/web/src/theme/themeTypes.d.ts index dce386168d..c85ebd33ce 100644 --- a/invokeai/frontend/web/src/theme/themeTypes.d.ts +++ b/invokeai/frontend/web/src/theme/themeTypes.d.ts @@ -1,11 +1,16 @@ export type InvokeAIThemeColors = { base: Partial; + baseAlpha: Partial; accent: Partial; + accentAlpha: Partial; working: Partial; + workingAlpha: Partial; warning: Partial; + warningAlpha: Partial; ok: Partial; + okAlpha: Partial; error: Partial; - gridLineColor: string; + errorAlpha: Partial; }; export type InvokeAIPaletteSteps = { diff --git a/invokeai/frontend/web/src/theme/util/generateColorPalette.ts b/invokeai/frontend/web/src/theme/util/generateColorPalette.ts index ed346c684a..6d90a070c0 100644 --- a/invokeai/frontend/web/src/theme/util/generateColorPalette.ts +++ b/invokeai/frontend/web/src/theme/util/generateColorPalette.ts @@ -2,57 +2,35 @@ import { InvokeAIPaletteSteps } from 'theme/themeTypes'; /** * Add two numbers together - * @param {String | Number} hue Hue of the color (0-360) - Reds 0, Greens 120, Blues 240 - * @param {String | Number} saturation Saturation of the color (0-100) - * @param {boolean} light True to generate light color palette + * @param {String | Number} H Hue of the color (0-360) - Reds 0, Greens 120, Blues 240 + * @param {String | Number} L Saturation of the color (0-100) + * @param {Boolean} alpha Whether or not to generate this palette as a transparency palette */ export function generateColorPalette( - hue: string | number, - saturation: string | number, - light = false + H: string | number, + S: string | number, + alpha = false ) { - hue = String(hue); - saturation = String(saturation); + H = String(H); + S = String(S); const colorSteps = Array.from({ length: 21 }, (_, i) => i * 50); + const lightnessSteps = [ - '0', - '5', - '10', - '15', - '20', - '25', - '30', - '35', - '40', - '45', - '50', - '55', - '59', - '64', - '68', - '73', - '77', - '82', - '86', - '95', - '100', + 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 59, 64, 68, 73, 77, 82, 86, + 95, 100, ]; - const darkPalette: Partial = {}; - const lightPalette: Partial = {}; + const p = colorSteps.reduce((palette, step, index) => { + const A = alpha ? lightnessSteps[index] / 100 : 1; - colorSteps.forEach((colorStep, index) => { - darkPalette[ - colorStep as keyof typeof darkPalette - ] = `hsl(${hue}, ${saturation}%, ${ - lightnessSteps[colorSteps.length - 1 - index] - }%)`; + // Lightness should be 50% for alpha colors + const L = alpha ? 50 : lightnessSteps[colorSteps.length - 1 - index]; - lightPalette[ - colorStep as keyof typeof lightPalette - ] = `hsl(${hue}, ${saturation}%, ${lightnessSteps[index]}%)`; - }); + palette[step as keyof typeof palette] = `hsl(${H} ${S}% ${L}% / ${A})`; - return light ? lightPalette : darkPalette; + return palette; + }, {} as InvokeAIPaletteSteps); + + return p; } diff --git a/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts b/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts index 85e9d109c5..8cf64cbd94 100644 --- a/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts +++ b/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts @@ -1,40 +1,40 @@ -import { StyleFunctionProps } from '@chakra-ui/theme-tools'; +import { StyleFunctionProps, mode } from '@chakra-ui/theme-tools'; -export const getInputOutlineStyles = (_props: StyleFunctionProps) => ({ +export const getInputOutlineStyles = (props: StyleFunctionProps) => ({ outline: 'none', borderWidth: 2, borderStyle: 'solid', - borderColor: 'base.800', - bg: 'base.900', + borderColor: mode('base.200', 'base.800')(props), + bg: mode('base.50', 'base.900')(props), borderRadius: 'base', - color: 'base.100', + color: mode('base.900', 'base.100')(props), boxShadow: 'none', _hover: { - borderColor: 'base.600', + borderColor: mode('base.300', 'base.600')(props), }, _focus: { - borderColor: 'accent.700', + borderColor: mode('accent.200', 'accent.600')(props), boxShadow: 'none', _hover: { - borderColor: 'accent.600', + borderColor: mode('accent.300', 'accent.500')(props), }, }, _invalid: { - borderColor: 'error.700', + borderColor: mode('error.300', 'error.600')(props), boxShadow: 'none', _hover: { - borderColor: 'error.600', + borderColor: mode('error.400', 'error.500')(props), }, }, _disabled: { - borderColor: 'base.700', - bg: 'base.700', - color: 'base.400', + borderColor: mode('base.300', 'base.700')(props), + bg: mode('base.300', 'base.700')(props), + color: mode('base.600', 'base.400')(props), _hover: { - borderColor: 'base.700', + borderColor: mode('base.300', 'base.700')(props), }, }, _placeholder: { - color: 'base.400', + color: mode('base.700', 'base.400')(props), }, }); diff --git a/invokeai/frontend/web/src/theme/util/mode.ts b/invokeai/frontend/web/src/theme/util/mode.ts new file mode 100644 index 0000000000..fbaabecb6c --- /dev/null +++ b/invokeai/frontend/web/src/theme/util/mode.ts @@ -0,0 +1,3 @@ +export const mode = + (light: string, dark: string) => (colorMode: 'light' | 'dark') => + colorMode === 'light' ? light : dark; diff --git a/static/dream_web/favicon.ico b/invokeai/frontend/web/static/dream_web/favicon.ico similarity index 100% rename from static/dream_web/favicon.ico rename to invokeai/frontend/web/static/dream_web/favicon.ico diff --git a/static/dream_web/index.css b/invokeai/frontend/web/static/dream_web/index.css similarity index 100% rename from static/dream_web/index.css rename to invokeai/frontend/web/static/dream_web/index.css diff --git a/static/dream_web/index.html b/invokeai/frontend/web/static/dream_web/index.html similarity index 100% rename from static/dream_web/index.html rename to invokeai/frontend/web/static/dream_web/index.html diff --git a/invokeai/frontend/web/static/dream_web/index.js b/invokeai/frontend/web/static/dream_web/index.js new file mode 100644 index 0000000000..438232f0c7 --- /dev/null +++ b/invokeai/frontend/web/static/dream_web/index.js @@ -0,0 +1,409 @@ +const socket = io(); + +var priorResultsLoadState = { + page: 0, + pages: 1, + per_page: 10, + total: 20, + offset: 0, // number of items generated since last load + loading: false, + initialized: false, +}; + +function loadPriorResults() { + // Fix next page by offset + let offsetPages = + priorResultsLoadState.offset / priorResultsLoadState.per_page; + priorResultsLoadState.page += offsetPages; + priorResultsLoadState.pages += offsetPages; + priorResultsLoadState.total += priorResultsLoadState.offset; + priorResultsLoadState.offset = 0; + + if (priorResultsLoadState.loading) { + return; + } + + if (priorResultsLoadState.page >= priorResultsLoadState.pages) { + return; // Nothing more to load + } + + // Load + priorResultsLoadState.loading = true; + let url = new URL('/api/images', document.baseURI); + url.searchParams.append( + 'page', + priorResultsLoadState.initialized + ? priorResultsLoadState.page + 1 + : priorResultsLoadState.page + ); + url.searchParams.append('per_page', priorResultsLoadState.per_page); + fetch(url.href, { + method: 'GET', + headers: new Headers({ 'content-type': 'application/json' }), + }) + .then((response) => response.json()) + .then((data) => { + priorResultsLoadState.page = data.page; + priorResultsLoadState.pages = data.pages; + priorResultsLoadState.per_page = data.per_page; + priorResultsLoadState.total = data.total; + + data.items.forEach(function (dreamId, index) { + let src = 'api/images/' + dreamId; + fetch('/api/images/' + dreamId + '/metadata', { + method: 'GET', + headers: new Headers({ 'content-type': 'application/json' }), + }) + .then((response) => response.json()) + .then((metadata) => { + let seed = metadata.seed || 0; // TODO: Parse old metadata + appendOutput(src, seed, metadata, true); + }); + }); + + // Load until page is full + if (!priorResultsLoadState.initialized) { + if (document.body.scrollHeight <= window.innerHeight) { + loadPriorResults(); + } + } + }) + .finally(() => { + priorResultsLoadState.loading = false; + priorResultsLoadState.initialized = true; + }); +} + +function resetForm() { + var form = document.getElementById('generate-form'); + form.querySelector('fieldset').removeAttribute('disabled'); +} + +function initProgress(totalSteps, showProgressImages) { + // TODO: Progress could theoretically come from multiple jobs at the same time (in the future) + let progressSectionEle = document.querySelector('#progress-section'); + progressSectionEle.style.display = 'initial'; + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('max', totalSteps); + + let progressImageEle = document.querySelector('#progress-image'); + progressImageEle.src = BLANK_IMAGE_URL; + progressImageEle.style.display = showProgressImages ? 'initial' : 'none'; +} + +function setProgress(step, totalSteps, src) { + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('value', step); + + if (src) { + let progressImageEle = document.querySelector('#progress-image'); + progressImageEle.src = src; + } +} + +function resetProgress(hide = true) { + if (hide) { + let progressSectionEle = document.querySelector('#progress-section'); + progressSectionEle.style.display = 'none'; + } + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('value', 0); +} + +function toBase64(file) { + return new Promise((resolve, reject) => { + const r = new FileReader(); + r.readAsDataURL(file); + r.onload = () => resolve(r.result); + r.onerror = (error) => reject(error); + }); +} + +function ondragdream(event) { + let dream = event.target.dataset.dream; + event.dataTransfer.setData('dream', dream); +} + +function seedClick(event) { + // Get element + var image = event.target.closest('figure').querySelector('img'); + var dream = JSON.parse(decodeURIComponent(image.dataset.dream)); + + let form = document.querySelector('#generate-form'); + for (const [k, v] of new FormData(form)) { + if (k == 'initimg') { + continue; + } + let formElem = form.querySelector(`*[name=${k}]`); + formElem.value = dream[k] !== undefined ? dream[k] : formElem.defaultValue; + } + + document.querySelector('#seed').value = dream.seed; + document.querySelector('#iterations').value = 1; // Reset to 1 iteration since we clicked a single image (not a full job) + + // NOTE: leaving this manual for the user for now - it was very confusing with this behavior + // document.querySelector("#with_variations").value = variations || ''; + // if (document.querySelector("#variation_amount").value <= 0) { + // document.querySelector("#variation_amount").value = 0.2; + // } + + saveFields(document.querySelector('#generate-form')); +} + +function appendOutput(src, seed, config, toEnd = false) { + let outputNode = document.createElement('figure'); + let altText = seed.toString() + ' | ' + config.prompt; + + // img needs width and height for lazy loading to work + // TODO: store the full config in a data attribute on the image? + const figureContents = ` +
+ ${altText} + +
${seed}
+ `; + + outputNode.innerHTML = figureContents; + + if (toEnd) { + document.querySelector('#results').append(outputNode); + } else { + document.querySelector('#results').prepend(outputNode); + } + document.querySelector('#no-results-message')?.remove(); +} + +function saveFields(form) { + for (const [k, v] of new FormData(form)) { + if (typeof v !== 'object') { + // Don't save 'file' type + localStorage.setItem(k, v); + } + } +} + +function loadFields(form) { + for (const [k, v] of new FormData(form)) { + const item = localStorage.getItem(k); + if (item != null) { + form.querySelector(`*[name=${k}]`).value = item; + } + } +} + +function clearFields(form) { + localStorage.clear(); + let prompt = form.prompt.value; + form.reset(); + form.prompt.value = prompt; +} + +const BLANK_IMAGE_URL = + 'data:image/svg+xml,'; +async function generateSubmit(form) { + // Convert file data to base64 + // TODO: Should probably uplaod files with formdata or something, and store them in the backend? + let formData = Object.fromEntries(new FormData(form)); + if (!formData.enable_generate && !formData.enable_init_image) { + gen_label = document.querySelector('label[for=enable_generate]').innerHTML; + initimg_label = document.querySelector( + 'label[for=enable_init_image]' + ).innerHTML; + alert(`Error: one of "${gen_label}" or "${initimg_label}" must be set`); + } + + formData.initimg_name = formData.initimg.name; + formData.initimg = + formData.initimg.name !== '' ? await toBase64(formData.initimg) : null; + + // Evaluate all checkboxes + let checkboxes = form.querySelectorAll('input[type=checkbox]'); + checkboxes.forEach(function (checkbox) { + if (checkbox.checked) { + formData[checkbox.name] = 'true'; + } + }); + + let strength = formData.strength; + let totalSteps = formData.initimg + ? Math.floor(strength * formData.steps) + : formData.steps; + let showProgressImages = formData.progress_images; + + // Set enabling flags + + // Initialize the progress bar + initProgress(totalSteps, showProgressImages); + + // POST, use response to listen for events + fetch(form.action, { + method: form.method, + headers: new Headers({ 'content-type': 'application/json' }), + body: JSON.stringify(formData), + }) + .then((response) => response.json()) + .then((data) => { + var jobId = data.jobId; + socket.emit('join_room', { room: jobId }); + }); + + form.querySelector('fieldset').setAttribute('disabled', ''); +} + +function fieldSetEnableChecked(event) { + cb = event.target; + fields = cb.closest('fieldset'); + fields.disabled = !cb.checked; +} + +// Socket listeners +socket.on('job_started', (data) => {}); + +socket.on('dream_result', (data) => { + var jobId = data.jobId; + var dreamId = data.dreamId; + var dreamRequest = data.dreamRequest; + var src = 'api/images/' + dreamId; + + priorResultsLoadState.offset += 1; + appendOutput(src, dreamRequest.seed, dreamRequest); + + resetProgress(false); +}); + +socket.on('dream_progress', (data) => { + // TODO: it'd be nice if we could get a seed reported here, but the generator would need to be updated + var step = data.step; + var totalSteps = data.totalSteps; + var jobId = data.jobId; + var dreamId = data.dreamId; + + var progressType = data.progressType; + if (progressType === 'GENERATION') { + var src = data.hasProgressImage + ? 'api/intermediates/' + dreamId + '/' + step + : null; + setProgress(step, totalSteps, src); + } else if (progressType === 'UPSCALING_STARTED') { + // step and totalSteps are used for upscale count on this message + document.getElementById('processing_cnt').textContent = step; + document.getElementById('processing_total').textContent = totalSteps; + document.getElementById('scaling-inprocess-message').style.display = + 'block'; + } else if (progressType == 'UPSCALING_DONE') { + document.getElementById('scaling-inprocess-message').style.display = 'none'; + } +}); + +socket.on('job_canceled', (data) => { + resetForm(); + resetProgress(); +}); + +socket.on('job_done', (data) => { + jobId = data.jobId; + socket.emit('leave_room', { room: jobId }); + + resetForm(); + resetProgress(); +}); + +window.onload = async () => { + document.querySelector('#prompt').addEventListener('keydown', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + const form = e.target.form; + generateSubmit(form); + } + }); + document.querySelector('#generate-form').addEventListener('submit', (e) => { + e.preventDefault(); + const form = e.target; + + generateSubmit(form); + }); + document.querySelector('#generate-form').addEventListener('change', (e) => { + saveFields(e.target.form); + }); + document.querySelector('#reset-seed').addEventListener('click', (e) => { + document.querySelector('#seed').value = 0; + saveFields(e.target.form); + }); + document.querySelector('#reset-all').addEventListener('click', (e) => { + clearFields(e.target.form); + }); + document.querySelector('#remove-image').addEventListener('click', (e) => { + initimg.value = null; + }); + loadFields(document.querySelector('#generate-form')); + + document.querySelector('#cancel-button').addEventListener('click', () => { + fetch('/api/cancel').catch((e) => { + console.error(e); + }); + }); + document.documentElement.addEventListener('keydown', (e) => { + if (e.key === 'Escape') + fetch('/api/cancel').catch((err) => { + console.error(err); + }); + }); + + if (!config.gfpgan_model_exists) { + document.querySelector('#gfpgan').style.display = 'none'; + } + + window.addEventListener('scroll', () => { + if (window.innerHeight + window.pageYOffset >= document.body.offsetHeight) { + loadPriorResults(); + } + }); + + // Enable/disable forms by checkboxes + document + .querySelectorAll('legend > input[type=checkbox]') + .forEach(function (cb) { + cb.addEventListener('change', fieldSetEnableChecked); + fieldSetEnableChecked({ target: cb }); + }); + + // Load some of the previous results + loadPriorResults(); + + // Image drop/upload WIP + /* + let drop = document.getElementById('dropper'); + function ondrop(event) { + let dreamData = event.dataTransfer.getData('dream'); + if (dreamData) { + var dream = JSON.parse(decodeURIComponent(dreamData)); + alert(dream.dreamId); + } + }; + + function ondragenter(event) { + event.preventDefault(); + }; + + function ondragover(event) { + event.preventDefault(); + }; + + function ondragleave(event) { + + } + + drop.addEventListener('drop', ondrop); + drop.addEventListener('dragenter', ondragenter); + drop.addEventListener('dragover', ondragover); + drop.addEventListener('dragleave', ondragleave); + */ +}; diff --git a/invokeai/frontend/web/static/dream_web/test.html b/invokeai/frontend/web/static/dream_web/test.html new file mode 100644 index 0000000000..cbb746a5a1 --- /dev/null +++ b/invokeai/frontend/web/static/dream_web/test.html @@ -0,0 +1,246 @@ + + + InvokeAI Test + + + + + + + + + + + + + + + +
+ +
+ + + + diff --git a/static/legacy_web/favicon.ico b/invokeai/frontend/web/static/legacy_web/favicon.ico similarity index 100% rename from static/legacy_web/favicon.ico rename to invokeai/frontend/web/static/legacy_web/favicon.ico diff --git a/static/legacy_web/index.css b/invokeai/frontend/web/static/legacy_web/index.css similarity index 100% rename from static/legacy_web/index.css rename to invokeai/frontend/web/static/legacy_web/index.css diff --git a/static/legacy_web/index.html b/invokeai/frontend/web/static/legacy_web/index.html similarity index 100% rename from static/legacy_web/index.html rename to invokeai/frontend/web/static/legacy_web/index.html diff --git a/invokeai/frontend/web/static/legacy_web/index.js b/invokeai/frontend/web/static/legacy_web/index.js new file mode 100644 index 0000000000..a150f3f2e9 --- /dev/null +++ b/invokeai/frontend/web/static/legacy_web/index.js @@ -0,0 +1,234 @@ +function toBase64(file) { + return new Promise((resolve, reject) => { + const r = new FileReader(); + r.readAsDataURL(file); + r.onload = () => resolve(r.result); + r.onerror = (error) => reject(error); + }); +} + +function appendOutput(src, seed, config) { + let outputNode = document.createElement('figure'); + + let variations = config.with_variations; + if (config.variation_amount > 0) { + variations = + (variations ? variations + ',' : '') + + seed + + ':' + + config.variation_amount; + } + let baseseed = + config.with_variations || config.variation_amount > 0 ? config.seed : seed; + let altText = + baseseed + ' | ' + (variations ? variations + ' | ' : '') + config.prompt; + + // img needs width and height for lazy loading to work + const figureContents = ` + + ${altText} + +
${seed}
+ `; + + outputNode.innerHTML = figureContents; + let figcaption = outputNode.querySelector('figcaption'); + + // Reload image config + figcaption.addEventListener('click', () => { + let form = document.querySelector('#generate-form'); + for (const [k, v] of new FormData(form)) { + if (k == 'initimg') { + continue; + } + form.querySelector(`*[name=${k}]`).value = config[k]; + } + + document.querySelector('#seed').value = baseseed; + document.querySelector('#with_variations').value = variations || ''; + if (document.querySelector('#variation_amount').value <= 0) { + document.querySelector('#variation_amount').value = 0.2; + } + + saveFields(document.querySelector('#generate-form')); + }); + + document.querySelector('#results').prepend(outputNode); +} + +function saveFields(form) { + for (const [k, v] of new FormData(form)) { + if (typeof v !== 'object') { + // Don't save 'file' type + localStorage.setItem(k, v); + } + } +} + +function loadFields(form) { + for (const [k, v] of new FormData(form)) { + const item = localStorage.getItem(k); + if (item != null) { + form.querySelector(`*[name=${k}]`).value = item; + } + } +} + +function clearFields(form) { + localStorage.clear(); + let prompt = form.prompt.value; + form.reset(); + form.prompt.value = prompt; +} + +const BLANK_IMAGE_URL = + 'data:image/svg+xml,'; +async function generateSubmit(form) { + const prompt = document.querySelector('#prompt').value; + + // Convert file data to base64 + let formData = Object.fromEntries(new FormData(form)); + formData.initimg_name = formData.initimg.name; + formData.initimg = + formData.initimg.name !== '' ? await toBase64(formData.initimg) : null; + + let strength = formData.strength; + let totalSteps = formData.initimg + ? Math.floor(strength * formData.steps) + : formData.steps; + + let progressSectionEle = document.querySelector('#progress-section'); + progressSectionEle.style.display = 'initial'; + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('max', totalSteps); + let progressImageEle = document.querySelector('#progress-image'); + progressImageEle.src = BLANK_IMAGE_URL; + + progressImageEle.style.display = {}.hasOwnProperty.call( + formData, + 'progress_images' + ) + ? 'initial' + : 'none'; + + // Post as JSON, using Fetch streaming to get results + fetch(form.action, { + method: form.method, + body: JSON.stringify(formData), + }).then(async (response) => { + const reader = response.body.getReader(); + + let noOutputs = true; + while (true) { + let { value, done } = await reader.read(); + value = new TextDecoder().decode(value); + if (done) { + progressSectionEle.style.display = 'none'; + break; + } + + for (let event of value.split('\n').filter((e) => e !== '')) { + const data = JSON.parse(event); + + if (data.event === 'result') { + noOutputs = false; + appendOutput(data.url, data.seed, data.config); + progressEle.setAttribute('value', 0); + progressEle.setAttribute('max', totalSteps); + } else if (data.event === 'upscaling-started') { + document.getElementById('processing_cnt').textContent = + data.processed_file_cnt; + document.getElementById('scaling-inprocess-message').style.display = + 'block'; + } else if (data.event === 'upscaling-done') { + document.getElementById('scaling-inprocess-message').style.display = + 'none'; + } else if (data.event === 'step') { + progressEle.setAttribute('value', data.step); + if (data.url) { + progressImageEle.src = data.url; + } + } else if (data.event === 'canceled') { + // avoid alerting as if this were an error case + noOutputs = false; + } + } + } + + // Re-enable form, remove no-results-message + form.querySelector('fieldset').removeAttribute('disabled'); + document.querySelector('#prompt').value = prompt; + document.querySelector('progress').setAttribute('value', '0'); + + if (noOutputs) { + alert('Error occurred while generating.'); + } + }); + + // Disable form while generating + form.querySelector('fieldset').setAttribute('disabled', ''); + document.querySelector('#prompt').value = `Generating: "${prompt}"`; +} + +async function fetchRunLog() { + try { + let response = await fetch('/run_log.json'); + const data = await response.json(); + for (let item of data.run_log) { + appendOutput(item.url, item.seed, item); + } + } catch (e) { + console.error(e); + } +} + +window.onload = async () => { + document.querySelector('#prompt').addEventListener('keydown', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + const form = e.target.form; + generateSubmit(form); + } + }); + document.querySelector('#generate-form').addEventListener('submit', (e) => { + e.preventDefault(); + const form = e.target; + + generateSubmit(form); + }); + document.querySelector('#generate-form').addEventListener('change', (e) => { + saveFields(e.target.form); + }); + document.querySelector('#reset-seed').addEventListener('click', (e) => { + document.querySelector('#seed').value = -1; + saveFields(e.target.form); + }); + document.querySelector('#reset-all').addEventListener('click', (e) => { + clearFields(e.target.form); + }); + document.querySelector('#remove-image').addEventListener('click', (e) => { + initimg.value = null; + }); + loadFields(document.querySelector('#generate-form')); + + document.querySelector('#cancel-button').addEventListener('click', () => { + fetch('/cancel').catch((e) => { + console.error(e); + }); + }); + document.documentElement.addEventListener('keydown', (e) => { + if (e.key === 'Escape') + fetch('/cancel').catch((err) => { + console.error(err); + }); + }); + + if (!config.gfpgan_model_exists) { + document.querySelector('#gfpgan').style.display = 'none'; + } + await fetchRunLog(); +}; diff --git a/invokeai/frontend/web/stats.html b/invokeai/frontend/web/stats.html index 589e02298c..dc999e13df 100644 --- a/invokeai/frontend/web/stats.html +++ b/invokeai/frontend/web/stats.html @@ -6157,7 +6157,7 @@ var drawChart = (function (exports) { - - - - - - - - - -
- -
- - - - - \ No newline at end of file diff --git a/static/legacy_web/index.js b/static/legacy_web/index.js deleted file mode 100644 index 57ad076062..0000000000 --- a/static/legacy_web/index.js +++ /dev/null @@ -1,213 +0,0 @@ -function toBase64(file) { - return new Promise((resolve, reject) => { - const r = new FileReader(); - r.readAsDataURL(file); - r.onload = () => resolve(r.result); - r.onerror = (error) => reject(error); - }); -} - -function appendOutput(src, seed, config) { - let outputNode = document.createElement("figure"); - - let variations = config.with_variations; - if (config.variation_amount > 0) { - variations = (variations ? variations + ',' : '') + seed + ':' + config.variation_amount; - } - let baseseed = (config.with_variations || config.variation_amount > 0) ? config.seed : seed; - let altText = baseseed + ' | ' + (variations ? variations + ' | ' : '') + config.prompt; - - // img needs width and height for lazy loading to work - const figureContents = ` - - ${altText} - -
${seed}
- `; - - outputNode.innerHTML = figureContents; - let figcaption = outputNode.querySelector('figcaption'); - - // Reload image config - figcaption.addEventListener('click', () => { - let form = document.querySelector("#generate-form"); - for (const [k, v] of new FormData(form)) { - if (k == 'initimg') { continue; } - form.querySelector(`*[name=${k}]`).value = config[k]; - } - - document.querySelector("#seed").value = baseseed; - document.querySelector("#with_variations").value = variations || ''; - if (document.querySelector("#variation_amount").value <= 0) { - document.querySelector("#variation_amount").value = 0.2; - } - - saveFields(document.querySelector("#generate-form")); - }); - - document.querySelector("#results").prepend(outputNode); -} - -function saveFields(form) { - for (const [k, v] of new FormData(form)) { - if (typeof v !== 'object') { // Don't save 'file' type - localStorage.setItem(k, v); - } - } -} - -function loadFields(form) { - for (const [k, v] of new FormData(form)) { - const item = localStorage.getItem(k); - if (item != null) { - form.querySelector(`*[name=${k}]`).value = item; - } - } -} - -function clearFields(form) { - localStorage.clear(); - let prompt = form.prompt.value; - form.reset(); - form.prompt.value = prompt; -} - -const BLANK_IMAGE_URL = 'data:image/svg+xml,'; -async function generateSubmit(form) { - const prompt = document.querySelector("#prompt").value; - - // Convert file data to base64 - let formData = Object.fromEntries(new FormData(form)); - formData.initimg_name = formData.initimg.name - formData.initimg = formData.initimg.name !== '' ? await toBase64(formData.initimg) : null; - - let strength = formData.strength; - let totalSteps = formData.initimg ? Math.floor(strength * formData.steps) : formData.steps; - - let progressSectionEle = document.querySelector('#progress-section'); - progressSectionEle.style.display = 'initial'; - let progressEle = document.querySelector('#progress-bar'); - progressEle.setAttribute('max', totalSteps); - let progressImageEle = document.querySelector('#progress-image'); - progressImageEle.src = BLANK_IMAGE_URL; - - progressImageEle.style.display = {}.hasOwnProperty.call(formData, 'progress_images') ? 'initial': 'none'; - - // Post as JSON, using Fetch streaming to get results - fetch(form.action, { - method: form.method, - body: JSON.stringify(formData), - }).then(async (response) => { - const reader = response.body.getReader(); - - let noOutputs = true; - while (true) { - let {value, done} = await reader.read(); - value = new TextDecoder().decode(value); - if (done) { - progressSectionEle.style.display = 'none'; - break; - } - - for (let event of value.split('\n').filter(e => e !== '')) { - const data = JSON.parse(event); - - if (data.event === 'result') { - noOutputs = false; - appendOutput(data.url, data.seed, data.config); - progressEle.setAttribute('value', 0); - progressEle.setAttribute('max', totalSteps); - } else if (data.event === 'upscaling-started') { - document.getElementById("processing_cnt").textContent=data.processed_file_cnt; - document.getElementById("scaling-inprocess-message").style.display = "block"; - } else if (data.event === 'upscaling-done') { - document.getElementById("scaling-inprocess-message").style.display = "none"; - } else if (data.event === 'step') { - progressEle.setAttribute('value', data.step); - if (data.url) { - progressImageEle.src = data.url; - } - } else if (data.event === 'canceled') { - // avoid alerting as if this were an error case - noOutputs = false; - } - } - } - - // Re-enable form, remove no-results-message - form.querySelector('fieldset').removeAttribute('disabled'); - document.querySelector("#prompt").value = prompt; - document.querySelector('progress').setAttribute('value', '0'); - - if (noOutputs) { - alert("Error occurred while generating."); - } - }); - - // Disable form while generating - form.querySelector('fieldset').setAttribute('disabled',''); - document.querySelector("#prompt").value = `Generating: "${prompt}"`; -} - -async function fetchRunLog() { - try { - let response = await fetch('/run_log.json') - const data = await response.json(); - for(let item of data.run_log) { - appendOutput(item.url, item.seed, item); - } - } catch (e) { - console.error(e); - } -} - -window.onload = async () => { - document.querySelector("#prompt").addEventListener("keydown", (e) => { - if (e.key === "Enter" && !e.shiftKey) { - const form = e.target.form; - generateSubmit(form); - } - }); - document.querySelector("#generate-form").addEventListener('submit', (e) => { - e.preventDefault(); - const form = e.target; - - generateSubmit(form); - }); - document.querySelector("#generate-form").addEventListener('change', (e) => { - saveFields(e.target.form); - }); - document.querySelector("#reset-seed").addEventListener('click', (e) => { - document.querySelector("#seed").value = -1; - saveFields(e.target.form); - }); - document.querySelector("#reset-all").addEventListener('click', (e) => { - clearFields(e.target.form); - }); - document.querySelector("#remove-image").addEventListener('click', (e) => { - initimg.value=null; - }); - loadFields(document.querySelector("#generate-form")); - - document.querySelector('#cancel-button').addEventListener('click', () => { - fetch('/cancel').catch(e => { - console.error(e); - }); - }); - document.documentElement.addEventListener('keydown', (e) => { - if (e.key === "Escape") - fetch('/cancel').catch(err => { - console.error(err); - }); - }); - - if (!config.gfpgan_model_exists) { - document.querySelector("#gfpgan").style.display = 'none'; - } - await fetchRunLog() -}; diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..06502b6c41 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,30 @@ +import pytest +from invokeai.app.services.invocation_services import InvocationServices +from invokeai.app.services.invocation_queue import MemoryInvocationQueue +from invokeai.app.services.sqlite import SqliteItemStorage, sqlite_memory +from invokeai.app.services.graph import LibraryGraph, GraphExecutionState +from invokeai.app.services.processor import DefaultInvocationProcessor + +# Ignore these files as they need to be rewritten following the model manager refactor +collect_ignore = ["nodes/test_graph_execution_state.py", "nodes/test_node_graph.py", "test_textual_inversion.py"] + +@pytest.fixture(scope="session", autouse=True) +def mock_services(): + # NOTE: none of these are actually called by the test invocations + return InvocationServices( + model_manager = None, # type: ignore + events = None, # type: ignore + logger = None, # type: ignore + images = None, # type: ignore + latents = None, # type: ignore + board_images=None, # type: ignore + boards=None, # type: ignore + queue = MemoryInvocationQueue(), + graph_library=SqliteItemStorage[LibraryGraph]( + filename=sqlite_memory, table_name="graphs" + ), + graph_execution_manager = SqliteItemStorage[GraphExecutionState](filename = sqlite_memory, table_name = 'graph_executions'), + processor = DefaultInvocationProcessor(), + restoration = None, # type: ignore + configuration = None, # type: ignore + ) diff --git a/tests/nodes/test_graph_execution_state.py b/tests/nodes/test_graph_execution_state.py index 9f433aa330..df8964da18 100644 --- a/tests/nodes/test_graph_execution_state.py +++ b/tests/nodes/test_graph_execution_state.py @@ -1,14 +1,18 @@ -from .test_invoker import create_edge -from .test_nodes import ImageTestInvocation, ListPassThroughInvocation, PromptTestInvocation, PromptCollectionTestInvocation -from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext +import pytest + +from invokeai.app.invocations.baseinvocation import (BaseInvocation, + BaseInvocationOutput, + InvocationContext) from invokeai.app.invocations.collections import RangeInvocation from invokeai.app.invocations.math import AddInvocation, MultiplyInvocation -from invokeai.app.services.processor import DefaultInvocationProcessor -from invokeai.app.services.sqlite import SqliteItemStorage, sqlite_memory -from invokeai.app.services.invocation_queue import MemoryInvocationQueue +from invokeai.app.services.graph import (CollectInvocation, Graph, + GraphExecutionState, + IterateInvocation) from invokeai.app.services.invocation_services import InvocationServices -from invokeai.app.services.graph import Graph, GraphInvocation, InvalidEdgeError, LibraryGraph, NodeAlreadyInGraphError, NodeNotFoundError, are_connections_compatible, EdgeConnection, CollectInvocation, IterateInvocation, GraphExecutionState -import pytest + +from .test_invoker import create_edge +from .test_nodes import (ImageTestInvocation, PromptCollectionTestInvocation, + PromptTestInvocation) @pytest.fixture @@ -19,30 +23,11 @@ def simple_graph(): g.add_edge(create_edge("1", "prompt", "2", "prompt")) return g -@pytest.fixture -def mock_services(): - # NOTE: none of these are actually called by the test invocations - return InvocationServices( - model_manager = None, # type: ignore - events = None, # type: ignore - logger = None, # type: ignore - images = None, # type: ignore - latents = None, # type: ignore - queue = MemoryInvocationQueue(), - graph_library=SqliteItemStorage[LibraryGraph]( - filename=sqlite_memory, table_name="graphs" - ), - graph_execution_manager = SqliteItemStorage[GraphExecutionState](filename = sqlite_memory, table_name = 'graph_executions'), - processor = DefaultInvocationProcessor(), - restoration = None, # type: ignore - configuration = None, # type: ignore - ) - def invoke_next(g: GraphExecutionState, services: InvocationServices) -> tuple[BaseInvocation, BaseInvocationOutput]: n = g.next() if n is None: return (None, None) - + print(f'invoking {n.id}: {type(n)}') o = n.invoke(InvocationContext(services, "1")) g.complete(n.id, o) @@ -51,7 +36,7 @@ def invoke_next(g: GraphExecutionState, services: InvocationServices) -> tuple[B def test_graph_state_executes_in_order(simple_graph, mock_services): g = GraphExecutionState(graph = simple_graph) - + n1 = invoke_next(g, mock_services) n2 = invoke_next(g, mock_services) n3 = g.next() @@ -88,11 +73,11 @@ def test_graph_state_expands_iterator(mock_services): graph.add_edge(create_edge("0", "collection", "1", "collection")) graph.add_edge(create_edge("1", "item", "2", "a")) graph.add_edge(create_edge("2", "a", "3", "a")) - + g = GraphExecutionState(graph = graph) while not g.is_complete(): invoke_next(g, mock_services) - + prepared_add_nodes = g.source_prepared_mapping['3'] results = set([g.results[n].a for n in prepared_add_nodes]) expected = set([1, 11, 21]) @@ -109,7 +94,7 @@ def test_graph_state_collects(mock_services): graph.add_edge(create_edge("1", "collection", "2", "collection")) graph.add_edge(create_edge("2", "item", "3", "prompt")) graph.add_edge(create_edge("3", "prompt", "4", "item")) - + g = GraphExecutionState(graph = graph) n1 = invoke_next(g, mock_services) n2 = invoke_next(g, mock_services) @@ -121,3 +106,78 @@ def test_graph_state_collects(mock_services): assert isinstance(n6[0], CollectInvocation) assert sorted(g.results[n6[0].id].collection) == sorted(test_prompts) + + +def test_graph_state_prepares_eagerly(mock_services): + """Tests that all prepareable nodes are prepared""" + graph = Graph() + + test_prompts = ["Banana sushi", "Cat sushi"] + graph.add_node(PromptCollectionTestInvocation(id="prompt_collection", collection=list(test_prompts))) + graph.add_node(IterateInvocation(id="iterate")) + graph.add_node(PromptTestInvocation(id="prompt_iterated")) + graph.add_edge(create_edge("prompt_collection", "collection", "iterate", "collection")) + graph.add_edge(create_edge("iterate", "item", "prompt_iterated", "prompt")) + + # separated, fully-preparable chain of nodes + graph.add_node(PromptTestInvocation(id="prompt_chain_1", prompt="Dinosaur sushi")) + graph.add_node(PromptTestInvocation(id="prompt_chain_2")) + graph.add_node(PromptTestInvocation(id="prompt_chain_3")) + graph.add_edge(create_edge("prompt_chain_1", "prompt", "prompt_chain_2", "prompt")) + graph.add_edge(create_edge("prompt_chain_2", "prompt", "prompt_chain_3", "prompt")) + + g = GraphExecutionState(graph=graph) + g.next() + + assert "prompt_collection" in g.source_prepared_mapping + assert "prompt_chain_1" in g.source_prepared_mapping + assert "prompt_chain_2" in g.source_prepared_mapping + assert "prompt_chain_3" in g.source_prepared_mapping + assert "iterate" not in g.source_prepared_mapping + assert "prompt_iterated" not in g.source_prepared_mapping + + +def test_graph_executes_depth_first(mock_services): + """Tests that the graph executes depth-first, executing a branch as far as possible before moving to the next branch""" + graph = Graph() + + test_prompts = ["Banana sushi", "Cat sushi"] + graph.add_node(PromptCollectionTestInvocation(id="prompt_collection", collection=list(test_prompts))) + graph.add_node(IterateInvocation(id="iterate")) + graph.add_node(PromptTestInvocation(id="prompt_iterated")) + graph.add_node(PromptTestInvocation(id="prompt_successor")) + graph.add_edge(create_edge("prompt_collection", "collection", "iterate", "collection")) + graph.add_edge(create_edge("iterate", "item", "prompt_iterated", "prompt")) + graph.add_edge(create_edge("prompt_iterated", "prompt", "prompt_successor", "prompt")) + + g = GraphExecutionState(graph=graph) + n1 = invoke_next(g, mock_services) + n2 = invoke_next(g, mock_services) + n3 = invoke_next(g, mock_services) + n4 = invoke_next(g, mock_services) + + # Because ordering is not guaranteed, we cannot compare results directly. + # Instead, we must count the number of results. + def get_completed_count(g, id): + ids = [i for i in g.source_prepared_mapping[id]] + completed_ids = [i for i in g.executed if i in ids] + return len(completed_ids) + + # Check at each step that the number of executed nodes matches the expectation for depth-first execution + assert get_completed_count(g, "prompt_iterated") == 1 + assert get_completed_count(g, "prompt_successor") == 0 + + n5 = invoke_next(g, mock_services) + + assert get_completed_count(g, "prompt_iterated") == 1 + assert get_completed_count(g, "prompt_successor") == 1 + + n6 = invoke_next(g, mock_services) + + assert get_completed_count(g, "prompt_iterated") == 2 + assert get_completed_count(g, "prompt_successor") == 1 + + n7 = invoke_next(g, mock_services) + + assert get_completed_count(g, "prompt_iterated") == 2 + assert get_completed_count(g, "prompt_successor") == 2 diff --git a/tests/nodes/test_invoker.py b/tests/nodes/test_invoker.py index 6e1dde716c..4331e62d21 100644 --- a/tests/nodes/test_invoker.py +++ b/tests/nodes/test_invoker.py @@ -1,13 +1,12 @@ -from .test_nodes import ErrorInvocation, ImageTestInvocation, ListPassThroughInvocation, PromptTestInvocation, PromptCollectionTestInvocation, TestEventService, create_edge, wait_until -from invokeai.app.services.processor import DefaultInvocationProcessor -from invokeai.app.services.sqlite import SqliteItemStorage, sqlite_memory -from invokeai.app.services.invocation_queue import MemoryInvocationQueue -from invokeai.app.services.invoker import Invoker -from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext -from invokeai.app.services.invocation_services import InvocationServices -from invokeai.app.services.graph import Graph, GraphInvocation, InvalidEdgeError, LibraryGraph, NodeAlreadyInGraphError, NodeNotFoundError, are_connections_compatible, EdgeConnection, CollectInvocation, IterateInvocation, GraphExecutionState import pytest +from invokeai.app.services.graph import Graph, GraphExecutionState +from invokeai.app.services.invocation_services import InvocationServices +from invokeai.app.services.invoker import Invoker + +from .test_nodes import (ErrorInvocation, ImageTestInvocation, + PromptTestInvocation, create_edge, wait_until) + @pytest.fixture def simple_graph(): @@ -17,25 +16,6 @@ def simple_graph(): g.add_edge(create_edge("1", "prompt", "2", "prompt")) return g -@pytest.fixture -def mock_services() -> InvocationServices: - # NOTE: none of these are actually called by the test invocations - return InvocationServices( - model_manager = None, # type: ignore - events = TestEventService(), - logger = None, # type: ignore - images = None, # type: ignore - latents = None, # type: ignore - queue = MemoryInvocationQueue(), - graph_library=SqliteItemStorage[LibraryGraph]( - filename=sqlite_memory, table_name="graphs" - ), - graph_execution_manager = SqliteItemStorage[GraphExecutionState](filename = sqlite_memory, table_name = 'graph_executions'), - processor = DefaultInvocationProcessor(), - restoration = None, # type: ignore - configuration = None, # type: ignore - ) - @pytest.fixture() def mock_invoker(mock_services: InvocationServices) -> Invoker: return Invoker( @@ -57,6 +37,7 @@ def test_can_create_graph_state_from_graph(mock_invoker: Invoker, simple_graph): assert isinstance(g, GraphExecutionState) assert g.graph == simple_graph +@pytest.mark.xfail(reason = "Requires fixing following the model manager refactor") def test_can_invoke(mock_invoker: Invoker, simple_graph): g = mock_invoker.create_execution_state(graph = simple_graph) invocation_id = mock_invoker.invoke(g) @@ -72,6 +53,7 @@ def test_can_invoke(mock_invoker: Invoker, simple_graph): g = mock_invoker.services.graph_execution_manager.get(g.id) assert len(g.executed) > 0 +@pytest.mark.xfail(reason = "Requires fixing following the model manager refactor") def test_can_invoke_all(mock_invoker: Invoker, simple_graph): g = mock_invoker.create_execution_state(graph = simple_graph) invocation_id = mock_invoker.invoke(g, invoke_all = True) @@ -87,6 +69,7 @@ def test_can_invoke_all(mock_invoker: Invoker, simple_graph): g = mock_invoker.services.graph_execution_manager.get(g.id) assert g.is_complete() +@pytest.mark.xfail(reason = "Requires fixing following the model manager refactor") def test_handles_errors(mock_invoker: Invoker): g = mock_invoker.create_execution_state() g.graph.add_node(ErrorInvocation(id = "1")) diff --git a/tests/test_config.py b/tests/test_config.py index 6d0586213e..cea4991d12 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,12 +1,13 @@ import os import pytest +import sys from omegaconf import OmegaConf from pathlib import Path os.environ['INVOKEAI_ROOT']='/tmp' -from invokeai.app.services.config import InvokeAIAppConfig, InvokeAISettings -from invokeai.app.invocations.generate import TextToImageInvocation + +from invokeai.app.services.config import InvokeAIAppConfig init1 = OmegaConf.create( ''' @@ -32,48 +33,56 @@ def test_use_init(): # note that we explicitly set omegaconf dict and argv here # so that the values aren't read from ~invokeai/invokeai.yaml and # sys.argv respectively. - conf1 = InvokeAIAppConfig(init1,[]) + conf1 = InvokeAIAppConfig.get_config() assert conf1 + conf1.parse_args(conf=init1,argv=[]) assert conf1.max_loaded_models==5 assert not conf1.nsfw_checker - conf2 = InvokeAIAppConfig(init2,[]) + conf2 = InvokeAIAppConfig.get_config() assert conf2 + conf2.parse_args(conf=init2,argv=[]) assert conf2.nsfw_checker assert conf2.max_loaded_models==2 assert not hasattr(conf2,'invalid_attribute') def test_argv_override(): - conf = InvokeAIAppConfig(init1,['--nsfw_checker','--max_loaded=10']) + conf = InvokeAIAppConfig.get_config() + conf.parse_args(conf=init1,argv=['--nsfw_checker','--max_loaded=10']) assert conf.nsfw_checker assert conf.max_loaded_models==10 assert conf.outdir==Path('outputs') # this is the default def test_env_override(): # argv overrides - conf = InvokeAIAppConfig(conf=init1,argv=['--max_loaded=10']) + conf = InvokeAIAppConfig() + conf.parse_args(conf=init1,argv=['--max_loaded=10']) assert conf.nsfw_checker==False - os.environ['INVOKEAI_nsfw_checker'] = 'True' - conf = InvokeAIAppConfig(conf=init1,argv=['--max_loaded=10']) + conf.parse_args(conf=init1,argv=['--max_loaded=10']) assert conf.nsfw_checker==True # environment variables should be case insensitive os.environ['InvokeAI_Max_Loaded_Models'] = '15' - conf = InvokeAIAppConfig(conf=init1) + conf = InvokeAIAppConfig() + conf.parse_args(conf=init1,argv=[]) assert conf.max_loaded_models == 15 - conf = InvokeAIAppConfig(conf=init1,argv=['--no-nsfw_checker','--max_loaded=10']) + conf = InvokeAIAppConfig() + conf.parse_args(conf=init1,argv=['--no-nsfw_checker','--max_loaded=10']) assert conf.nsfw_checker==False assert conf.max_loaded_models==10 - conf = InvokeAIAppConfig(conf=init1,argv=[],max_loaded_models=20) + conf = InvokeAIAppConfig.get_config(max_loaded_models=20) + conf.parse_args(conf=init1,argv=[]) assert conf.max_loaded_models==20 def test_type_coercion(): - conf = InvokeAIAppConfig(argv=['--root=/tmp/foobar']) + conf = InvokeAIAppConfig().get_config() + conf.parse_args(argv=['--root=/tmp/foobar']) assert conf.root==Path('/tmp/foobar') assert isinstance(conf.root,Path) - conf = InvokeAIAppConfig(argv=['--root=/tmp/foobar'],root='/tmp/different') + conf = InvokeAIAppConfig.get_config(root='/tmp/different') + conf.parse_args(argv=['--root=/tmp/foobar']) assert conf.root==Path('/tmp/different') assert isinstance(conf.root,Path)