Merge branch 'main' into installer/platform-specific-help

This commit is contained in:
Lincoln Stein 2023-02-07 20:25:02 -05:00 committed by GitHub
commit c5b4397212
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
302 changed files with 11268 additions and 4088 deletions

View File

@ -1,18 +1,21 @@
# use this file as a whitelist # use this file as a whitelist
* *
!backend
!invokeai !invokeai
!ldm !ldm
!pyproject.toml !pyproject.toml
!README.md !README.md
!scripts
# Guard against pulling in any models that might exist in the directory tree # Guard against pulling in any models that might exist in the directory tree
**/*.pt* **/*.pt*
**/*.ckpt **/*.ckpt
# whitelist frontend, but ignore node_modules # ignore frontend but whitelist dist
invokeai/frontend/node_modules invokeai/frontend/**
!invokeai/frontend/dist
# ignore invokeai/assets but whitelist invokeai/assets/web
invokeai/assets
!invokeai/assets/web
# ignore python cache # ignore python cache
**/__pycache__ **/__pycache__

View File

@ -3,6 +3,7 @@ on:
push: push:
branches: branches:
- 'main' - 'main'
- 'update/ci/*'
tags: tags:
- 'v*.*.*' - 'v*.*.*'
@ -47,16 +48,17 @@ jobs:
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=sha type=sha,enable=true,prefix=sha-,format=short
flavor: | flavor: |
latest=${{ matrix.flavor == 'cuda' && github.ref == 'refs/heads/main' }} latest=${{ matrix.flavor == 'cuda' && github.ref == 'refs/heads/main' }}
suffix=${{ matrix.flavor }},onlatest=false suffix=-${{ matrix.flavor }},onlatest=false
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
with:
platforms: ${{ matrix.platforms }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
@ -67,7 +69,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build container - name: Build container
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
context: . context: .
file: ${{ matrix.dockerfile }} file: ${{ matrix.dockerfile }}

View File

@ -8,10 +8,11 @@ on:
- 'ready_for_review' - 'ready_for_review'
- 'opened' - 'opened'
- 'synchronize' - 'synchronize'
workflow_dispatch:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
matrix: matrix:
@ -62,28 +63,13 @@ jobs:
# github-env: $env:GITHUB_ENV # github-env: $env:GITHUB_ENV
name: ${{ matrix.pytorch }} on ${{ matrix.python-version }} name: ${{ matrix.pytorch }} on ${{ matrix.python-version }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
PIP_USE_PEP517: '1'
steps: steps:
- name: Checkout sources - name: Checkout sources
id: checkout-sources id: checkout-sources
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: setup python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Set Cache-Directory Windows
if: runner.os == 'Windows'
id: set-cache-dir-windows
run: |
echo "CACHE_DIR=$HOME\invokeai\models" >> ${{ matrix.github-env }}
echo "PIP_NO_CACHE_DIR=1" >> ${{ matrix.github-env }}
- name: Set Cache-Directory others
if: runner.os != 'Windows'
id: set-cache-dir-others
run: echo "CACHE_DIR=$HOME/invokeai/models" >> ${{ matrix.github-env }}
- name: set test prompt to main branch validation - name: set test prompt to main branch validation
if: ${{ github.ref == 'refs/heads/main' }} if: ${{ github.ref == 'refs/heads/main' }}
run: echo "TEST_PROMPTS=tests/preflight_prompts.txt" >> ${{ matrix.github-env }} run: echo "TEST_PROMPTS=tests/preflight_prompts.txt" >> ${{ matrix.github-env }}
@ -92,26 +78,29 @@ jobs:
if: ${{ github.ref != 'refs/heads/main' }} if: ${{ github.ref != 'refs/heads/main' }}
run: echo "TEST_PROMPTS=tests/validate_pr_prompt.txt" >> ${{ matrix.github-env }} run: echo "TEST_PROMPTS=tests/validate_pr_prompt.txt" >> ${{ matrix.github-env }}
- name: setup python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml
- name: install invokeai - name: install invokeai
env: env:
PIP_EXTRA_INDEX_URL: ${{ matrix.extra-index-url }} PIP_EXTRA_INDEX_URL: ${{ matrix.extra-index-url }}
run: > run: >
pip3 install pip3 install
--use-pep517
--editable=".[test]" --editable=".[test]"
- name: run pytest - name: run pytest
id: run-pytest
run: pytest run: pytest
- name: Use Cached models - name: set INVOKEAI_OUTDIR
id: cache-sd-model run: >
uses: actions/cache@v3 python -c
env: "import os;from ldm.invoke.globals import Globals;OUTDIR=os.path.join(Globals.root,str('outputs'));print(f'INVOKEAI_OUTDIR={OUTDIR}')"
cache-name: huggingface-models >> ${{ matrix.github-env }}
with:
path: ${{ env.CACHE_DIR }}
key: ${{ env.cache-name }}
enableCrossOsArchive: true
- name: run invokeai-configure - name: run invokeai-configure
id: run-preload-models id: run-preload-models
@ -124,9 +113,8 @@ jobs:
--full-precision --full-precision
# can't use fp16 weights without a GPU # can't use fp16 weights without a GPU
- name: Run the tests - name: run invokeai
if: runner.os != 'Windows' id: run-invokeai
id: run-tests
env: env:
# Set offline mode to make sure configure preloaded successfully. # Set offline mode to make sure configure preloaded successfully.
HF_HUB_OFFLINE: 1 HF_HUB_OFFLINE: 1
@ -137,10 +125,11 @@ jobs:
--no-patchmatch --no-patchmatch
--no-nsfw_checker --no-nsfw_checker
--from_file ${{ env.TEST_PROMPTS }} --from_file ${{ env.TEST_PROMPTS }}
--outdir ${{ env.INVOKEAI_OUTDIR }}/${{ matrix.python-version }}/${{ matrix.pytorch }}
- name: Archive results - name: Archive results
id: archive-results id: archive-results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: results_${{ matrix.pytorch }}_${{ matrix.python-version }} name: results
path: ${{ env.INVOKEAI_ROOT }}/outputs path: ${{ env.INVOKEAI_OUTDIR }}

View File

@ -1,24 +1,23 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
# Maintained by Matthias Wild <mauwii@outlook.de>
ARG PYTHON_VERSION=3.9 ARG PYTHON_VERSION=3.9
################## ##################
### base image ### ## base image ##
################## ##################
FROM python:${PYTHON_VERSION}-slim AS python-base FROM python:${PYTHON_VERSION}-slim AS python-base
# prepare for buildkit cache
RUN rm -f /etc/apt/apt.conf.d/docker-clean
# Install necesarry packages # Install necesarry packages
RUN \ RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update \ apt-get update \
&& apt-get install -y \ && apt-get install \
-yqq \
--no-install-recommends \ --no-install-recommends \
libgl1-mesa-glx=20.3.* \ libgl1-mesa-glx=20.3.* \
libglib2.0-0=2.66.* \ libglib2.0-0=2.66.* \
libopencv-dev=4.5.* \ libopencv-dev=4.5.* \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# set working directory and path # set working directory and path
@ -27,86 +26,61 @@ ARG APPNAME=InvokeAI
WORKDIR ${APPDIR} WORKDIR ${APPDIR}
ENV PATH=${APPDIR}/${APPNAME}/bin:$PATH ENV PATH=${APPDIR}/${APPNAME}/bin:$PATH
###################### #######################
### build frontend ### ## build pyproject ##
###################### #######################
FROM node:lts as frontend-builder
# Copy Sources
ARG APPDIR=/usr/src
WORKDIR ${APPDIR}
COPY --link . .
# install dependencies and build frontend
WORKDIR ${APPDIR}/invokeai/frontend
RUN \
--mount=type=cache,target=/usr/local/share/.cache/yarn/v6 \
yarn install \
--prefer-offline \
--frozen-lockfile \
--non-interactive \
--production=false \
&& yarn build
###################################
### install python dependencies ###
###################################
FROM python-base AS pyproject-builder FROM python-base AS pyproject-builder
ENV PIP_USE_PEP517=1
# prepare for buildkit cache
ARG PIP_CACHE_DIR=/var/cache/buildkit/pip
ENV PIP_CACHE_DIR ${PIP_CACHE_DIR}
RUN mkdir -p ${PIP_CACHE_DIR}
# Install dependencies # Install dependencies
RUN \ RUN \
--mount=type=cache,target=${PIP_CACHE_DIR} \
--mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update \ apt-get update \
&& apt-get install -y \ && apt-get install \
-yqq \
--no-install-recommends \ --no-install-recommends \
build-essential=12.9 \
gcc=4:10.2.* \ gcc=4:10.2.* \
python3-dev=3.9.* \ python3-dev=3.9.* \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# create virtual environment # create virtual environment
RUN python3 -m venv "${APPNAME}" \ RUN --mount=type=cache,target=${PIP_CACHE_DIR} \
python3 -m venv "${APPNAME}" \
--upgrade-deps --upgrade-deps
# copy sources # copy sources
COPY --from=frontend-builder ${APPDIR} . COPY --link . .
# install pyproject.toml # install pyproject.toml
ARG PIP_EXTRA_INDEX_URL ARG PIP_EXTRA_INDEX_URL
ENV PIP_EXTRA_INDEX_URL ${PIP_EXTRA_INDEX_URL} ENV PIP_EXTRA_INDEX_URL ${PIP_EXTRA_INDEX_URL}
RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ ARG PIP_PACKAGE=.
"${APPDIR}/${APPNAME}/bin/pip" install \ RUN --mount=type=cache,target=${PIP_CACHE_DIR} \
--use-pep517 \ "${APPDIR}/${APPNAME}/bin/pip" install ${PIP_PACKAGE}
.
# build patchmatch
RUN python3 -c "from patchmatch import patch_match"
##################### #####################
### runtime image ### ## runtime image ##
##################### #####################
FROM python-base AS runtime FROM python-base AS runtime
# setup environment # setup environment
COPY --from=pyproject-builder ${APPDIR}/${APPNAME} ${APPDIR}/${APPNAME} COPY --from=pyproject-builder --link ${APPDIR}/${APPNAME} ${APPDIR}/${APPNAME}
ENV INVOKEAI_ROOT=/data ENV INVOKEAI_ROOT=/data
ENV INVOKE_MODEL_RECONFIGURE="--yes --default_only" ENV INVOKE_MODEL_RECONFIGURE="--yes --default_only"
# build patchmatch
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update \
&& apt-get install -y \
--no-install-recommends \
build-essential=12.9 \
&& PYTHONDONTWRITEBYTECODE=1 \
python3 -c "from patchmatch import patch_match" \
&& apt-get remove -y \
--autoremove \
build-essential \
&& apt-get autoclean \
&& rm -rf /var/lib/apt/lists/*
# set Entrypoint and default CMD # set Entrypoint and default CMD
ENTRYPOINT [ "invokeai" ] ENTRYPOINT [ "invokeai" ]
CMD [ "--web", "--host=0.0.0.0" ] CMD [ "--web", "--host=0.0.0.0" ]
VOLUME [ "/data" ] VOLUME [ "/data" ]
LABEL org.opencontainers.image.authors="mauwii@outlook.de"

View File

@ -17,14 +17,14 @@ DOCKERFILE=${INVOKE_DOCKERFILE:-Dockerfile}
# print the settings # print the settings
echo -e "You are using these values:\n" echo -e "You are using these values:\n"
echo -e "Dockerfile: \t${DOCKERFILE}" echo -e "Dockerfile:\t\t${DOCKERFILE}"
echo -e "index-url: \t${PIP_EXTRA_INDEX_URL:-none}" echo -e "index-url:\t\t${PIP_EXTRA_INDEX_URL:-none}"
echo -e "Volumename: \t${VOLUMENAME}" echo -e "Volumename:\t\t${VOLUMENAME}"
echo -e "Platform: \t${PLATFORM}" echo -e "Platform:\t\t${PLATFORM}"
echo -e "Registry: \t${CONTAINER_REGISTRY}" echo -e "Registry:\t\t${CONTAINER_REGISTRY}"
echo -e "Repository: \t${CONTAINER_REPOSITORY}" echo -e "Repository:\t\t${CONTAINER_REPOSITORY}"
echo -e "Container Tag: \t${CONTAINER_TAG}" echo -e "Container Tag:\t\t${CONTAINER_TAG}"
echo -e "Container Image: ${CONTAINER_IMAGE}\n" echo -e "Container Image:\t${CONTAINER_IMAGE}\n"
# Create docker volume # Create docker volume
if [[ -n "$(docker volume ls -f name="${VOLUMENAME}" -q)" ]]; then if [[ -n "$(docker volume ls -f name="${VOLUMENAME}" -q)" ]]; then
@ -35,9 +35,10 @@ else
fi fi
# Build Container # Build Container
docker build \ DOCKER_BUILDKIT=1 docker build \
--platform="${PLATFORM}" \ --platform="${PLATFORM}" \
--tag="${CONTAINER_IMAGE}" \ --tag="${CONTAINER_IMAGE}" \
${PIP_EXTRA_INDEX_URL:+--build-arg="PIP_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}"} \ ${PIP_EXTRA_INDEX_URL:+--build-arg="PIP_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}"} \
${PIP_PACKAGE:+--build-arg="PIP_PACKAGE=${PIP_PACKAGE}"} \
--file="${DOCKERFILE}" \ --file="${DOCKERFILE}" \
.. ..

View File

@ -2,12 +2,12 @@
if [[ -z "$PIP_EXTRA_INDEX_URL" ]]; then if [[ -z "$PIP_EXTRA_INDEX_URL" ]]; then
# Decide which container flavor to build if not specified # Decide which container flavor to build if not specified
if [[ -z "$CONTAINER_FLAVOR" ]]; then if [[ -z "$CONTAINER_FLAVOR" ]] && python -c "import torch" &>/dev/null; then
# Check for CUDA and ROCm # Check for CUDA and ROCm
CUDA_AVAILABLE=$(python -c "import torch;print(torch.cuda.is_available())") CUDA_AVAILABLE=$(python -c "import torch;print(torch.cuda.is_available())")
ROCM_AVAILABLE=$(python -c "import torch;print(torch.version.hip is not None)") ROCM_AVAILABLE=$(python -c "import torch;print(torch.version.hip is not None)")
if [[ "$(uname -s)" != "Darwin" && "${CUDA_AVAILABLE}" == "True" ]]; then if [[ "$(uname -s)" != "Darwin" && "${CUDA_AVAILABLE}" == "True" ]]; then
CONTAINER_FLAVOR=cuda CONTAINER_FLAVOR="cuda"
elif [[ "$(uname -s)" != "Darwin" && "${ROCM_AVAILABLE}" == "True" ]]; then elif [[ "$(uname -s)" != "Darwin" && "${ROCM_AVAILABLE}" == "True" ]]; then
CONTAINER_FLAVOR="rocm" CONTAINER_FLAVOR="rocm"
else else
@ -16,9 +16,11 @@ if [[ -z "$PIP_EXTRA_INDEX_URL" ]]; then
fi fi
# Set PIP_EXTRA_INDEX_URL based on container flavor # Set PIP_EXTRA_INDEX_URL based on container flavor
if [[ "$CONTAINER_FLAVOR" == "rocm" ]]; then if [[ "$CONTAINER_FLAVOR" == "rocm" ]]; then
PIP_EXTRA_INDEX_URL="${PIP_EXTRA_INDEX_URL-"https://download.pytorch.org/whl/rocm"}" PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/rocm"
elif CONTAINER_FLAVOR=cpu; then elif [[ "$CONTAINER_FLAVOR" == "cpu" ]]; then
PIP_EXTRA_INDEX_URL="${PIP_EXTRA_INDEX_URL-"https://download.pytorch.org/whl/cpu"}" PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/cpu"
# elif [[ -z "$CONTAINER_FLAVOR" || "$CONTAINER_FLAVOR" == "cuda" ]]; then
# PIP_PACKAGE=${PIP_PACKAGE-".[xformers]"}
fi fi
fi fi
@ -30,6 +32,7 @@ PLATFORM="${PLATFORM-Linux/${ARCH}}"
INVOKEAI_BRANCH="${INVOKEAI_BRANCH-$(git branch --show)}" INVOKEAI_BRANCH="${INVOKEAI_BRANCH-$(git branch --show)}"
CONTAINER_REGISTRY="${CONTAINER_REGISTRY-"ghcr.io"}" CONTAINER_REGISTRY="${CONTAINER_REGISTRY-"ghcr.io"}"
CONTAINER_REPOSITORY="${CONTAINER_REPOSITORY-"$(whoami)/${REPOSITORY_NAME}"}" CONTAINER_REPOSITORY="${CONTAINER_REPOSITORY-"$(whoami)/${REPOSITORY_NAME}"}"
CONTAINER_FLAVOR="${CONTAINER_FLAVOR-cuda}"
CONTAINER_TAG="${CONTAINER_TAG-"${INVOKEAI_BRANCH##*/}-${CONTAINER_FLAVOR}"}" CONTAINER_TAG="${CONTAINER_TAG-"${INVOKEAI_BRANCH##*/}-${CONTAINER_FLAVOR}"}"
CONTAINER_IMAGE="${CONTAINER_REGISTRY}/${CONTAINER_REPOSITORY}:${CONTAINER_TAG}" CONTAINER_IMAGE="${CONTAINER_REGISTRY}/${CONTAINER_REPOSITORY}:${CONTAINER_TAG}"
CONTAINER_IMAGE="${CONTAINER_IMAGE,,}" CONTAINER_IMAGE="${CONTAINER_IMAGE,,}"

View File

@ -54,8 +54,7 @@ Please enter 1, 2, 3, or 4: [1] 3
``` ```
From the command line, with the InvokeAI virtual environment active, From the command line, with the InvokeAI virtual environment active,
you can launch the front end with the command `textual_inversion you can launch the front end with the command `invokeai-ti --gui`.
--gui`.
This will launch a text-based front end that will look like this: This will launch a text-based front end that will look like this:
@ -227,12 +226,12 @@ It accepts a large number of arguments, which can be summarized by
passing the `--help` argument: passing the `--help` argument:
```sh ```sh
textual_inversion --help invokeai-ti --help
``` ```
Typical usage is shown here: Typical usage is shown here:
```sh ```sh
textual_inversion \ invokeai-ti \
--model=stable-diffusion-1.5 \ --model=stable-diffusion-1.5 \
--resolution=512 \ --resolution=512 \
--learnable_property=style \ --learnable_property=style \

View File

@ -16,10 +16,6 @@ title: Installing with Docker
For general use, install locally to leverage your machine's GPU. For general use, install locally to leverage your machine's GPU.
!!! tip "For running on a cloud instance/service"
Check out the [Running InvokeAI in the cloud with Docker](#running-invokeai-in-the-cloud-with-docker) section below
## Why containers? ## Why containers?
They provide a flexible, reliable way to build and deploy InvokeAI. You'll also They provide a flexible, reliable way to build and deploy InvokeAI. You'll also
@ -78,38 +74,40 @@ Some Suggestions of variables you may want to change besides the Token:
<figure markdown> <figure markdown>
| Environment-Variable | Default value | Description | | Environment-Variable <img width="220" align="right"/> | Default value <img width="360" align="right"/> | Description |
| -------------------- | ----------------------------- | -------------------------------------------------------------------------------------------- | | ----------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HUGGINGFACE_TOKEN` | No default, but **required**! | This is the only **required** variable, without it you can't download the huggingface models | | `HUGGING_FACE_HUB_TOKEN` | No default, but **required**! | This is the only **required** variable, without it you can't download the huggingface models |
| `REPOSITORY_NAME` | The Basename of the Repo folder | This name will used as the container repository/image name | | `REPOSITORY_NAME` | The Basename of the Repo folder | This name will used as the container repository/image name |
| `VOLUMENAME` | `${REPOSITORY_NAME,,}_data` | Name of the Docker Volume where model files will be stored | | `VOLUMENAME` | `${REPOSITORY_NAME,,}_data` | Name of the Docker Volume where model files will be stored |
| `ARCH` | arch of the build machine | can be changed if you want to build the image for another arch | | `ARCH` | arch of the build machine | Can be changed if you want to build the image for another arch |
| `INVOKEAI_TAG` | latest | the Container Repository / Tag which will be used | | `CONTAINER_REGISTRY` | ghcr.io | Name of the Container Registry to use for the full tag |
| `PIP_REQUIREMENTS` | `requirements-lin-cuda.txt` | the requirements file to use (from `environments-and-requirements`) | | `CONTAINER_REPOSITORY` | `$(whoami)/${REPOSITORY_NAME}` | Name of the Container Repository |
| `CONTAINER_FLAVOR` | cuda | the flavor of the image, which can be changed if you build f.e. with amd requirements file. | | `CONTAINER_FLAVOR` | `cuda` | The flavor of the image to built, available options are `cuda`, `rocm` and `cpu`. If you choose `rocm` or `cpu`, the extra-index-url will be selected automatically, unless you set one yourself. |
| `INVOKE_DOCKERFILE` | `docker-build/Dockerfile` | the Dockerfile which should be built, handy for development | | `CONTAINER_TAG` | `${INVOKEAI_BRANCH##*/}-${CONTAINER_FLAVOR}` | The Container Repository / Tag which will be used |
| `INVOKE_DOCKERFILE` | `Dockerfile` | The Dockerfile which should be built, handy for development |
| `PIP_EXTRA_INDEX_URL` | | If you want to use a custom pip-extra-index-url |
</figure> </figure>
#### Build the Image #### Build the Image
I provided a build script, which is located in `docker-build/build.sh` but still I provided a build script, which is located next to the Dockerfile in
needs to be executed from the Repository root. `docker/build.sh`. It can be executed from repository root like this:
```bash ```bash
./docker-build/build.sh ./docker/build.sh
``` ```
The build Script not only builds the container, but also creates the docker The build Script not only builds the container, but also creates the docker
volume if not existing yet, or if empty it will just download the models. volume if not existing yet.
#### Run the Container #### Run the Container
After the build process is done, you can run the container via the provided After the build process is done, you can run the container via the provided
`docker-build/run.sh` script `docker/run.sh` script
```bash ```bash
./docker-build/run.sh ./docker/run.sh
``` ```
When used without arguments, the container will start the webserver and provide When used without arguments, the container will start the webserver and provide
@ -119,7 +117,7 @@ also do so.
!!! example "run script example" !!! example "run script example"
```bash ```bash
./docker-build/run.sh "banana sushi" -Ak_lms -S42 -s10 ./docker/run.sh "banana sushi" -Ak_lms -S42 -s10
``` ```
This would generate the legendary "banana sushi" with Seed 42, k_lms Sampler and 10 steps. This would generate the legendary "banana sushi" with Seed 42, k_lms Sampler and 10 steps.
@ -130,16 +128,18 @@ also do so.
## Running the container on your GPU ## Running the container on your GPU
If you have an Nvidia GPU, you can enable InvokeAI to run on the GPU by running the container with an extra If you have an Nvidia GPU, you can enable InvokeAI to run on the GPU by running
environment variable to enable GPU usage and have the process run much faster: the container with an extra environment variable to enable GPU usage and have
the process run much faster:
```bash ```bash
GPU_FLAGS=all ./docker-build/run.sh GPU_FLAGS=all ./docker/run.sh
``` ```
This passes the `--gpus all` to docker and uses the GPU. This passes the `--gpus all` to docker and uses the GPU.
If you don't have a GPU (or your host is not yet setup to use it) you will see a message like this: If you don't have a GPU (or your host is not yet setup to use it) you will see a
message like this:
`docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]].` `docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]].`
@ -147,84 +147,8 @@ You can use the full set of GPU combinations documented here:
https://docs.docker.com/config/containers/resource_constraints/#gpu https://docs.docker.com/config/containers/resource_constraints/#gpu
For example, use `GPU_FLAGS=device=GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a` to choose a specific device identified by a UUID. For example, use `GPU_FLAGS=device=GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a` to
choose a specific device identified by a UUID.
## Running InvokeAI in the cloud with Docker
We offer an optimized Ubuntu-based image that has been well-tested in cloud deployments. Note: it also works well locally on Linux x86_64 systems with an Nvidia GPU. It *may* also work on Windows under WSL2 and on Intel Mac (not tested).
An advantage of this method is that it does not need any local setup or additional dependencies.
See the `docker-build/Dockerfile.cloud` file to familizarize yourself with the image's content.
### Prerequisites
- a `docker` runtime
- `make` (optional but helps for convenience)
- Huggingface token to download models, or an existing InvokeAI runtime directory from a previous installation
Neither local Python nor any dependencies are required. If you don't have `make` (part of `build-essentials` on Ubuntu), or do not wish to install it, the commands from the `docker-build/Makefile` are readily adaptable to be executed directly.
### Building and running the image locally
1. Clone this repo and `cd docker-build`
1. `make build` - this will build the image. (This does *not* require a GPU-capable system).
1. _(skip this step if you already have a complete InvokeAI runtime directory)_
- `make configure` (This does *not* require a GPU-capable system)
- this will create a local cache of models and configs (a.k.a the _runtime dir_)
- enter your Huggingface token when prompted
1. `make web`
1. Open the `http://localhost:9090` URL in your browser, and enjoy the banana sushi!
To use InvokeAI on the cli, run `make cli`. To open a Bash shell in the container for arbitraty advanced use, `make shell`.
#### Building and running without `make`
(Feel free to adapt paths such as `${HOME}/invokeai` to your liking, and modify the CLI arguments as necessary).
!!! example "Build the image and configure the runtime directory"
```Shell
cd docker-build
DOCKER_BUILDKIT=1 docker build -t local/invokeai:latest -f Dockerfile.cloud ..
docker run --rm -it -v ${HOME}/invokeai:/mnt/invokeai local/invokeai:latest -c "python scripts/configure_invokeai.py"
```
!!! example "Run the web server"
```Shell
docker run --runtime=nvidia --gpus=all --rm -it -v ${HOME}/invokeai:/mnt/invokeai -p9090:9090 local/invokeai:latest
```
Access the Web UI at http://localhost:9090
!!! example "Run the InvokeAI interactive CLI"
```
docker run --runtime=nvidia --gpus=all --rm -it -v ${HOME}/invokeai:/mnt/invokeai local/invokeai:latest -c "python scripts/invoke.py"
```
### Running the image in the cloud
This image works anywhere you can run a container with a mounted Docker volume. You may either build this image on a cloud instance, or build and push it to your Docker registry. To manually run this on a cloud instance (such as AWS EC2, GCP or Azure VM):
1. build this image either in the cloud (you'll need to pull the repo), or locally
1. `docker tag` it as `your-registry/invokeai` and push to your registry (i.e. Dockerhub)
1. `docker pull` it on your cloud instance
1. configure the runtime directory as per above example, using `docker run ... configure_invokeai.py` script
1. use either one of the `docker run` commands above, substituting the image name for your own image.
To run this on Runpod, please refer to the following Runpod template: https://www.runpod.io/console/gpu-secure-cloud?template=vm19ukkycf (you need a Runpod subscription). When launching the template, feel free to set the image to pull your own build.
The template's `README` provides ample detail, but at a high level, the process is as follows:
1. create a pod using this Docker image
1. ensure the pod has an `INVOKEAI_ROOT=<path_to_your_persistent_volume>` environment variable, and that it corresponds to the path to your pod's persistent volume mount
1. Run the pod with `sleep infinity` as the Docker command
1. Use Runpod basic SSH to connect to the pod, and run `python scripts/configure_invokeai.py` script
1. Stop the pod, and change the Docker command to `python scripts/invoke.py --web --host 0.0.0.0`
1. Run the pod again, connect to your pod on HTTP port 9090, and enjoy the banana sushi!
Running on other cloud providers such as Vast.ai will likely work in a similar fashion.
--- ---
@ -240,13 +164,12 @@ Running on other cloud providers such as Vast.ai will likely work in a similar f
If you're on a **Linux container** the `invoke` script is **automatically If you're on a **Linux container** the `invoke` script is **automatically
started** and the output dir set to the Docker volume you created earlier. started** and the output dir set to the Docker volume you created earlier.
If you're **directly on macOS follow these startup instructions**. If you're **directly on macOS follow these startup instructions**. With the
With the Conda environment activated (`conda activate ldm`), run the interactive Conda environment activated (`conda activate ldm`), run the interactive
interface that combines the functionality of the original scripts `txt2img` and interface that combines the functionality of the original scripts `txt2img` and
`img2img`: `img2img`: Use the more accurate but VRAM-intensive full precision math because
Use the more accurate but VRAM-intensive full precision math because half-precision requires autocast and won't work. By default the images are saved
half-precision requires autocast and won't work. in `outputs/img-samples/`.
By default the images are saved in `outputs/img-samples/`.
```Shell ```Shell
python3 scripts/invoke.py --full_precision python3 scripts/invoke.py --full_precision
@ -262,9 +185,9 @@ invoke> q
### Text to Image ### Text to Image
For quick (but bad) image results test with 5 steps (default 50) and 1 sample For quick (but bad) image results test with 5 steps (default 50) and 1 sample
image. This will let you know that everything is set up correctly. image. This will let you know that everything is set up correctly. Then increase
Then increase steps to 100 or more for good (but slower) results. steps to 100 or more for good (but slower) results. The prompt can be in quotes
The prompt can be in quotes or not. or not.
```Shell ```Shell
invoke> The hulk fighting with sheldon cooper -s5 -n1 invoke> The hulk fighting with sheldon cooper -s5 -n1
@ -277,10 +200,9 @@ You'll need to experiment to see if face restoration is making it better or
worse for your specific prompt. worse for your specific prompt.
If you're on a container the output is set to the Docker volume. You can copy it If you're on a container the output is set to the Docker volume. You can copy it
wherever you want. wherever you want. You can download it from the Docker Desktop app, Volumes,
You can download it from the Docker Desktop app, Volumes, my-vol, data. my-vol, data. Or you can copy it from your Mac terminal. Keep in mind
Or you can copy it from your Mac terminal. Keep in mind `docker cp` can't expand `docker cp` can't expand `*.png` so you'll need to specify the image file name.
`*.png` so you'll need to specify the image file name.
On your host Mac (you can use the name of any container that mounted the On your host Mac (you can use the name of any container that mounted the
volume): volume):

View File

@ -29,6 +29,7 @@ if [ "$RESPONSE" == 'y' ]; then
echo "Existing/invalid tag" echo "Existing/invalid tag"
exit -1 exit -1
fi fi
git push origin :refs/tags/$LATEST_TAG git push origin :refs/tags/$LATEST_TAG
git tag -fa $LATEST_TAG git tag -fa $LATEST_TAG
fi fi

View File

@ -249,6 +249,7 @@ class InvokeAiInstance:
"--require-virtualenv", "--require-virtualenv",
"torch", "torch",
"torchvision", "torchvision",
"--force-reinstall",
"--find-links" if find_links is not None else None, "--find-links" if find_links is not None else None,
find_links, find_links,
"--extra-index-url" if extra_index_url is not None else None, "--extra-index-url" if extra_index_url is not None else None,
@ -325,6 +326,7 @@ class InvokeAiInstance:
Configure the InvokeAI runtime directory Configure the InvokeAI runtime directory
""" """
# set sys.argv to a consistent state
new_argv = [sys.argv[0]] new_argv = [sys.argv[0]]
for i in range(1,len(sys.argv)): for i in range(1,len(sys.argv)):
el = sys.argv[i] el = sys.argv[i]
@ -344,9 +346,6 @@ class InvokeAiInstance:
# NOTE: currently the config script does its own arg parsing! this means the command-line switches # NOTE: currently the config script does its own arg parsing! this means the command-line switches
# from the installer will also automatically propagate down to the config script. # from the installer will also automatically propagate down to the config script.
# this may change in the future with config refactoring! # this may change in the future with config refactoring!
# set sys.argv to a consistent state
invokeai_configure.main() invokeai_configure.main()
def install_user_scripts(self): def install_user_scripts(self):

View File

@ -1208,12 +1208,18 @@ class InvokeAIWebServer:
) )
except KeyboardInterrupt: except KeyboardInterrupt:
# Clear the CUDA cache on an exception
self.empty_cuda_cache()
self.socketio.emit("processingCanceled") self.socketio.emit("processingCanceled")
raise raise
except CanceledException: except CanceledException:
# Clear the CUDA cache on an exception
self.empty_cuda_cache()
self.socketio.emit("processingCanceled") self.socketio.emit("processingCanceled")
pass pass
except Exception as e: except Exception as e:
# Clear the CUDA cache on an exception
self.empty_cuda_cache()
print(e) print(e)
self.socketio.emit("error", {"message": (str(e))}) self.socketio.emit("error", {"message": (str(e))})
print("\n") print("\n")
@ -1221,6 +1227,12 @@ class InvokeAIWebServer:
traceback.print_exc() traceback.print_exc()
print("\n") print("\n")
def empty_cuda_cache(self):
if self.generate.device.type == "cuda":
import torch.cuda
torch.cuda.empty_cache()
def parameters_to_generated_image_metadata(self, parameters): def parameters_to_generated_image_metadata(self, parameters):
try: try:
# top-level metadata minus `image` or `images` # top-level metadata minus `image` or `images`

View File

@ -0,0 +1,13 @@
{
"plugins": [
[
"transform-imports",
{
"lodash": {
"transform": "lodash/${member}",
"preventFullImport": true
}
}
]
]
}

View File

@ -0,0 +1,5 @@
dist/
.husky/
node_modules/
patches/
public/

View File

@ -1,13 +0,0 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin-react-hooks'],
root: true,
rules: {
'@typescript-eslint/no-unused-vars': ['warn', { varsIgnorePattern: '_+' }],
},
};

View File

@ -0,0 +1,40 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:prettier/recommended',
'plugin:react/jsx-runtime',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'eslint-plugin-react-hooks'],
root: true,
rules: {
'react-hooks/exhaustive-deps': 'error',
'no-var': 'error',
'brace-style': 'error',
'prefer-template': 'error',
radix: 'error',
'space-before-blocks': 'error',
'import/prefer-default-export': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { varsIgnorePattern: '_+' }],
},
settings: {
react: {
version: 'detect',
},
},
};

View File

@ -23,3 +23,6 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# build stats
stats.html

View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd invokeai/frontend/ && npx run lint

View File

@ -0,0 +1,5 @@
dist/
.husky/
node_modules/
patches/
public/

View File

@ -0,0 +1,6 @@
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: true,
singleQuote: true,
};

View File

@ -1,28 +1,20 @@
# Stable Diffusion Web UI # InvokeAI UI dev setup
## Run The UI is in `invokeai/frontend`.
- `python scripts/dream.py --web` serves both frontend and backend at ## Environment set up
http://localhost:9090
## Evironment Install [node](https://nodejs.org/en/download/) (includes npm) and
Install [node](https://nodejs.org/en/download/) (includes npm) and optionally
[yarn](https://yarnpkg.com/getting-started/install). [yarn](https://yarnpkg.com/getting-started/install).
From `frontend/` run `npm install` / `yarn install` to install the frontend From `invokeai/frontend/` run `yarn install` to get everything set up.
packages.
## Dev ## Dev
1. From `frontend/`, run `npm dev` / `yarn dev` to start the dev server. 1. Start the dev server: `yarn dev`
2. Run `python scripts/dream.py --web`. 2. Start the InvokeAI UI per usual: `invokeai --web`
3. Navigate to the dev server address e.g. `http://localhost:5173/`. 3. Point your browser to the dev server address e.g. `http://localhost:5173/`
To build for dev: `npm build-dev` / `yarn build-dev` To build for dev: `yarn build-dev`
To build for production: `npm build` / `yarn build` To build for production: `yarn build`
## TODO
- Search repo for "TODO"

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,23 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<script type="module" crossorigin src="./assets/polyfills.1ff60148.js"></script>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title> <title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" /> <link rel="shortcut icon" type="icon" href="./assets/favicon-0d253ced.ico" />
<script type="module" crossorigin src="./assets/index.dd4ad8a1.js"></script> <script type="module" crossorigin src="./assets/index-34c8aef8.js"></script>
<link rel="stylesheet" href="./assets/index.8badc8b4.css"> <link rel="stylesheet" href="./assets/index-b0bf79f4.css">
<script type="module">try{import.meta.url;import("_").catch(()=>1);}catch(e){}window.__vite_is_modern_browser=true;</script>
<script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
<script nomodule crossorigin id="vite-legacy-polyfill" src="./assets/polyfills-legacy-dde3a68a.js"></script>
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-8219c08f.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
</body> </body>
</html> </html>

View File

@ -24,6 +24,7 @@
"otherOptions": "Other Options", "otherOptions": "Other Options",
"seamlessTiling": "Seamless Tiling", "seamlessTiling": "Seamless Tiling",
"hiresOptim": "High Res Optimization", "hiresOptim": "High Res Optimization",
"hiresStrength": "High Res Strength",
"imageFit": "Fit Initial Image To Output Size", "imageFit": "Fit Initial Image To Output Size",
"codeformerFidelity": "Fidelity", "codeformerFidelity": "Fidelity",
"seamSize": "Seam Size", "seamSize": "Seam Size",

View File

@ -24,6 +24,7 @@
"otherOptions": "Other Options", "otherOptions": "Other Options",
"seamlessTiling": "Seamless Tiling", "seamlessTiling": "Seamless Tiling",
"hiresOptim": "High Res Optimization", "hiresOptim": "High Res Optimization",
"hiresStrength": "High Res Strength",
"imageFit": "Fit Initial Image To Output Size", "imageFit": "Fit Initial Image To Output Size",
"codeformerFidelity": "Fidelity", "codeformerFidelity": "Fidelity",
"seamSize": "Seam Size", "seamSize": "Seam Size",
@ -43,6 +44,7 @@
"invoke": "Invoke", "invoke": "Invoke",
"cancel": "Cancel", "cancel": "Cancel",
"promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)", "promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)",
"negativePrompts": "Negative Prompts",
"sendTo": "Send to", "sendTo": "Send to",
"sendToImg2Img": "Send to Image to Image", "sendToImg2Img": "Send to Image to Image",
"sendToUnifiedCanvas": "Send To Unified Canvas", "sendToUnifiedCanvas": "Send To Unified Canvas",

View File

@ -1,23 +0,0 @@
{
"eslintConfig": {
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "eslint-plugin-react-hooks"],
"root": true,
"settings": {
"import/resolver": {
"node": {
"paths": ["src"],
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
"rules": {
"react/jsx-filename-extension": [1, { "extensions": [".tsx", ".ts"] }]
}
}
}

View File

@ -2,15 +2,15 @@
"name": "invoke-ai-ui", "name": "invoke-ai-ui",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module",
"scripts": { "scripts": {
"prepare": "cd ../../ && husky install invokeai/frontend/.husky",
"dev": "vite dev", "dev": "vite dev",
"build": "tsc && vite build", "build": "tsc && vite build",
"build-dev": "tsc && vite build -m development",
"preview": "vite preview", "preview": "vite preview",
"madge": "madge --circular src/main.tsx", "madge": "madge --circular src/main.tsx",
"lint": "eslint src/", "lint": "eslint --fix .",
"prettier": "prettier *.{json,cjs,ts,html} src/**/*.{ts,tsx}", "lint-staged": "lint-staged",
"prettier": "prettier *.{json,js,ts,html} src/**/*.{ts,tsx,scss} --write .",
"fmt": "npm run prettier -- --write", "fmt": "npm run prettier -- --write",
"postinstall": "patch-package" "postinstall": "patch-package"
}, },
@ -25,6 +25,7 @@
"@radix-ui/react-tooltip": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.2",
"@reduxjs/toolkit": "^1.8.5", "@reduxjs/toolkit": "^1.8.5",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@vitejs/plugin-react-swc": "^3.1.0",
"add": "^2.0.6", "add": "^2.0.6",
"dateformat": "^5.0.3", "dateformat": "^5.0.3",
"formik": "^2.2.9", "formik": "^2.2.9",
@ -62,22 +63,26 @@
"@types/react-transition-group": "^4.4.5", "@types/react-transition-group": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/eslint-plugin": "^5.36.2",
"@typescript-eslint/parser": "^5.36.2", "@typescript-eslint/parser": "^5.36.2",
"@vitejs/plugin-legacy": "^3.0.1", "babel-plugin-transform-imports": "^2.0.0",
"@vitejs/plugin-react": "^2.0.1",
"eslint": "^8.23.0", "eslint": "^8.23.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.0",
"madge": "^5.0.1", "madge": "^5.0.1",
"patch-package": "^6.5.0", "patch-package": "^6.5.0",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.1", "prettier": "^2.8.3",
"rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.55.0", "sass": "^1.55.0",
"terser": "^5.16.1", "terser": "^5.16.1",
"tsc-watch": "^5.0.3", "tsc-watch": "^5.0.3",
"typescript": "^4.6.4", "typescript": "^5.0.0-beta",
"vite": "^3.0.7", "vite": "^4.1.1",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^3.5.2" "vite-tsconfig-paths": "^4.0.5"
}, },
"madge": { "madge": {
"detectiveOptions": { "detectiveOptions": {
@ -88,5 +93,11 @@
"skipTypeImports": true "skipTypeImports": true
} }
} }
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx,cjs}": [
"npx prettier --write",
"npx eslint --fix"
]
} }
} }

View File

@ -24,6 +24,7 @@
"otherOptions": "Other Options", "otherOptions": "Other Options",
"seamlessTiling": "Seamless Tiling", "seamlessTiling": "Seamless Tiling",
"hiresOptim": "High Res Optimization", "hiresOptim": "High Res Optimization",
"hiresStrength": "High Res Strength",
"imageFit": "Fit Initial Image To Output Size", "imageFit": "Fit Initial Image To Output Size",
"codeformerFidelity": "Fidelity", "codeformerFidelity": "Fidelity",
"seamSize": "Seam Size", "seamSize": "Seam Size",

View File

@ -24,6 +24,7 @@
"otherOptions": "Other Options", "otherOptions": "Other Options",
"seamlessTiling": "Seamless Tiling", "seamlessTiling": "Seamless Tiling",
"hiresOptim": "High Res Optimization", "hiresOptim": "High Res Optimization",
"hiresStrength": "High Res Strength",
"imageFit": "Fit Initial Image To Output Size", "imageFit": "Fit Initial Image To Output Size",
"codeformerFidelity": "Fidelity", "codeformerFidelity": "Fidelity",
"seamSize": "Seam Size", "seamSize": "Seam Size",
@ -43,6 +44,7 @@
"invoke": "Invoke", "invoke": "Invoke",
"cancel": "Cancel", "cancel": "Cancel",
"promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)", "promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)",
"negativePrompts": "Negative Prompts",
"sendTo": "Send to", "sendTo": "Send to",
"sendToImg2Img": "Send to Image to Image", "sendToImg2Img": "Send to Image to Image",
"sendToUnifiedCanvas": "Send To Unified Canvas", "sendToUnifiedCanvas": "Send To Unified Canvas",

View File

@ -1,14 +1,14 @@
import ImageUploader from 'common/components/ImageUploader';
import Console from 'features/system/components/Console';
import ProgressBar from 'features/system/components/ProgressBar'; import ProgressBar from 'features/system/components/ProgressBar';
import SiteHeader from 'features/system/components/SiteHeader'; import SiteHeader from 'features/system/components/SiteHeader';
import Console from 'features/system/components/Console'; import InvokeTabs from 'features/ui/components/InvokeTabs';
import { keepGUIAlive } from './utils'; import { keepGUIAlive } from './utils';
import InvokeTabs from 'features/tabs/components/InvokeTabs';
import ImageUploader from 'common/components/ImageUploader';
import useToastWatcher from 'features/system/hooks/useToastWatcher'; import useToastWatcher from 'features/system/hooks/useToastWatcher';
import FloatingOptionsPanelButtons from 'features/tabs/components/FloatingOptionsPanelButtons'; import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
import FloatingGalleryButton from 'features/tabs/components/FloatingGalleryButton'; import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
keepGUIAlive(); keepGUIAlive();
@ -27,7 +27,7 @@ const App = () => {
<Console /> <Console />
</div> </div>
</ImageUploader> </ImageUploader>
<FloatingOptionsPanelButtons /> <FloatingParametersPanelButtons />
<FloatingGalleryButton /> <FloatingGalleryButton />
</div> </div>
); );

View File

@ -16,6 +16,20 @@ export const SAMPLERS: Array<string> = [
'k_heun', 'k_heun',
]; ];
// Valid Diffusers Samplers
export const DIFFUSERS_SAMPLERS: Array<string> = [
'ddim',
'plms',
'k_lms',
'dpmpp_2',
'k_dpm_2',
'k_dpm_2_a',
'k_dpmpp_2',
'k_euler',
'k_euler_a',
'k_heun',
];
// Valid image widths // Valid image widths
export const WIDTHS: Array<number> = [ export const WIDTHS: Array<number> = [
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960,

View File

@ -12,7 +12,7 @@
* 'gfpgan'. * 'gfpgan'.
*/ */
import { InvokeTabName } from 'features/tabs/tabMap'; import { InvokeTabName } from 'features/ui/store/tabMap';
import { IRect } from 'konva/lib/types'; import { IRect } from 'konva/lib/types';
/** /**

View File

@ -1,32 +1,26 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { RootState } from 'app/store';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { OptionsState } from 'features/options/store/optionsSlice';
import { SystemState } from 'features/system/store/systemSlice';
import { validateSeedWeights } from 'common/util/seedWeightPairs'; import { validateSeedWeights } from 'common/util/seedWeightPairs';
import { initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors'; import { initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors';
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';
export const readinessSelector = createSelector( export const readinessSelector = createSelector(
[ [
(state: RootState) => state.options, generationSelector,
(state: RootState) => state.system, systemSelector,
initialCanvasImageSelector, initialCanvasImageSelector,
activeTabNameSelector, activeTabNameSelector,
], ],
( (generation, system, initialCanvasImage, activeTabName) => {
options: OptionsState,
system: SystemState,
initialCanvasImage,
activeTabName
) => {
const { const {
prompt, prompt,
shouldGenerateVariations, shouldGenerateVariations,
seedWeights, seedWeights,
initialImage, initialImage,
seed, seed,
} = options; } = generation;
const { isProcessing, isConnected } = system; const { isProcessing, isConnected } = system;
@ -71,8 +65,8 @@ export const readinessSelector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
equalityCheck: _.isEqual, equalityCheck: isEqual,
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,7 +1,7 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import { GalleryCategory } from 'features/gallery/store/gallerySlice';
import { InvokeTabName } from 'features/tabs/tabMap';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
import { GalleryCategory } from 'features/gallery/store/gallerySlice';
import { InvokeTabName } from 'features/ui/store/tabMap';
/** /**
* We can't use redux-toolkit's createSlice() to make these actions, * We can't use redux-toolkit's createSlice() to make these actions,

View File

@ -1,25 +1,24 @@
import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit'; import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
import dateFormat from 'dateformat'; import * as InvokeAI from 'app/invokeai';
import { Socket } from 'socket.io-client'; import type { RootState } from 'app/store';
import { import {
frontendToBackendParameters, frontendToBackendParameters,
FrontendToBackendParametersConfig, FrontendToBackendParametersConfig,
} from 'common/util/parameterTranslation'; } from 'common/util/parameterTranslation';
import dateFormat from 'dateformat';
import { import {
GalleryCategory, GalleryCategory,
GalleryState, GalleryState,
removeImage, removeImage,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import { OptionsState } from 'features/options/store/optionsSlice';
import { import {
addLogEntry, addLogEntry,
generationRequested, generationRequested,
modelChangeRequested, modelChangeRequested,
setIsProcessing, setIsProcessing,
} from 'features/system/store/systemSlice'; } from 'features/system/store/systemSlice';
import { InvokeTabName } from 'features/tabs/tabMap'; import { InvokeTabName } from 'features/ui/store/tabMap';
import * as InvokeAI from 'app/invokeai'; import { Socket } from 'socket.io-client';
import type { RootState } from 'app/store';
/** /**
* Returns an object containing all functions which use `socketio.emit()`. * Returns an object containing all functions which use `socketio.emit()`.
@ -39,7 +38,8 @@ const makeSocketIOEmitters = (
const state: RootState = getState(); const state: RootState = getState();
const { const {
options: optionsState, generation: generationState,
postprocessing: postprocessingState,
system: systemState, system: systemState,
canvas: canvasState, canvas: canvasState,
} = state; } = state;
@ -47,7 +47,8 @@ const makeSocketIOEmitters = (
const frontendToBackendParametersConfig: FrontendToBackendParametersConfig = const frontendToBackendParametersConfig: FrontendToBackendParametersConfig =
{ {
generationMode, generationMode,
optionsState, generationState,
postprocessingState,
canvasState, canvasState,
systemState, systemState,
}; };
@ -90,8 +91,11 @@ const makeSocketIOEmitters = (
}, },
emitRunESRGAN: (imageToProcess: InvokeAI.Image) => { emitRunESRGAN: (imageToProcess: InvokeAI.Image) => {
dispatch(setIsProcessing(true)); dispatch(setIsProcessing(true));
const options: OptionsState = getState().options;
const { upscalingLevel, upscalingStrength } = options; const {
postprocessing: { upscalingLevel, upscalingStrength },
} = getState();
const esrganParameters = { const esrganParameters = {
upscale: [upscalingLevel, upscalingStrength], upscale: [upscalingLevel, upscalingStrength],
}; };
@ -111,8 +115,10 @@ const makeSocketIOEmitters = (
}, },
emitRunFacetool: (imageToProcess: InvokeAI.Image) => { emitRunFacetool: (imageToProcess: InvokeAI.Image) => {
dispatch(setIsProcessing(true)); dispatch(setIsProcessing(true));
const options: OptionsState = getState().options;
const { facetoolType, facetoolStrength, codeformerFidelity } = options; const {
postprocessing: { facetoolType, facetoolStrength, codeformerFidelity },
} = getState();
const facetoolParameters: Record<string, unknown> = { const facetoolParameters: Record<string, unknown> = {
facetool_strength: facetoolStrength, facetool_strength: facetoolStrength,

View File

@ -1,24 +1,24 @@
import { AnyAction, MiddlewareAPI, Dispatch } from '@reduxjs/toolkit'; import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import i18n from 'i18n'; import i18n from 'i18n';
import { v4 as uuidv4 } from 'uuid';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
import { import {
addLogEntry, addLogEntry,
addToast,
errorOccurred,
processingCanceled,
setCurrentStatus,
setFoundModels,
setIsCancelable,
setIsConnected, setIsConnected,
setIsProcessing, setIsProcessing,
setSystemStatus,
setCurrentStatus,
setSystemConfig,
processingCanceled,
errorOccurred,
setModelList, setModelList,
setIsCancelable,
addToast,
setFoundModels,
setSearchFolder, setSearchFolder,
setSystemConfig,
setSystemStatus,
} from 'features/system/store/systemSlice'; } from 'features/system/store/systemSlice';
import { import {
@ -30,20 +30,20 @@ import {
setIntermediateImage, setIntermediateImage,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import type { RootState } from 'app/store';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { import {
clearInitialImage, clearInitialImage,
setInfillMethod, setInfillMethod,
setInitialImage, setInitialImage,
setMaskPath, setMaskPath,
} from 'features/options/store/optionsSlice'; } from 'features/parameters/store/generationSlice';
import { tabMap } from 'features/ui/store/tabMap';
import { import {
requestImages, requestImages,
requestNewImages, requestNewImages,
requestSystemConfig, requestSystemConfig,
} from './actions'; } from './actions';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { tabMap } from 'features/tabs/tabMap';
import type { RootState } from 'app/store';
/** /**
* Returns an object containing listener callbacks for socketio events. * Returns an object containing listener callbacks for socketio events.
@ -104,8 +104,9 @@ const makeSocketIOListeners = (
*/ */
onGenerationResult: (data: InvokeAI.ImageResultResponse) => { onGenerationResult: (data: InvokeAI.ImageResultResponse) => {
try { try {
const state: RootState = getState(); const state = getState();
const { shouldLoopback, activeTab } = state.options; const { activeTab } = state.ui;
const { shouldLoopback } = state.postprocessing;
const { boundingBox: _, generationMode, ...rest } = data; const { boundingBox: _, generationMode, ...rest } = data;
const newImage = { const newImage = {
@ -327,7 +328,9 @@ const makeSocketIOListeners = (
dispatch(removeImage(data)); dispatch(removeImage(data));
// remove references to image in options // remove references to image in options
const { initialImage, maskPath } = getState().options; const {
generation: { initialImage, maskPath },
} = getState();
if ( if (
initialImage === url || initialImage === url ||

View File

@ -1,8 +1,8 @@
import { Middleware } from '@reduxjs/toolkit'; import { Middleware } from '@reduxjs/toolkit';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
import makeSocketIOListeners from './listeners';
import makeSocketIOEmitters from './emitters'; import makeSocketIOEmitters from './emitters';
import makeSocketIOListeners from './listeners';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
@ -26,7 +26,7 @@ export const socketioMiddleware = () => {
const socketio = io(origin, { const socketio = io(origin, {
timeout: 60000, timeout: 60000,
path: window.location.pathname + 'socket.io', path: `${window.location.pathname}socket.io`,
}); });
let areListenersSet = false; let areListenersSet = false;

View File

@ -5,10 +5,13 @@ import storage from 'redux-persist/lib/storage'; // defaults to localStorage for
import { getPersistConfig } from 'redux-deep-persist'; import { getPersistConfig } from 'redux-deep-persist';
import optionsReducer from 'features/options/store/optionsSlice';
import galleryReducer from 'features/gallery/store/gallerySlice';
import systemReducer from 'features/system/store/systemSlice';
import canvasReducer from 'features/canvas/store/canvasSlice'; import canvasReducer from 'features/canvas/store/canvasSlice';
import galleryReducer from 'features/gallery/store/gallerySlice';
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 uiReducer from 'features/ui/store/uiSlice';
import { socketioMiddleware } from './socketio/middleware'; import { socketioMiddleware } from './socketio/middleware';
@ -58,10 +61,13 @@ const galleryBlacklist = [
].map((blacklistItem) => `gallery.${blacklistItem}`); ].map((blacklistItem) => `gallery.${blacklistItem}`);
const rootReducer = combineReducers({ const rootReducer = combineReducers({
options: optionsReducer, generation: generationReducer,
postprocessing: postprocessingReducer,
gallery: galleryReducer, gallery: galleryReducer,
system: systemReducer, system: systemReducer,
canvas: canvasReducer, canvas: canvasReducer,
ui: uiReducer,
lightbox: lightboxReducer,
}); });
const rootPersistConfig = getPersistConfig({ const rootPersistConfig = getPersistConfig({
@ -89,8 +95,8 @@ export const store = configureStore({
'canvas/setStageCoordinates', 'canvas/setStageCoordinates',
'canvas/setStageScale', 'canvas/setStageScale',
'canvas/setIsDrawing', 'canvas/setIsDrawing',
// 'canvas/setBoundingBoxCoordinates', 'canvas/setBoundingBoxCoordinates',
// 'canvas/setBoundingBoxDimensions', 'canvas/setBoundingBoxDimensions',
'canvas/setIsDrawing', 'canvas/setIsDrawing',
'canvas/addPointToCurrentLine', 'canvas/addPointToCurrentLine',
], ],

View File

@ -1,7 +1,7 @@
import { Box, forwardRef, Icon } from '@chakra-ui/react'; import { Box, forwardRef, Icon } from '@chakra-ui/react';
import { Feature } from 'app/features';
import { IconType } from 'react-icons'; import { IconType } from 'react-icons';
import { MdHelp } from 'react-icons/md'; import { MdHelp } from 'react-icons/md';
import { Feature } from 'app/features';
import GuidePopover from './GuidePopover'; import GuidePopover from './GuidePopover';
type GuideIconProps = { type GuideIconProps = {

View File

@ -1,29 +1,29 @@
import { import {
Box,
Popover, Popover,
PopoverArrow, PopoverArrow,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
Box,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { SystemState } from 'features/system/store/systemSlice';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { ReactElement } from 'react';
import { Feature, useFeatureHelpInfo } from 'app/features'; import { Feature, useFeatureHelpInfo } from 'app/features';
import { useAppSelector } from 'app/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors';
import { SystemState } from 'features/system/store/systemSlice';
import { ReactElement } from 'react';
type GuideProps = { type GuideProps = {
children: ReactElement; children: ReactElement;
feature: Feature; feature: Feature;
}; };
const systemSelector = createSelector( const guidePopoverSelector = createSelector(
(state: RootState) => state.system, systemSelector,
(system: SystemState) => system.shouldDisplayGuides (system: SystemState) => system.shouldDisplayGuides
); );
const GuidePopover = ({ children, feature }: GuideProps) => { const GuidePopover = ({ children, feature }: GuideProps) => {
const shouldDisplayGuides = useAppSelector(systemSelector); const shouldDisplayGuides = useAppSelector(guidePopoverSelector);
const { text } = useFeatureHelpInfo(feature); const { text } = useFeatureHelpInfo(feature);
if (!shouldDisplayGuides) return null; if (!shouldDisplayGuides) return null;

View File

@ -1,9 +1,9 @@
import { import {
IconButtonProps, forwardRef,
IconButton, IconButton,
IconButtonProps,
Tooltip, Tooltip,
TooltipProps, TooltipProps,
forwardRef,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
export type IAIIconButtonProps = IconButtonProps & { export type IAIIconButtonProps = IconButtonProps & {

View File

@ -1,19 +1,20 @@
import { import {
FormControl, FormControl,
FormControlProps,
FormLabel,
FormLabelProps,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput, NumberInput,
NumberInputField, NumberInputField,
NumberIncrementStepper,
NumberDecrementStepper,
NumberInputProps,
FormLabel,
NumberInputFieldProps, NumberInputFieldProps,
NumberInputProps,
NumberInputStepperProps, NumberInputStepperProps,
FormControlProps,
FormLabelProps,
TooltipProps,
Tooltip, Tooltip,
TooltipProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import _ from 'lodash'; import { clamp } from 'lodash';
import { FocusEvent, useEffect, useState } from 'react'; import { FocusEvent, useEffect, useState } from 'react';
const numberStringRegex = /^-?(0\.)?\.?$/; const numberStringRegex = /^-?(0\.)?\.?$/;
@ -104,7 +105,7 @@ const IAINumberInput = (props: Props) => {
* clamp it on blur and floor it if needed. * clamp it on blur and floor it if needed.
*/ */
const handleBlur = (e: FocusEvent<HTMLInputElement>) => { const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
const clamped = _.clamp( const clamped = clamp(
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value), isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
min, min,
max max

View File

@ -1,11 +1,11 @@
import { import {
BoxProps,
Popover, Popover,
PopoverArrow, PopoverArrow,
PopoverContent, PopoverContent,
PopoverProps,
PopoverTrigger, PopoverTrigger,
BoxProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { PopoverProps } from '@chakra-ui/react';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
type IAIPopoverProps = PopoverProps & { type IAIPopoverProps = PopoverProps & {

View File

@ -23,10 +23,11 @@ import {
Tooltip, Tooltip,
TooltipProps, TooltipProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React, { FocusEvent, useMemo, useState, useEffect } from 'react'; import { clamp } from 'lodash';
import { FocusEvent, useEffect, useMemo, useState } from 'react';
import { BiReset } from 'react-icons/bi'; import { BiReset } from 'react-icons/bi';
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton'; import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
import _ from 'lodash';
export type IAIFullSliderProps = { export type IAIFullSliderProps = {
label: string; label: string;
@ -122,7 +123,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => { const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
if (e.target.value === '') e.target.value = String(min); if (e.target.value === '') e.target.value = String(min);
const clamped = _.clamp( const clamped = clamp(
isInteger ? Math.floor(Number(e.target.value)) : Number(localInputValue), isInteger ? Math.floor(Number(e.target.value)) : Number(localInputValue),
min, min,
numberInputMax numberInputMax

View File

@ -1,20 +1,20 @@
import {
useCallback,
ReactNode,
useState,
useEffect,
KeyboardEvent,
} from 'react';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { FileRejection, useDropzone } from 'react-dropzone';
import { useToast } from '@chakra-ui/react'; import { useToast } from '@chakra-ui/react';
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext'; import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors'; import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { tabDict } from 'features/tabs/components/InvokeTabs';
import ImageUploadOverlay from './ImageUploadOverlay';
import { uploadImage } from 'features/gallery/store/thunks/uploadImage';
import useImageUploader from 'common/hooks/useImageUploader'; import useImageUploader from 'common/hooks/useImageUploader';
import { uploadImage } from 'features/gallery/store/thunks/uploadImage';
import { tabDict } from 'features/ui/components/InvokeTabs';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import {
KeyboardEvent,
ReactNode,
useCallback,
useEffect,
useState,
} from 'react';
import { FileRejection, useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import ImageUploadOverlay from './ImageUploadOverlay';
type ImageUploaderProps = { type ImageUploaderProps = {
children: ReactNode; children: ReactNode;
@ -33,7 +33,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
(rejection: FileRejection) => { (rejection: FileRejection) => {
setIsHandlingUpload(true); setIsHandlingUpload(true);
const msg = rejection.errors.reduce( const msg = rejection.errors.reduce(
(acc: string, cur: { message: string }) => acc + '\n' + cur.message, (acc: string, cur: { message: string }) => `${acc}\n${cur.message}`,
'' ''
); );
toast({ toast({

View File

@ -1,7 +1,7 @@
import { Heading } from '@chakra-ui/react'; import { Heading } from '@chakra-ui/react';
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
import { useContext } from 'react'; import { useContext } from 'react';
import { FaUpload } from 'react-icons/fa'; import { FaUpload } from 'react-icons/fa';
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
type ImageUploaderButtonProps = { type ImageUploaderButtonProps = {
styleClass?: string; styleClass?: string;

View File

@ -1,6 +1,6 @@
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
import { useContext } from 'react'; import { useContext } from 'react';
import { FaUpload } from 'react-icons/fa'; import { FaUpload } from 'react-icons/fa';
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
import IAIIconButton from './IAIIconButton'; import IAIIconButton from './IAIIconButton';
const ImageUploaderIconButton = () => { const ImageUploaderIconButton = () => {

View File

@ -1,4 +1,3 @@
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export default function NodesWIP() { export default function NodesWIP() {

View File

@ -1,4 +1,3 @@
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const PostProcessingWIP = () => { export const PostProcessingWIP = () => {

View File

@ -1,4 +1,3 @@
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export default function TrainingWIP() { export default function TrainingWIP() {

View File

@ -1,6 +1,5 @@
import { Tooltip } from '@chakra-ui/react'; import { Tooltip } from '@chakra-ui/react';
import * as Slider from '@radix-ui/react-slider'; import * as Slider from '@radix-ui/react-slider';
import React from 'react';
type IAISliderProps = Slider.SliderProps & { type IAISliderProps = Slider.SliderProps & {
value: number[]; value: number[];

View File

@ -11,7 +11,6 @@ const useClickOutsideWatcher = () => {
function handleClickOutside(e: MouseEvent) { function handleClickOutside(e: MouseEvent) {
watchers.forEach(({ ref, enable, callback }) => { watchers.forEach(({ ref, enable, callback }) => {
if (enable && ref.current && !ref.current.contains(e.target as Node)) { if (enable && ref.current && !ref.current.contains(e.target as Node)) {
console.log('callback');
callback(); callback();
} }
}); });

View File

@ -0,0 +1,20 @@
import * as InvokeAI from 'app/invokeai';
import promptToString from './promptToString';
export function getPromptAndNegative(input_prompt: InvokeAI.Prompt) {
let prompt: string = promptToString(input_prompt);
let negativePrompt: string | null = null;
const negativePromptRegExp = new RegExp(/(?<=\[)[^\][]*(?=])/, 'gi');
const negativePromptMatches = [...prompt.matchAll(negativePromptRegExp)];
if (negativePromptMatches && negativePromptMatches.length > 0) {
negativePrompt = negativePromptMatches.join(', ');
prompt = prompt
.replaceAll(negativePromptRegExp, '')
.replaceAll('[]', '')
.trim();
}
return [prompt, negativePrompt];
}

View File

@ -1,27 +1,29 @@
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants'; import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
import { OptionsState } from 'features/options/store/optionsSlice'; import { Dimensions } from 'features/canvas/store/canvasTypes';
import { GenerationState } from 'features/parameters/store/generationSlice';
import { SystemState } from 'features/system/store/systemSlice'; import { SystemState } from 'features/system/store/systemSlice';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import { Dimensions } from 'features/canvas/store/canvasTypes';
import { stringToSeedWeightsArray } from './seedWeightPairs';
import randomInt from './randomInt';
import { InvokeTabName } from 'features/tabs/tabMap';
import { import {
CanvasState, CanvasState,
isCanvasMaskLine, isCanvasMaskLine,
} from 'features/canvas/store/canvasTypes'; } from 'features/canvas/store/canvasTypes';
import generateMask from 'features/canvas/util/generateMask'; import generateMask from 'features/canvas/util/generateMask';
import openBase64ImageInTab from './openBase64ImageInTab';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import type { import type {
UpscalingLevel,
FacetoolType, FacetoolType,
} from 'features/options/store/optionsSlice'; UpscalingLevel,
} from 'features/parameters/store/postprocessingSlice';
import { PostprocessingState } from 'features/parameters/store/postprocessingSlice';
import { InvokeTabName } from 'features/ui/store/tabMap';
import openBase64ImageInTab from './openBase64ImageInTab';
import randomInt from './randomInt';
import { stringToSeedWeightsArray } from './seedWeightPairs';
export type FrontendToBackendParametersConfig = { export type FrontendToBackendParametersConfig = {
generationMode: InvokeTabName; generationMode: InvokeTabName;
optionsState: OptionsState; generationState: GenerationState;
postprocessingState: PostprocessingState;
canvasState: CanvasState; canvasState: CanvasState;
systemState: SystemState; systemState: SystemState;
imageToProcessUrl?: string; imageToProcessUrl?: string;
@ -91,21 +93,38 @@ export const frontendToBackendParameters = (
): BackendParameters => { ): BackendParameters => {
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
const { generationMode, optionsState, canvasState, systemState } = config; const {
generationMode,
generationState,
postprocessingState,
canvasState,
systemState,
} = config;
const { const {
cfgScale,
codeformerFidelity, codeformerFidelity,
facetoolStrength, facetoolStrength,
facetoolType, facetoolType,
height,
hiresFix, hiresFix,
hiresStrength,
shouldRunESRGAN,
shouldRunFacetool,
upscalingLevel,
upscalingStrength,
} = postprocessingState;
const {
cfgScale,
height,
img2imgStrength, img2imgStrength,
infillMethod, infillMethod,
initialImage, initialImage,
iterations, iterations,
perlin, perlin,
prompt, prompt,
negativePrompt,
sampler, sampler,
seamBlur, seamBlur,
seamless, seamless,
@ -117,16 +136,14 @@ export const frontendToBackendParameters = (
shouldFitToWidthHeight, shouldFitToWidthHeight,
shouldGenerateVariations, shouldGenerateVariations,
shouldRandomizeSeed, shouldRandomizeSeed,
shouldRunESRGAN,
shouldRunFacetool,
steps, steps,
threshold, threshold,
tileSize, tileSize,
upscalingLevel,
upscalingStrength,
variationAmount, variationAmount,
width, width,
} = optionsState; } = generationState;
const { const {
shouldDisplayInProgressType, shouldDisplayInProgressType,
@ -155,6 +172,10 @@ export const frontendToBackendParameters = (
let esrganParameters: false | BackendEsrGanParameters = false; let esrganParameters: false | BackendEsrGanParameters = false;
let facetoolParameters: false | BackendFacetoolParameters = false; let facetoolParameters: false | BackendFacetoolParameters = false;
if (negativePrompt !== '') {
generationParameters.prompt = `${prompt} [${negativePrompt}]`;
}
generationParameters.seed = shouldRandomizeSeed generationParameters.seed = shouldRandomizeSeed
? randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX) ? randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)
: seed; : seed;
@ -164,6 +185,8 @@ export const frontendToBackendParameters = (
generationParameters.seamless = seamless; generationParameters.seamless = seamless;
generationParameters.hires_fix = hiresFix; generationParameters.hires_fix = hiresFix;
if (hiresFix) generationParameters.strength = hiresStrength;
if (shouldRunESRGAN) { if (shouldRunESRGAN) {
esrganParameters = { esrganParameters = {
level: upscalingLevel, level: upscalingLevel,

View File

@ -63,6 +63,6 @@ export const stringToSeedWeightsArray = (
const stringPairs = string.split(','); const stringPairs = string.split(',');
const arrPairs = stringPairs.map((p) => p.split(':')); const arrPairs = stringPairs.map((p) => p.split(':'));
return arrPairs.map( return arrPairs.map(
(p: Array<string>): Array<number> => [parseInt(p[0]), parseFloat(p[1])] (p: Array<string>): Array<number> => [parseInt(p[0], 10), parseFloat(p[1])]
); );
}; };

View File

@ -1,37 +1,38 @@
import { useCallback, useRef } from 'react'; import { createSelector } from '@reduxjs/toolkit';
import Konva from 'konva';
import { Layer, Stage } from 'react-konva';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
} from 'features/canvas/store/canvasSelectors'; } from 'features/canvas/store/canvasSelectors';
import IAICanvasMaskLines from './IAICanvasMaskLines'; import Konva from 'konva';
import IAICanvasToolPreview from './IAICanvasToolPreview'; import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox'; import { isEqual } from 'lodash';
import { useCallback, useRef } from 'react';
import { Layer, Stage } from 'react-konva';
import useCanvasDragMove from '../hooks/useCanvasDragMove';
import useCanvasHotkeys from '../hooks/useCanvasHotkeys'; import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
import _ from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
import IAICanvasMaskCompositer from './IAICanvasMaskCompositer';
import useCanvasWheel from '../hooks/useCanvasZoom';
import useCanvasMouseDown from '../hooks/useCanvasMouseDown'; import useCanvasMouseDown from '../hooks/useCanvasMouseDown';
import useCanvasMouseUp from '../hooks/useCanvasMouseUp';
import useCanvasMouseMove from '../hooks/useCanvasMouseMove'; import useCanvasMouseMove from '../hooks/useCanvasMouseMove';
import useCanvasMouseOut from '../hooks/useCanvasMouseOut'; import useCanvasMouseOut from '../hooks/useCanvasMouseOut';
import useCanvasDragMove from '../hooks/useCanvasDragMove'; import useCanvasMouseUp from '../hooks/useCanvasMouseUp';
import IAICanvasObjectRenderer from './IAICanvasObjectRenderer'; import useCanvasWheel from '../hooks/useCanvasZoom';
import IAICanvasGrid from './IAICanvasGrid';
import IAICanvasIntermediateImage from './IAICanvasIntermediateImage';
import IAICanvasStatusText from './IAICanvasStatusText';
import IAICanvasStagingArea from './IAICanvasStagingArea';
import IAICanvasStagingAreaToolbar from './IAICanvasStagingAreaToolbar';
import { import {
setCanvasBaseLayer, setCanvasBaseLayer,
setCanvasStage, setCanvasStage,
} from '../util/konvaInstanceProvider'; } from '../util/konvaInstanceProvider';
import { KonvaEventObject } from 'konva/lib/Node';
import IAICanvasBoundingBoxOverlay from './IAICanvasBoundingBoxOverlay'; import IAICanvasBoundingBoxOverlay from './IAICanvasBoundingBoxOverlay';
import IAICanvasGrid from './IAICanvasGrid';
import IAICanvasIntermediateImage from './IAICanvasIntermediateImage';
import IAICanvasMaskCompositer from './IAICanvasMaskCompositer';
import IAICanvasMaskLines from './IAICanvasMaskLines';
import IAICanvasObjectRenderer from './IAICanvasObjectRenderer';
import IAICanvasStagingArea from './IAICanvasStagingArea';
import IAICanvasStagingAreaToolbar from './IAICanvasStagingAreaToolbar';
import IAICanvasStatusText from './IAICanvasStatusText';
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
import IAICanvasToolPreview from './IAICanvasToolPreview';
const selector = createSelector( const selector = createSelector(
[canvasSelector, isStagingSelector], [canvasSelector, isStagingSelector],
@ -82,7 +83,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,6 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import _ from 'lodash'; import { isEqual } from 'lodash';
import { Group, Rect } from 'react-konva'; import { Group, Rect } from 'react-konva';
import { canvasSelector } from '../store/canvasSelectors'; import { canvasSelector } from '../store/canvasSelectors';
@ -27,7 +28,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -3,10 +3,11 @@
import { useColorMode } from '@chakra-ui/react'; import { useColorMode } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import _ from 'lodash'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { isEqual, range } from 'lodash';
import { ReactNode, useCallback, useLayoutEffect, useState } from 'react'; import { ReactNode, useCallback, useLayoutEffect, useState } from 'react';
import { Group, Line as KonvaLine } from 'react-konva'; import { Group, Line as KonvaLine } from 'react-konva';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const selector = createSelector( const selector = createSelector(
[canvasSelector], [canvasSelector],
@ -16,7 +17,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );
@ -85,7 +86,7 @@ const IAICanvasGrid = () => {
xSteps = Math.round(xSize / 64) + 1, xSteps = Math.round(xSize / 64) + 1,
ySteps = Math.round(ySize / 64) + 1; ySteps = Math.round(ySize / 64) + 1;
const xLines = _.range(0, xSteps).map((i) => ( const xLines = range(0, xSteps).map((i) => (
<KonvaLine <KonvaLine
key={`x_${i}`} key={`x_${i}`}
x={fullRect.x1 + i * 64} x={fullRect.x1 + i * 64}
@ -95,7 +96,7 @@ const IAICanvasGrid = () => {
strokeWidth={1} strokeWidth={1}
/> />
)); ));
const yLines = _.range(0, ySteps).map((i) => ( const yLines = range(0, ySteps).map((i) => (
<KonvaLine <KonvaLine
key={`y_${i}`} key={`y_${i}`}
x={fullRect.x1} x={fullRect.x1}

View File

@ -3,7 +3,8 @@ import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import { GalleryState } from 'features/gallery/store/gallerySlice'; import { GalleryState } from 'features/gallery/store/gallerySlice';
import { ImageConfig } from 'konva/lib/shapes/Image'; import { ImageConfig } from 'konva/lib/shapes/Image';
import _ from 'lodash'; import { isEqual } from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Image as KonvaImage } from 'react-konva'; import { Image as KonvaImage } from 'react-konva';
@ -14,7 +15,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,13 +1,13 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { RectConfig } from 'konva/lib/shapes/Rect'; import { RectConfig } from 'konva/lib/shapes/Rect';
import { Rect } from 'react-konva'; import { Rect } from 'react-konva';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { useCallback, useEffect, useRef, useState } from 'react';
import Konva from 'konva'; import Konva from 'konva';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
export const canvasMaskCompositerSelector = createSelector( export const canvasMaskCompositerSelector = createSelector(
canvasSelector, canvasSelector,

View File

@ -1,10 +1,11 @@
import { GroupConfig } from 'konva/lib/Group';
import { Group, Line } from 'react-konva';
import { useAppSelector } from 'app/storeHooks';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash';
import { Group, Line } from 'react-konva';
import { isCanvasMaskLine } from '../store/canvasTypes'; import { isCanvasMaskLine } from '../store/canvasTypes';
import _ from 'lodash';
export const canvasLinesSelector = createSelector( export const canvasLinesSelector = createSelector(
[canvasSelector], [canvasSelector],
@ -13,7 +14,7 @@ export const canvasLinesSelector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,6 +1,9 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import _ from 'lodash'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { isEqual } from 'lodash';
import { Group, Line, Rect } from 'react-konva'; import { Group, Line, Rect } from 'react-konva';
import { import {
isCanvasBaseImage, isCanvasBaseImage,
@ -9,8 +12,6 @@ import {
isCanvasFillRect, isCanvasFillRect,
} from '../store/canvasTypes'; } from '../store/canvasTypes';
import IAICanvasImage from './IAICanvasImage'; import IAICanvasImage from './IAICanvasImage';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const selector = createSelector( const selector = createSelector(
[canvasSelector], [canvasSelector],
@ -24,7 +25,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,18 +1,18 @@
import { Spinner } from '@chakra-ui/react'; import { Spinner } from '@chakra-ui/react';
import { useLayoutEffect, useRef } from 'react'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors'; import {
canvasSelector,
initialCanvasImageSelector,
} from 'features/canvas/store/canvasSelectors';
import { import {
resizeAndScaleCanvas, resizeAndScaleCanvas,
resizeCanvas, resizeCanvas,
setCanvasContainerDimensions, setCanvasContainerDimensions,
setDoesCanvasNeedScaling, setDoesCanvasNeedScaling,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { createSelector } from '@reduxjs/toolkit'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { import { useLayoutEffect, useRef } from 'react';
canvasSelector,
initialCanvasImageSelector,
} from 'features/canvas/store/canvasSelectors';
const canvasResizerSelector = createSelector( const canvasResizerSelector = createSelector(
canvasSelector, canvasSelector,

View File

@ -1,9 +1,10 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import { GroupConfig } from 'konva/lib/Group';
import _ from 'lodash';
import { Group, Rect } from 'react-konva';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash';
import { Group, Rect } from 'react-konva';
import IAICanvasImage from './IAICanvasImage'; import IAICanvasImage from './IAICanvasImage';
const selector = createSelector( const selector = createSelector(
@ -34,7 +35,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,18 +1,8 @@
import { ButtonGroup, Flex } from '@chakra-ui/react'; import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import _ from 'lodash';
import { useCallback } from 'react';
import {
FaArrowLeft,
FaArrowRight,
FaCheck,
FaEye,
FaEyeSlash,
FaPlus,
FaSave,
} from 'react-icons/fa';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { import {
commitStagingAreaImage, commitStagingAreaImage,
@ -22,9 +12,20 @@ import {
setShouldShowStagingImage, setShouldShowStagingImage,
setShouldShowStagingOutline, setShouldShowStagingOutline,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { isEqual } from 'lodash';
import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import {
FaArrowLeft,
FaArrowRight,
FaCheck,
FaEye,
FaEyeSlash,
FaPlus,
FaSave,
} from 'react-icons/fa';
const selector = createSelector( const selector = createSelector(
[canvasSelector], [canvasSelector],
@ -48,7 +49,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,10 +1,11 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import _ from 'lodash';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos'; import { isEqual } from 'lodash';
import roundToHundreth from '../util/roundToHundreth';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import roundToHundreth from '../util/roundToHundreth';
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos';
const selector = createSelector( const selector = createSelector(
[canvasSelector], [canvasSelector],
@ -59,7 +60,7 @@ const selector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

View File

@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import React from 'react';
import _ from 'lodash';
import roundToHundreth from 'features/canvas/util/roundToHundreth'; import roundToHundreth from 'features/canvas/util/roundToHundreth';
import { isEqual } from 'lodash';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const cursorPositionSelector = createSelector( const cursorPositionSelector = createSelector(
@ -23,7 +23,7 @@ const cursorPositionSelector = createSelector(
}, },
{ {
memoizeOptions: { memoizeOptions: {
resultEqualityCheck: _.isEqual, resultEqualityCheck: isEqual,
}, },
} }
); );

Some files were not shown because too many files have changed in this diff Show More