Compare commits

...

88 Commits

Author SHA1 Message Date
mauwii
8aa4a258f4 replace old fashined markdown templates with forms
this will help the readability of issues a lot 🤓
2022-11-03 16:28:06 -04:00
Lincoln Stein
174a9b78b0 Bring main back into a consistent state with other branches
- Due to misuse of rebase command, main was transiently
  in an inconsistent state.

- This repairs the damage, and adds a few post-release
  patches that ensure stable conda installs on Mac and Windows.
2022-11-03 15:44:06 -04:00
Lincoln Stein
aa247e68be use refined model by default 2022-11-02 18:29:34 -04:00
Lincoln Stein
895c47fd11 Merge branch 'patch-9' of https://github.com/spezialspezial/stable-diffusion into spezialspezial-patch-9 2022-11-02 18:24:55 -04:00
Lincoln Stein
0c32d7b507 add release-candidate-branch to mkdocs action 2022-11-02 18:17:16 -04:00
Lincoln Stein
09625eae66 webgui working again 2022-11-02 18:07:18 -04:00
Lincoln Stein
76249b3d4e copy dev frontend code over again 2022-11-02 17:56:30 -04:00
damian
d85cd99f17 add option to show intermediate latent space 2022-11-02 17:53:11 -04:00
damian
f4576dcc2d update frontend 2022-11-02 17:51:01 -04:00
damian
62fe308f84 wip base64 2022-11-02 17:47:25 -04:00
psychedelicious
9b984e0d1e resolve conflicts 2022-11-02 17:45:48 -04:00
damian0815
5502b29340 do not try to save base64 intermediates in gallery on cancellation 2022-11-02 17:44:53 -04:00
damian0815
15fa246ccf convert progress display to a drop-down 2022-11-02 17:44:14 -04:00
damian0815
4929ae6c1d shorter strings 2022-11-02 17:41:42 -04:00
Lincoln Stein
16a52a607d updated documentation 2022-11-02 17:28:50 -04:00
Lincoln Stein
7c68eff99f remove unused frontend assets 2022-11-02 17:10:20 -04:00
Lincoln Stein
2048a47b85 copy frontend from dev 2022-11-02 17:08:00 -04:00
damian0815
f73d5a647d Final WebUI build for Release 2.1
- squashed commit of 52 commits from PR #1327

don't log base64 progress images

Fresh Build For WebUI

[WebUI] Loopback Default False

Fixes bugs/styling

- Fixes missing web app state on new version:
Adds stateReconciler to redux-persist.

When we add more values to the state and then release the update app, they will be automatically merged in.

Reseting web UI will be needed far less.
7159ec

- Fixes console z-index
- Moves reset web UI button to visible area

Decreases gallery width on inpainting

Increases workarea split padding to 1rem

Adds missing tooltips to site header

Changes inpainting controls settings to hover

Fixes hotkeys and settings buttons not working

Improves bounding box interactions

- Bounding box can now be moved by dragging any of its edges
- Bounding box does not affect drawing if already drawing a stroke
- Can lock bounding box to draw directly on the bounding box edges
- Removes spacebar-hold behaviour due to technical issues

Fixes silent crash when init image too large

To send the mask to the server, the UI rendered the mask onto the init image and sent the whole image. The mask was then cropped by the server.

If the image was too large, the app silently failed. Maybe it exceeds the websocket size limit.

Fixed by cropping the mask in the UI layer, sending only bounding-box-sized mask image data.

Disabled bounding box settings when locked

Styles image uploader

Builds fresh bundle

Improves bounding box interaction

Added spacebar-hold-to-transform back.

Address bounding box feedback

- Adds back toggle to hide bounding box
- Box quick toggle = q, normal toggle = shift + q
- Styles canvas alert icons

Adds hints when unable to invoke

- Popover on Invoke button indicates why exactly it is disabled, e.g. prompt is empty, something else is processing, etc.
- There may be more than one reason; all are displayed.

Fix Inpainting Alerts Styling

Preventing unnecessary re-renders across the app

Code Split Inpaint Options

Isolate features to their own components so they dont re-render the other stuff each time.

[TESTING] Remove  global isReady checking

I dont believe this is need at all because the isready state is constantly updated when needed and tracked real time in the Redux store. This causes massive re-renders. @psychedelicious If this is absolutely essential for a reason that I do not see, please hit me up on Discord.

Fresh Bundle

Fix Bounding Box Settings re-rendering on brush stroke

[Code Splitting] Bounding Box Options

Isolated all bounding box components to trigger unnecessary re-renders. Still need to fix  bounding box  triggering re-renders on the control panel inside the canvas itself. But the options panel should be a good to go with this change.

Inpainting Controls Code Spitting and Performance

Codesplit the entirety of the inpainting controls. Created new selectors for each and every component to ensure there are no unnecessary re-renders. App feels a lot smoother.

Fixes rerenders on ClearBrushHistory

Fixes crash when requesting post-generation upscale/face restoration

- Moves the inpainting paste to before the postprocessing.

Removes unused isReady state

Changes Report Bug icon to a bug

Restores shift+q bounding box shortcut

Adds alert for bounding box size to status icons

Adds asCheckbox to IAIIconButton

Rough draft of this. Not happy with the styling but it's clearer than having them look just like buttons.

Fixes crash related to old value of progress_latents in state

Styling changes and settings modal minor refactor

Fixes: uploaded JPG images not loading

Reworks CurrentImageButtons.tsx

- Change all icons to FA iconset for consistency
- Refactors IAIIconButton, IAIButton, IAIPopover to handle ref forwarding
- Redesigns buttons into group

Only generate 1 iteration when seed fixed & variations disabled

Fixes progress images select

Fixes edge case: upload over gets stuck while alt tabbing

- Press esc to close it now

Fixes display progress images select typing

Fixes current image button rerenders

Adds min width to ImageUploader

Makes fast-latents in progress default

Update Icon Button Checkbox Style Styling

Fixes next/prev image buttons

Refactor canvas buttons + more

Add Save Intermediates Step Count

For accurate mode only.

Co-Authored-By: Richard Macarthy <richardmacarthy@protonmail.com>

Restores "initial image" text

Address feedback

- moves mask clear button
- fixes intermediates
- shrinks inpainting icons by 10%

Fix Loopback Styling

Adds escape hotkey to close floating panels

Readd Hotkey for Dual Display

Updated Current Image Button Styling
2022-11-02 17:01:02 -04:00
blessedcoolant
365e2dde1b [WebUI] Final 2.1 Release Build 2022-11-02 16:48:35 -04:00
Lincoln Stein
a48e021c0b remove antlr4 from requirements 2022-11-02 16:35:14 -04:00
spezialspezial
825fa6977d Update outcrop.py 2022-11-02 16:33:35 -04:00
spezialspezial
e332529fbd Prevent outcrop error when no callback is supplied 2022-11-02 16:33:35 -04:00
Lincoln Stein
0f6aa7fe19 add antlr4 to requirements to fix Windows conda glitch 2022-11-02 15:31:09 -04:00
Lincoln Stein
b8870d8290 more bug fixes to install scripts 2022-11-02 15:26:02 -04:00
David Burnett
ffa91be3f1 Install older version of torch and matching torchvision, fix pytorch-lightning=1.7.7 2022-11-02 14:49:36 -04:00
Lincoln Stein
2d5294bca1 speculative change for .bat installer 2022-11-02 13:56:17 -04:00
spezialspezial
6c9a2761f5 Optional refined model for Txt2Mask
Don't merge right now, just wanted to show the necessary changes
2022-11-02 00:33:46 +01:00
Lincoln Stein
90d37eac03 update requirements to address #1149 2022-10-18 16:00:59 -04:00
Lincoln Stein
230de023ff resolve doc conflicts during merge 2022-10-18 08:27:33 -04:00
mauwii
febf86dedf Merge branch 'fix-gh-actions' of github.com:mauwii/stable-diffusion into fix-gh-actions 2022-10-18 13:26:03 +02:00
mauwii
76ae17abac update cache steps
remove restore-keys, make keys uniuqe
2022-10-18 13:25:51 +02:00
mauwii
339ff4b464 fix conda pkg cache name
also change content of hashFile-function
2022-10-18 13:25:51 +02:00
mauwii
00c0e487dd move export behind the tests, upload with artifact
also switch to python between 3.9-3.10 and use conda-forge again
2022-10-18 13:25:50 +02:00
mauwii
5c8dfa38be readd pip dependencie in environment-ma.yml 2022-10-18 13:25:50 +02:00
mauwii
acf85c66a5 add current branch to push trigger 2022-10-18 13:25:50 +02:00
mauwii
3619918954 rename step to export conda env 2022-10-18 13:25:50 +02:00
mauwii
65b14683a8 unpin conda package versions in environment.yml 2022-10-18 13:25:50 +02:00
mauwii
f4fc02a3da switch to default channel in environment-mac.yml 2022-10-18 13:25:50 +02:00
mauwii
c334170a93 pin versions only for pip packages 2022-10-18 13:25:50 +02:00
mauwii
deab6c64fc export conda env instead of only print versions 2022-10-18 13:25:50 +02:00
mauwii
e1c9503951 list conda packages after activating env
also want to show how much faster it will run now with cached pkgs
2022-10-18 13:25:50 +02:00
mauwii
9a21812bf5 revert changes to environment.yml
@tildebyte this would not have been pointed out without PR-Validation
2022-10-18 13:25:50 +02:00
mauwii
347b5ce452 fix expression 2022-10-18 13:25:50 +02:00
mauwii
b39029521b use very short validation for Pull Requests 2022-10-18 13:25:49 +02:00
mauwii
97b26f3de2 remove doubled checkpoint cache 2022-10-18 13:25:49 +02:00
mauwii
e19a7a990d unpin versions in environment
as asked by @tildebyte
2022-10-18 13:25:49 +02:00
mauwii
3e424e1046 remove pip from dependencies 2022-10-18 13:25:49 +02:00
mauwii
db20b4af9c remove pr trigger 2022-10-18 13:25:15 +02:00
Matthias Wild
44ff8f8531 squash merge update-gh-actions into fix-gh-actions
* fix mkdocs deployment

* update path to python bin

* add trigger for current branch

* change path to python_bin for mac as well

* try to use setup-python@v4 instead of setting env

* remove setup conda action

* try to use $CONDA

* remove overseen action

* change branch from master to main

* sort out if then else for faster syntax

* remove more if functions

* add updates to create-caches as well

* eliminate the rest of if functions

* try to unpin pytorch and torchvision

* restore pinned versions

* try switching from set-output to use env

* update test-invoke-conda as well

* fix env var creation

* quote variable

* add second equal to compare

* try another way to use outputs

* fix outputs

* pip install for mac before creating conda env

* fix output variable

* fix python bin path

* remove pip install for before creating conda env

* unpin streamlit version in conda mac env

* try to make git-workflows better readable

* remove 4gotten trigger

* Update-gh-actions (#6)

* fix mkdocs deployment

* update path to python bin

* add trigger for current branch

* change path to python_bin for mac as well

* try to use setup-python@v4 instead of setting env

* remove setup conda action

* try to use $CONDA

* remove overseen action

* change branch from master to main

* sort out if then else for faster syntax

* remove more if functions

* add updates to create-caches as well

* eliminate the rest of if functions

* try to unpin pytorch and torchvision

* restore pinned versions

* try switching from set-output to use env

* update test-invoke-conda as well

* fix env var creation

* quote variable

* add second equal to compare

* try another way to use outputs

* fix outputs

* pip install for mac before creating conda env

* fix output variable

* fix python bin path

* remove pip install for before creating conda env

* unpin streamlit version in conda mac env

* try to make git-workflows better readable

* use macos-latest

* try to update conda before creating mac env

* better conda update trial

* re-pin streamlit version

* re-added trigger to run workflow in current branch

* try to find out if conda mac env could be updated

* install cmake, protobuf and rust b4 conda

* add yes to conda update

* lets try anaconda3-2022.05

* try environment.yml for mac as well

* reenable conda mac env, add pip install
also fix gitignore by changing from dream to invoke

* remove
- unecesary virtualenv creation
- conda update

change != macos back to == linux

* remove cmake from brew install since pre-installed

* disable opencv-python pip requirement

* fixed commands to find latest package versions

* update requirements for mac env

* back to the roots - only install conda env
depending on runner_os with or without extra env variables

* check out macOS in azure-pipelines
since becoming kind of tired of the GitHub Runner which is broken as ...

* let's try to setup python and update conda env

* initialize conda before using it

* add trigger in azure-pipelines.yml

* And another go for update first ....

* update azure-pipelines.yml
- add caching
- add checkpoint download
- add paths to trigger
and more

* unquote checkpoint-url

* fix chekpoint-url variable

* mkdir before downloading model

* set pr trigger to main, rename anaconda cache

* unique cacheHitVariables

* try to use macos-latest instead of macos-12

* update test-invoke-conda.yml:
- remove unecesarry echo step
- use s-weigand/setup-conda@v1
- remove conda update from install deps step since updated with action

* update test-invoke-conda.yml:
- rename conda env cache from ldm to invokeai
- reorder steps:
  1. checkout sources
  2. setup python
  3. setup conda
  4. keep order after set platform variables

* change macos back to 12 since also fails with 11

* update condition in run the tests
make difference between main or not main

* fix path to cache invokeai conda env

* fix invokeai conda env cache path

* update mkdocs-flow.yml

* change conda-channel priority

* update create-caches

* update conda env also when cache was used

* os dependend conda env cache path

* use existing CONDA env pointing to conda root

* create CONDA_ROOT output from $CONDA

* use output variable to define test prompts

* use setup-python v4, get rid of PYTHON_BIN env

* add runner.os to result artifacts name

* update test-invoke-conda.yml:
- reuse macos-latest
- disable setup python 3.9
- setup conda with default python version
- create or update conda environment depending on cache success
- remove name parameter from conda update since name is set in env yml

* improve mkdocs-flow.yml

* disable cache-hugginface-torch
since preload_models.py downloads to more than one location

* update mkdocs-flow.yml with new name

* rename mkdocs action to mkdocs-material

* try to ignore error when creating conda env
maybe it would still be usable, lets see ;P

* remove bloat

* update environment-mac.yml
to match dependencies of invoke-ai/InvokeAI's main branch

* disable conda update, tweak prompt condition

* try to set some env vars for macOS to fix conda

* stop ignoring error, use env instead of outputs

* tweak `[[` connditions

* update python and pip dependencie
makes a difference of 1 sec per itteration compared to 3.9!!!
also I see no reason why using a old pip version would be beneficial

* remove unecesarry env for macOS
everything was pre-tested on my MacBook Air 2020 with M1

* update conda env in setup step

* activate conda env after installation

* update test-invoke-conda.yml
- set conda env dependent on matrix.os
- set CONDA_ENV_NAME to prevent breaking action when renaming conda env
- fix conda env activation

* fix activate conda env

* set bash -l as default shell

* use action to activate conda env

* add conda env file to env activation

* try to replace s-weigeand with conda-incubator

* remove azure-pipelines.yml
funniest part is that the macos runner is the same as the one on github!

* include environment-file in matrix
- also disable auto-activate-base and auto-update-conda
- include macos-latest and macos-12 for debugging purpose
- set miniforge-version in matrix

* fix miniforge-variant, set fail-fast to false

* add step to setup miniconda
- make default shell a matrix variable
- remove bloat

* use a mac env yml without pinned versions

* unpin nomkl, pytorch and torchvision
also removed opencv-pyhton

* cache conda pkgs dir instead of conda env

* use python 3.10, exclude macos-12 from cache

* fix expression

* prepare for PR

* fix doubled id

* reuse pinned versions in mac conda env
- updated python pip version
- unpined pytorch and torchvision
- removed opencv-python
- updated versions to most recent (tested locally)

* fix classical copy/paste error

* remove unused env from shell-block comment

* fix hashFiles function to determine restore-keys

* reenable caching `~.cache`, update create-caches

* unpin all versions in mac conda env file
this was the only way I got it working in the action, also works locally
tested on MacBook Air 2020 M1
remove environment-mac-unpinned.yml

* prepare merge by removing this branch from trigger

* include pull_request trigger for main and dev

* remove pull_request trigger
2022-10-18 13:25:15 +02:00
mauwii
a8b794d7e0 update precision info 2022-10-17 22:27:27 -04:00
mauwii
f868362ca8 fix prompt in README.md 2022-10-17 22:27:27 -04:00
mauwii
8858f7e97c (re-) fix a lot in mkdocs 2022-10-17 22:27:27 -04:00
Matthias Wild
2db4969e18 Merge branch 'main' into fix-gh-actions 2022-10-17 23:41:36 +02:00
mauwii
2ecc1abf21 fix links to point to invoke-ai.github.io 2022-10-17 17:40:31 -04:00
mauwii
703bc9494a Merge remote-tracking branch 'upstream/main' into fix-gh-actions-fork 2022-10-17 21:40:16 +02:00
Lincoln Stein
e5ab07091d adding license using GitHub template
Did not attempt to add additional copyright information.
2022-10-17 12:09:24 -04:00
Lincoln Stein
891678b656 remove license files temporarily 2022-10-17 12:08:09 -04:00
Lincoln Stein
39ea2a257c remove additional copyrights from license file
Trying to get GitHub to recognize our MIT license. Perhaps the additional copyrights are confusing it.
2022-10-17 12:07:00 -04:00
Lincoln Stein
2d68eae16b Second try at getting GitHub to register license 2022-10-17 12:05:42 -04:00
spezialspezial
d65948c423 Update gitignore to ignore codeformer weights at new location
Eventually making it slightly more flexible
2022-10-17 11:54:45 -04:00
majick
9910a0b004 Fix broken links to CLI.md
* Looks like there was a bad paste
2022-10-16 23:40:27 -04:00
majick
ff96358cb3 Correct typo in the subtitle of the project
* “Formally” means that there is a formality such as a rule or declaration, “formerly” refers to a prior state.  The latter is almost certainly what is meant here.
2022-10-16 23:40:27 -04:00
mauwii
edf471f655 update cache steps
remove restore-keys, make keys uniuqe
2022-10-17 04:43:06 +02:00
mauwii
5b02c8ca4a fix conda pkg cache name
also change content of hashFile-function
2022-10-17 04:02:38 +02:00
mauwii
e7688c53b8 move export behind the tests, upload with artifact
also switch to python between 3.9-3.10 and use conda-forge again
2022-10-17 03:27:15 +02:00
mauwii
87cada42db readd pip dependencie in environment-ma.yml 2022-10-17 02:22:19 +02:00
mauwii
6fe67ee426 add current branch to push trigger 2022-10-17 02:12:46 +02:00
mauwii
5fbc81885a rename step to export conda env 2022-10-17 02:08:08 +02:00
mauwii
25ba5451f2 unpin conda package versions in environment.yml 2022-10-17 02:07:17 +02:00
mauwii
138c9cf7a8 switch to default channel in environment-mac.yml 2022-10-17 02:05:59 +02:00
mauwii
87981306a3 pin versions only for pip packages 2022-10-17 01:50:19 +02:00
mauwii
f7893b3ea9 export conda env instead of only print versions 2022-10-17 01:48:22 +02:00
mauwii
87395fe6fe list conda packages after activating env
also want to show how much faster it will run now with cached pkgs
2022-10-16 22:48:53 +02:00
mauwii
15f876c66c revert changes to environment.yml
@tildebyte this would not have been pointed out without PR-Validation
2022-10-16 22:02:58 +02:00
mauwii
522c35ac5b fix expression 2022-10-16 21:52:49 +02:00
mauwii
bb2d6d640f use very short validation for Pull Requests 2022-10-16 21:50:57 +02:00
mauwii
2412d8dec1 remove doubled checkpoint cache 2022-10-16 20:53:07 +02:00
mauwii
2ab5a43663 unpin versions in environment
as asked by @tildebyte
2022-10-16 20:48:31 +02:00
mauwii
0ec3d6c10a remove pip from dependencies 2022-10-16 20:36:33 +02:00
mauwii
d208e1b0f5 Merge branch 'fix-gh-actions' of github.com:mauwii/stable-diffusion into fix-gh-actions 2022-10-16 20:35:57 +02:00
mauwii
8a6ba6a212 remove pr trigger 2022-10-16 13:56:45 -04:00
Matthias Wild
b793d69ff3 squash merge update-gh-actions into fix-gh-actions
* fix mkdocs deployment

* update path to python bin

* add trigger for current branch

* change path to python_bin for mac as well

* try to use setup-python@v4 instead of setting env

* remove setup conda action

* try to use $CONDA

* remove overseen action

* change branch from master to main

* sort out if then else for faster syntax

* remove more if functions

* add updates to create-caches as well

* eliminate the rest of if functions

* try to unpin pytorch and torchvision

* restore pinned versions

* try switching from set-output to use env

* update test-invoke-conda as well

* fix env var creation

* quote variable

* add second equal to compare

* try another way to use outputs

* fix outputs

* pip install for mac before creating conda env

* fix output variable

* fix python bin path

* remove pip install for before creating conda env

* unpin streamlit version in conda mac env

* try to make git-workflows better readable

* remove 4gotten trigger

* Update-gh-actions (#6)

* fix mkdocs deployment

* update path to python bin

* add trigger for current branch

* change path to python_bin for mac as well

* try to use setup-python@v4 instead of setting env

* remove setup conda action

* try to use $CONDA

* remove overseen action

* change branch from master to main

* sort out if then else for faster syntax

* remove more if functions

* add updates to create-caches as well

* eliminate the rest of if functions

* try to unpin pytorch and torchvision

* restore pinned versions

* try switching from set-output to use env

* update test-invoke-conda as well

* fix env var creation

* quote variable

* add second equal to compare

* try another way to use outputs

* fix outputs

* pip install for mac before creating conda env

* fix output variable

* fix python bin path

* remove pip install for before creating conda env

* unpin streamlit version in conda mac env

* try to make git-workflows better readable

* use macos-latest

* try to update conda before creating mac env

* better conda update trial

* re-pin streamlit version

* re-added trigger to run workflow in current branch

* try to find out if conda mac env could be updated

* install cmake, protobuf and rust b4 conda

* add yes to conda update

* lets try anaconda3-2022.05

* try environment.yml for mac as well

* reenable conda mac env, add pip install
also fix gitignore by changing from dream to invoke

* remove
- unecesary virtualenv creation
- conda update

change != macos back to == linux

* remove cmake from brew install since pre-installed

* disable opencv-python pip requirement

* fixed commands to find latest package versions

* update requirements for mac env

* back to the roots - only install conda env
depending on runner_os with or without extra env variables

* check out macOS in azure-pipelines
since becoming kind of tired of the GitHub Runner which is broken as ...

* let's try to setup python and update conda env

* initialize conda before using it

* add trigger in azure-pipelines.yml

* And another go for update first ....

* update azure-pipelines.yml
- add caching
- add checkpoint download
- add paths to trigger
and more

* unquote checkpoint-url

* fix chekpoint-url variable

* mkdir before downloading model

* set pr trigger to main, rename anaconda cache

* unique cacheHitVariables

* try to use macos-latest instead of macos-12

* update test-invoke-conda.yml:
- remove unecesarry echo step
- use s-weigand/setup-conda@v1
- remove conda update from install deps step since updated with action

* update test-invoke-conda.yml:
- rename conda env cache from ldm to invokeai
- reorder steps:
  1. checkout sources
  2. setup python
  3. setup conda
  4. keep order after set platform variables

* change macos back to 12 since also fails with 11

* update condition in run the tests
make difference between main or not main

* fix path to cache invokeai conda env

* fix invokeai conda env cache path

* update mkdocs-flow.yml

* change conda-channel priority

* update create-caches

* update conda env also when cache was used

* os dependend conda env cache path

* use existing CONDA env pointing to conda root

* create CONDA_ROOT output from $CONDA

* use output variable to define test prompts

* use setup-python v4, get rid of PYTHON_BIN env

* add runner.os to result artifacts name

* update test-invoke-conda.yml:
- reuse macos-latest
- disable setup python 3.9
- setup conda with default python version
- create or update conda environment depending on cache success
- remove name parameter from conda update since name is set in env yml

* improve mkdocs-flow.yml

* disable cache-hugginface-torch
since preload_models.py downloads to more than one location

* update mkdocs-flow.yml with new name

* rename mkdocs action to mkdocs-material

* try to ignore error when creating conda env
maybe it would still be usable, lets see ;P

* remove bloat

* update environment-mac.yml
to match dependencies of invoke-ai/InvokeAI's main branch

* disable conda update, tweak prompt condition

* try to set some env vars for macOS to fix conda

* stop ignoring error, use env instead of outputs

* tweak `[[` connditions

* update python and pip dependencie
makes a difference of 1 sec per itteration compared to 3.9!!!
also I see no reason why using a old pip version would be beneficial

* remove unecesarry env for macOS
everything was pre-tested on my MacBook Air 2020 with M1

* update conda env in setup step

* activate conda env after installation

* update test-invoke-conda.yml
- set conda env dependent on matrix.os
- set CONDA_ENV_NAME to prevent breaking action when renaming conda env
- fix conda env activation

* fix activate conda env

* set bash -l as default shell

* use action to activate conda env

* add conda env file to env activation

* try to replace s-weigeand with conda-incubator

* remove azure-pipelines.yml
funniest part is that the macos runner is the same as the one on github!

* include environment-file in matrix
- also disable auto-activate-base and auto-update-conda
- include macos-latest and macos-12 for debugging purpose
- set miniforge-version in matrix

* fix miniforge-variant, set fail-fast to false

* add step to setup miniconda
- make default shell a matrix variable
- remove bloat

* use a mac env yml without pinned versions

* unpin nomkl, pytorch and torchvision
also removed opencv-pyhton

* cache conda pkgs dir instead of conda env

* use python 3.10, exclude macos-12 from cache

* fix expression

* prepare for PR

* fix doubled id

* reuse pinned versions in mac conda env
- updated python pip version
- unpined pytorch and torchvision
- removed opencv-python
- updated versions to most recent (tested locally)

* fix classical copy/paste error

* remove unused env from shell-block comment

* fix hashFiles function to determine restore-keys

* reenable caching `~.cache`, update create-caches

* unpin all versions in mac conda env file
this was the only way I got it working in the action, also works locally
tested on MacBook Air 2020 M1
remove environment-mac-unpinned.yml

* prepare merge by removing this branch from trigger

* include pull_request trigger for main and dev

* remove pull_request trigger
2022-10-16 13:56:45 -04:00
mauwii
54f55471df remove pr trigger 2022-10-16 19:34:31 +02:00
Matthias Wild
cec7fb7dc6 squash merge update-gh-actions into fix-gh-actions
* fix mkdocs deployment

* update path to python bin

* add trigger for current branch

* change path to python_bin for mac as well

* try to use setup-python@v4 instead of setting env

* remove setup conda action

* try to use $CONDA

* remove overseen action

* change branch from master to main

* sort out if then else for faster syntax

* remove more if functions

* add updates to create-caches as well

* eliminate the rest of if functions

* try to unpin pytorch and torchvision

* restore pinned versions

* try switching from set-output to use env

* update test-invoke-conda as well

* fix env var creation

* quote variable

* add second equal to compare

* try another way to use outputs

* fix outputs

* pip install for mac before creating conda env

* fix output variable

* fix python bin path

* remove pip install for before creating conda env

* unpin streamlit version in conda mac env

* try to make git-workflows better readable

* remove 4gotten trigger

* Update-gh-actions (#6)

* fix mkdocs deployment

* update path to python bin

* add trigger for current branch

* change path to python_bin for mac as well

* try to use setup-python@v4 instead of setting env

* remove setup conda action

* try to use $CONDA

* remove overseen action

* change branch from master to main

* sort out if then else for faster syntax

* remove more if functions

* add updates to create-caches as well

* eliminate the rest of if functions

* try to unpin pytorch and torchvision

* restore pinned versions

* try switching from set-output to use env

* update test-invoke-conda as well

* fix env var creation

* quote variable

* add second equal to compare

* try another way to use outputs

* fix outputs

* pip install for mac before creating conda env

* fix output variable

* fix python bin path

* remove pip install for before creating conda env

* unpin streamlit version in conda mac env

* try to make git-workflows better readable

* use macos-latest

* try to update conda before creating mac env

* better conda update trial

* re-pin streamlit version

* re-added trigger to run workflow in current branch

* try to find out if conda mac env could be updated

* install cmake, protobuf and rust b4 conda

* add yes to conda update

* lets try anaconda3-2022.05

* try environment.yml for mac as well

* reenable conda mac env, add pip install
also fix gitignore by changing from dream to invoke

* remove
- unecesary virtualenv creation
- conda update

change != macos back to == linux

* remove cmake from brew install since pre-installed

* disable opencv-python pip requirement

* fixed commands to find latest package versions

* update requirements for mac env

* back to the roots - only install conda env
depending on runner_os with or without extra env variables

* check out macOS in azure-pipelines
since becoming kind of tired of the GitHub Runner which is broken as ...

* let's try to setup python and update conda env

* initialize conda before using it

* add trigger in azure-pipelines.yml

* And another go for update first ....

* update azure-pipelines.yml
- add caching
- add checkpoint download
- add paths to trigger
and more

* unquote checkpoint-url

* fix chekpoint-url variable

* mkdir before downloading model

* set pr trigger to main, rename anaconda cache

* unique cacheHitVariables

* try to use macos-latest instead of macos-12

* update test-invoke-conda.yml:
- remove unecesarry echo step
- use s-weigand/setup-conda@v1
- remove conda update from install deps step since updated with action

* update test-invoke-conda.yml:
- rename conda env cache from ldm to invokeai
- reorder steps:
  1. checkout sources
  2. setup python
  3. setup conda
  4. keep order after set platform variables

* change macos back to 12 since also fails with 11

* update condition in run the tests
make difference between main or not main

* fix path to cache invokeai conda env

* fix invokeai conda env cache path

* update mkdocs-flow.yml

* change conda-channel priority

* update create-caches

* update conda env also when cache was used

* os dependend conda env cache path

* use existing CONDA env pointing to conda root

* create CONDA_ROOT output from $CONDA

* use output variable to define test prompts

* use setup-python v4, get rid of PYTHON_BIN env

* add runner.os to result artifacts name

* update test-invoke-conda.yml:
- reuse macos-latest
- disable setup python 3.9
- setup conda with default python version
- create or update conda environment depending on cache success
- remove name parameter from conda update since name is set in env yml

* improve mkdocs-flow.yml

* disable cache-hugginface-torch
since preload_models.py downloads to more than one location

* update mkdocs-flow.yml with new name

* rename mkdocs action to mkdocs-material

* try to ignore error when creating conda env
maybe it would still be usable, lets see ;P

* remove bloat

* update environment-mac.yml
to match dependencies of invoke-ai/InvokeAI's main branch

* disable conda update, tweak prompt condition

* try to set some env vars for macOS to fix conda

* stop ignoring error, use env instead of outputs

* tweak `[[` connditions

* update python and pip dependencie
makes a difference of 1 sec per itteration compared to 3.9!!!
also I see no reason why using a old pip version would be beneficial

* remove unecesarry env for macOS
everything was pre-tested on my MacBook Air 2020 with M1

* update conda env in setup step

* activate conda env after installation

* update test-invoke-conda.yml
- set conda env dependent on matrix.os
- set CONDA_ENV_NAME to prevent breaking action when renaming conda env
- fix conda env activation

* fix activate conda env

* set bash -l as default shell

* use action to activate conda env

* add conda env file to env activation

* try to replace s-weigeand with conda-incubator

* remove azure-pipelines.yml
funniest part is that the macos runner is the same as the one on github!

* include environment-file in matrix
- also disable auto-activate-base and auto-update-conda
- include macos-latest and macos-12 for debugging purpose
- set miniforge-version in matrix

* fix miniforge-variant, set fail-fast to false

* add step to setup miniconda
- make default shell a matrix variable
- remove bloat

* use a mac env yml without pinned versions

* unpin nomkl, pytorch and torchvision
also removed opencv-pyhton

* cache conda pkgs dir instead of conda env

* use python 3.10, exclude macos-12 from cache

* fix expression

* prepare for PR

* fix doubled id

* reuse pinned versions in mac conda env
- updated python pip version
- unpined pytorch and torchvision
- removed opencv-python
- updated versions to most recent (tested locally)

* fix classical copy/paste error

* remove unused env from shell-block comment

* fix hashFiles function to determine restore-keys

* reenable caching `~.cache`, update create-caches

* unpin all versions in mac conda env file
this was the only way I got it working in the action, also works locally
tested on MacBook Air 2020 M1
remove environment-mac-unpinned.yml

* prepare merge by removing this branch from trigger

* include pull_request trigger for main and dev

* remove pull_request trigger
2022-10-16 19:19:49 +02:00
Lincoln Stein
b0b82efffe restore inline images
<div> around the inline images works great in gh-pages, but breaks plain old markdown in GitHub code display. This removes the <div>s, causing slight degradation in quality of gh-page appearance.
2022-10-16 12:07:21 -04:00
Lincoln Stein
e599604294 restore inline images
<div> seems to be messing with the ability of the plain-old markdown processor to display inline images. Slightly degrades appearance of gh-pages.
2022-10-16 12:05:33 -04:00
Eric Wolf
57a3ea9d7b Update 'ldm' env to 'invokeai' in troubleshooting steps 2022-10-16 11:23:00 -04:00
Conor Reid
a3a50bb886 Update generate.py
Fixed spelling mistake (open source king)
2022-10-15 16:02:14 -04:00
149 changed files with 18653 additions and 4115 deletions

102
.github/ISSUE_TEMPLATE/BUG_REPORT.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: 🐞 Bug Report
description: File a bug report
title: '[bug]: '
labels: ['bug']
# assignees:
# - moderator_bot
# - lstein
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this Bug Report!
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: |
Please use the [search function](https://github.com/invoke-ai/InvokeAI/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
irst to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: markdown
attributes:
value: __Describe your environment__
- type: dropdown
id: os_dropdown
attributes:
label: OS
description: Which operating System did you use when the bug occured
multiple: false
options:
- 'Linux'
- 'Windows'
- 'macOS'
validations:
required: true
- type: dropdown
id: gpu_dropdown
attributes:
label: GPU
description: Which kind of Graphic-Adapter is your System using
multiple: false
options:
- 'cuda'
- 'amd'
- 'mps'
- 'cpu'
validations:
required: true
- type: input
id: vram
attributes:
label: VRAM
description: Size of the VRAM if known
placeholder: 8GB
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: |
Briefly describe what happened, what you expected to happen and how to reproduce this bug.
placeholder: When using the webinterface and right-clicking on button X instead of the popup-menu there error Y appears
validations:
required: true
- type: textarea
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem
placeholder: this is what the result looked like <screenshot>
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: Add any other context about the problem here
placeholder: Only happens when there is full moon and Friday the 13th on Christmas Eve 🎅🏻
validations:
required: false
- type: input
id: contact
attributes:
label: Contact Details
description: __OPTIONAL__ How can we get in touch with you if we need more info (besides this issue)?
placeholder: ex. email@example.com, discordname, twitter, ...
validations:
required: false

View File

@@ -0,0 +1,56 @@
name: Feature Request
description: Commit a idea or Request a new feature
title: '[enhancement]: '
labels: ['enhancement']
# assignees:
# - lstein
# - tildebyte
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this Feature request!
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: |
Please make use of the [search function](https://github.com/invoke-ai/InvokeAI/labels/enhancement)
to see if a simmilar issue already exists for the feature you want to request
options:
- label: I have searched the existing issues
required: true
- type: input
id: contact
attributes:
label: Contact Details
description: __OPTIONAL__ How could we get in touch with you if we need more info (besides this issue)?
placeholder: ex. email@example.com, discordname, twitter, ...
validations:
required: false
- type: textarea
id: whatisexpected
attributes:
label: What should this feature add?
description: Please try to explain the functionality this feature should add
placeholder: |
Instead of one huge textfield, it would be nice to have forms for bug-reports, feature-requests, ...
Great benefits with automatic labeling, assigning and other functionalitys not available in that form
via old-fashioned markdown-templates. I would also love to see the use of a moderator bot 🤖 like
https://github.com/marketplace/actions/issue-moderator-with-commands to auto close old issues and other things
validations:
required: true
- type: textarea
attributes:
label: Alternatives
description: Describe alternatives you've considered
placeholder: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
attributes:
label: Aditional Content
description: Add any other context or screenshots about the feature request here.
placeholder: This is a Mockup of the design how I imagine it <screenshot>

View File

@@ -1,36 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe your environment**
- GPU: [cuda/amd/mps/cpu]
- VRAM: [if known]
- CPU arch: [x86/arm]
- OS: [Linux/Windows/macOS]
- Python: [Anaconda/miniconda/miniforge/pyenv/other (explain)]
- Branch: [if `git status` says anything other than "On branch main" paste it here]
- Commit: [run `git show` and paste the line that starts with "Merge" here]
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

14
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Project-Documentation
url: https://invoke-ai.github.io/InvokeAI/
about: Should be your first place to go when looking for manuals/FAQs regarding our InvokeAI Toolkit
- name: Discord
url: https://discord.gg/ZmtBAhwWhy
about: Our Discord Community could maybe help you out via live-chat
- name: GitHub Community Support
url: https://github.com/orgs/community/discussions
about: Please ask and answer questions regarding the GitHub Platform here.
- name: GitHub Security Bug Bounty
url: https://bounty.github.com/
about: Please report security vulnerabilities of the GitHub Platform here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,28 +0,0 @@
name: Deploy
on:
push:
branches:
- main
# pull_request:
# branches:
# - main
jobs:
build:
name: Deploy docs to GitHub Pages
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Build
uses: Tiryoh/actions-mkdocs@v0
with:
mkdocs_version: 'latest' # option
requirements: '/requirements-mkdocs.txt' # option
configfile: '/mkdocs.yml' # option
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site

View File

@@ -4,6 +4,7 @@ on:
branches:
- 'main'
- 'development'
- 'fix-gh-actions-fork'
pull_request:
branches:
- 'main'

4
.gitignore vendored
View File

@@ -1,7 +1,7 @@
# ignore default image save location and model symbolic link
outputs/
models/ldm/stable-diffusion-v1/model.ckpt
ldm/invoke/restoration/codeformer/weights
**/restoration/codeformer/weights
# ignore user models config
configs/models.user.yaml
@@ -184,7 +184,7 @@ src
**/__pycache__/
outputs
# Logs and associated folders
# Logs and associated folders
# created from generated embeddings.
logs
testtube

13
LICENSE
View File

@@ -1,17 +1,6 @@
MIT License
Copyright (c) 2022 Lincoln Stein and InvokeAI Organization
This software is derived from a fork of the source code available from
https://github.com/pesser/stable-diffusion and
https://github.com/CompViz/stable-diffusion. They carry the following
copyrights:
Copyright (c) 2022 Machine Vision and Learning Group, LMU Munich
Copyright (c) 2022 Robin Rombach and Patrick Esser and contributors
Please see individual source code files for copyright and authorship
attributions.
Copyright (c) 2022 InvokeAI Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -68,11 +68,11 @@ requests. Be sure to use the provided templates. They will help aid diagnose iss
This fork is supported across multiple platforms. You can find individual installation instructions
below.
- #### [Linux](docs/installation/INSTALL_LINUX.md)
- #### [Linux](https://invoke-ai.github.io/InvokeAI/installation/INSTALL_LINUX/)
- #### [Windows](docs/installation/INSTALL_WINDOWS.md)
- #### [Windows](https://invoke-ai.github.io/InvokeAI/installation/INSTALL_WINDOWS/)
- #### [Macintosh](docs/installation/INSTALL_MAC.md)
- #### [Macintosh](https://invoke-ai.github.io/InvokeAI/installation/INSTALL_MAC/)
### Hardware Requirements
@@ -103,34 +103,33 @@ errors like 'expected type Float but found Half' or 'not implemented for Half'
you can try starting `invoke.py` with the `--precision=float32` flag:
```bash
(ldm) ~/stable-diffusion$ python scripts/invoke.py --precision=float32
(invokeai) ~/InvokeAI$ python scripts/invoke.py --precision=float32
```
### Features
#### Major Features
- [Web Server](docs/features/WEB.md)
- [Interactive Command Line Interface](docs/features/CLI.md)
- [Image To Image](docs/features/IMG2IMG.md)
- [Inpainting Support](docs/features/INPAINTING.md)
- [Outpainting Support](docs/features/OUTPAINTING.md)
- [Upscaling, face-restoration and outpainting](docs/features/POSTPROCESS.md)
- [Seamless Tiling](docs/features/OTHER.md#seamless-tiling)
- [Google Colab](docs/features/OTHER.md#google-colab)
- [Reading Prompts From File](docs/features/PROMPTS.md#reading-prompts-from-a-file)
- [Shortcut: Reusing Seeds](docs/features/OTHER.md#shortcuts-reusing-seeds)
- [Prompt Blending](docs/features/PROMPTS.md#prompt-blending)
- [Thresholding and Perlin Noise Initialization Options](/docs/features/OTHER.md#thresholding-and-perlin-noise-initialization-options)
- [Negative/Unconditioned Prompts](docs/features/PROMPTS.md#negative-and-unconditioned-prompts)
- [Variations](docs/features/VARIATIONS.md)
- [Personalizing Text-to-Image Generation](docs/features/TEXTUAL_INVERSION.md)
- [Simplified API for text to image generation](docs/features/OTHER.md#simplified-api)
- [Web Server](https://invoke-ai.github.io/InvokeAI/features/WEB/)
- [Interactive Command Line Interface](https://invoke-ai.github.io/InvokeAI/features/CLI/)
- [Image To Image](https://invoke-ai.github.io/InvokeAI/features/IMG2IMG/)
- [Inpainting Support](https://invoke-ai.github.io/InvokeAI/features/INPAINTING/)
- [Outpainting Support](https://invoke-ai.github.io/InvokeAI/features/OUTPAINTING/)
- [Upscaling, face-restoration and outpainting](https://invoke-ai.github.io/InvokeAI/features/POSTPROCESS/)
- [Reading Prompts From File](https://invoke-ai.github.io/InvokeAI/features/PROMPTS/#reading-prompts-from-a-file)
- [Prompt Blending](https://invoke-ai.github.io/InvokeAI/features/PROMPTS/#prompt-blending)
- [Thresholding and Perlin Noise Initialization Options](https://invoke-ai.github.io/InvokeAI/features/OTHER/#thresholding-and-perlin-noise-initialization-options)
- [Negative/Unconditioned Prompts](https://invoke-ai.github.io/InvokeAI/features/PROMPTS/#negative-and-unconditioned-prompts)
- [Variations](https://invoke-ai.github.io/InvokeAI/features/VARIATIONS/)
- [Personalizing Text-to-Image Generation](https://invoke-ai.github.io/InvokeAI/features/TEXTUAL_INVERSION/)
- [Simplified API for text to image generation](https://invoke-ai.github.io/InvokeAI/features/OTHER/#simplified-api)
#### Other Features
- [Creating Transparent Regions for Inpainting](docs/features/INPAINTING.md#creating-transparent-regions-for-inpainting)
- [Preload Models](docs/features/OTHER.md#preload-models)
- [Google Colab](https://invoke-ai.github.io/InvokeAI/features/OTHER/#google-colab)
- [Seamless Tiling](https://invoke-ai.github.io/InvokeAI/features/OTHER/#seamless-tiling)
- [Shortcut: Reusing Seeds](https://invoke-ai.github.io/InvokeAI/features/OTHER/#shortcuts-reusing-seeds)
- [Preload Models](https://invoke-ai.github.io/InvokeAI/features/OTHER/#preload-models)
### Latest Changes
@@ -144,33 +143,33 @@ you can try starting `invoke.py` with the `--precision=float32` flag:
- `dream.py` script renamed `invoke.py`. A `dream.py` script wrapper remains
for backward compatibility.
- Completely new WebGUI - launch with `python3 scripts/invoke.py --web`
- Support for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/INPAINTING.md">inpainting</a> and <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/OUTPAINTING.md">outpainting</a>
- Support for <a href="https://invoke-ai.github.io/InvokeAI/features/INPAINTING/">inpainting</a> and <a href="https://invoke-ai.github.io/InvokeAI/features/OUTPAINTING/">outpainting</a>
- img2img runs on all k* samplers
- Support for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/PROMPTS.md#negative-and-unconditioned-prompts">negative prompts</a>
- Support for <a href="https://invoke-ai.github.io/InvokeAI/features/PROMPTS/#negative-and-unconditioned-prompts">negative prompts</a>
- Support for CodeFormer face reconstruction
- Support for Textual Inversion on Macintoshes
- Support in both WebGUI and CLI for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/POSTPROCESS.md">post-processing of previously-generated images</a>
- Support in both WebGUI and CLI for <a href="https://invoke-ai.github.io/InvokeAI/features/POSTPROCESS/">post-processing of previously-generated images</a>
using facial reconstruction, ESRGAN upscaling, outcropping (similar to DALL-E infinite canvas),
and "embiggen" upscaling. See the `!fix` command.
- New `--hires` option on `invoke>` line allows <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CLI.md#this-is-an-example-of-txt2img">larger images to be created without duplicating elements</a>, at the cost of some performance.
- New `--hires` option on `invoke>` line allows <a href="https://invoke-ai.github.io/InvokeAI/features/CLI/#txt2img">larger images to be created without duplicating elements</a>, at the cost of some performance.
- New `--perlin` and `--threshold` options allow you to add and control variation
during image generation (see <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/OTHER.md#thresholding-and-perlin-noise-initialization-options">Thresholding and Perlin Noise Initialization</a>
- Extensive metadata now written into PNG files, allowing reliable regeneration of images
and tweaking of previous settings.
- Command-line completion in `invoke.py` now works on Windows, Linux and Mac platforms.
- Improved <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CLI.md">command-line completion behavior</a>.
- Improved <a href="https://invoke-ai.github.io/InvokeAI/features/CLI/">command-line completion behavior</a>.
New commands added:
* List command-line history with `!history`
* Search command-line history with `!search`
* Clear history with `!clear`
- List command-line history with `!history`
- Search command-line history with `!search`
- Clear history with `!clear`
- Deprecated `--full_precision` / `-F`. Simply omit it and `invoke.py` will auto
configure. To switch away from auto use the new flag like `--precision=float32`.
For older changelogs, please visit the **[CHANGELOG](docs/features/CHANGELOG.md)**.
For older changelogs, please visit the **[CHANGELOG](https://invoke-ai.github.io/InvokeAI/CHANGELOG#v114-11-september-2022)**.
### Troubleshooting
Please check out our **[Q&A](docs/help/TROUBLESHOOT.md)** to get solutions for common installation
Please check out our **[Q&A](https://invoke-ai.github.io/InvokeAI/help/TROUBLESHOOT/#faq)** to get solutions for common installation
problems and other issues.
# Contributing
@@ -188,7 +187,7 @@ changes.
### Contributors
This fork is a combined effort of various people from across the world.
[Check out the list of all these amazing people](docs/other/CONTRIBUTORS.md). We thank them for
[Check out the list of all these amazing people](https://invoke-ai.github.io/InvokeAI/other/CONTRIBUTORS/). We thank them for
their time, hard work and effort.
### Support
@@ -202,4 +201,4 @@ Original portions of the software are Copyright (c) 2020
### Further Reading
Please see the original README for more information on this software and underlying algorithm,
located in the file [README-CompViz.md](docs/other/README-CompViz.md).
located in the file [README-CompViz.md](https://invoke-ai.github.io/InvokeAI/other/README-CompViz/).

View File

@@ -187,7 +187,10 @@ class InvokeAIWebServer:
base_path = (
self.result_path if category == "result" else self.init_image_path
)
paths = glob.glob(os.path.join(base_path, "*.png"))
paths = []
for ext in ("*.png", "*.jpg", "*.jpeg"):
paths.extend(glob.glob(os.path.join(base_path, ext)))
image_paths = sorted(
paths, key=lambda x: os.path.getmtime(x), reverse=True
@@ -203,13 +206,19 @@ class InvokeAIWebServer:
image_array = []
for path in image_paths:
metadata = retrieve_metadata(path)
if os.path.splitext(path)[1] == ".png":
metadata = retrieve_metadata(path)
sd_metadata = metadata["sd-metadata"]
else:
sd_metadata = {}
(width, height) = Image.open(path).size
image_array.append(
{
"url": self.get_url_from_image_path(path),
"mtime": os.path.getmtime(path),
"metadata": metadata["sd-metadata"],
"metadata": sd_metadata,
"width": width,
"height": height,
"category": category,
@@ -236,7 +245,9 @@ class InvokeAIWebServer:
self.result_path if category == "result" else self.init_image_path
)
paths = glob.glob(os.path.join(base_path, "*.png"))
paths = []
for ext in ("*.png", "*.jpg", "*.jpeg"):
paths.extend(glob.glob(os.path.join(base_path, ext)))
image_paths = sorted(
paths, key=lambda x: os.path.getmtime(x), reverse=True
@@ -254,9 +265,12 @@ class InvokeAIWebServer:
image_paths = image_paths[slice(0, page_size)]
image_array = []
for path in image_paths:
metadata = retrieve_metadata(path)
if os.path.splitext(path)[1] == ".png":
metadata = retrieve_metadata(path)
sd_metadata = metadata["sd-metadata"]
else:
sd_metadata = {}
(width, height) = Image.open(path).size
@@ -264,7 +278,7 @@ class InvokeAIWebServer:
{
"url": self.get_url_from_image_path(path),
"mtime": os.path.getmtime(path),
"metadata": metadata["sd-metadata"],
"metadata": sd_metadata,
"width": width,
"height": height,
"category": category,
@@ -573,11 +587,7 @@ class InvokeAIWebServer:
)
)
)
# crop the mask image
cropped_mask_image = copy_image_from_bounding_box(
mask_image, **generation_parameters["bounding_box"]
)
generation_parameters["init_mask"] = cropped_mask_image
generation_parameters["init_mask"] = mask_image
totalSteps = self.calculate_real_steps(
steps=generation_parameters["steps"],
@@ -606,7 +616,7 @@ class InvokeAIWebServer:
if (
generation_parameters["progress_images"]
and step % 5 == 0
and step % generation_parameters['save_intermediates'] == 0
and step < generation_parameters["steps"] - 1
):
image = self.generate.sample_to_image(sample)
@@ -637,14 +647,17 @@ class InvokeAIWebServer:
"height": height,
},
)
if generation_parameters['progress_latents']:
if generation_parameters["progress_latents"]:
image = self.generate.sample_to_lowres_estimated_image(sample)
(width, height) = image.size
width *= 8
height *= 8
buffered = io.BytesIO()
image.save(buffered, format="PNG")
img_base64 = "data:image/png;base64," + base64.b64encode(buffered.getvalue()).decode('UTF-8')
img_base64 = "data:image/png;base64," + base64.b64encode(
buffered.getvalue()
).decode("UTF-8")
self.socketio.emit(
"intermediateResult",
{
@@ -654,8 +667,9 @@ class InvokeAIWebServer:
"metadata": {},
"width": width,
"height": height,
}
},
)
self.socketio.emit("progressUpdate", progress.to_formatted_dict())
eventlet.sleep(0)
@@ -671,6 +685,14 @@ class InvokeAIWebServer:
step_index = 1
nonlocal prior_variations
# paste the inpainting image back onto the original
if "init_mask" in generation_parameters:
image = paste_image_into_bounding_box(
Image.open(init_img_path),
image,
**generation_parameters["bounding_box"],
)
progress.set_current_status("Generation Complete")
self.socketio.emit("progressUpdate", progress.to_formatted_dict())
@@ -759,14 +781,6 @@ class InvokeAIWebServer:
self.socketio.emit("progressUpdate", progress.to_formatted_dict())
eventlet.sleep(0)
# paste the inpainting image back onto the original
if "init_mask" in generation_parameters:
image = paste_image_into_bounding_box(
Image.open(init_img_path),
image,
**generation_parameters["bounding_box"],
)
# restore the stashed URLS and discard the paths, we are about to send the result to client
if "init_img" in all_parameters:
all_parameters["init_img"] = init_img_url

View File

@@ -6,64 +6,64 @@ title: Changelog
## v2.0.1 (13 October 2022)
- fix noisy images at high step count when using k* samplers
- dream.py script now calls invoke.py module directly rather than
- fix noisy images at high step count when using k* samplers
- dream.py script now calls invoke.py module directly rather than
via a new python process (which could break the environment)
## v2.0.0 <small>(9 October 2022)</small>
- `dream.py` script renamed `invoke.py`. A `dream.py` script wrapper remains
- `dream.py` script renamed `invoke.py`. A `dream.py` script wrapper remains
for backward compatibility.
- Completely new WebGUI - launch with `python3 scripts/invoke.py --web`
- Support for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/INPAINTING.md">inpainting</a> and <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/OUTPAINTING.md">outpainting</a>
- img2img runs on all k* samplers
- Support for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/PROMPTS.md#negative-and-unconditioned-prompts">negative prompts</a>
- Support for CodeFormer face reconstruction
- Support for Textual Inversion on Macintoshes
- Support in both WebGUI and CLI for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/POSTPROCESS.md">post-processing of previously-generated images</a>
- Completely new WebGUI - launch with `python3 scripts/invoke.py --web`
- Support for [inpainting](features/INPAINTING.md) and [outpainting](features/OUTPAINTING.md)
- img2img runs on all k* samplers
- Support for [negative prompts](features/PROMPTS.md#negative-and-unconditioned-prompts)
- Support for CodeFormer face reconstruction
- Support for Textual Inversion on Macintoshes
- Support in both WebGUI and CLI for [post-processing of previously-generated images](features/POSTPROCESS.md)
using facial reconstruction, ESRGAN upscaling, outcropping (similar to DALL-E infinite canvas),
and "embiggen" upscaling. See the `!fix` command.
- New `--hires` option on `invoke>` line allows <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CLI.m#this-is-an-example-of-txt2img">larger images to be created without duplicating elements</a>, at the cost of some performance.
- New `--perlin` and `--threshold` options allow you to add and control variation
during image generation (see <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/OTHER.md#thresholding-and-perlin-noise-initialization-options">Thresholding and Perlin Noise Initialization</a>
- Extensive metadata now written into PNG files, allowing reliable regeneration of images
- New `--hires` option on `invoke>` line allows [larger images to be created without duplicating elements](features/CLI.md#this-is-an-example-of-txt2img), at the cost of some performance.
- New `--perlin` and `--threshold` options allow you to add and control variation
during image generation (see [Thresholding and Perlin Noise Initialization](features/OTHER.md#thresholding-and-perlin-noise-initialization-options))
- Extensive metadata now written into PNG files, allowing reliable regeneration of images
and tweaking of previous settings.
- Command-line completion in `invoke.py` now works on Windows, Linux and Mac platforms.
- Improved <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CLI.m">command-line completion behavior</a>.
- Command-line completion in `invoke.py` now works on Windows, Linux and Mac platforms.
- Improved [command-line completion behavior](features/CLI.md)
New commands added:
* List command-line history with `!history`
* Search command-line history with `!search`
* Clear history with `!clear`
- Deprecated `--full_precision` / `-F`. Simply omit it and `invoke.py` will auto
- List command-line history with `!history`
- Search command-line history with `!search`
- Clear history with `!clear`
- Deprecated `--full_precision` / `-F`. Simply omit it and `invoke.py` will auto
configure. To switch away from auto use the new flag like `--precision=float32`.
## v1.14 <small>(11 September 2022)</small>
- Memory optimizations for small-RAM cards. 512x512 now possible on 4 GB GPUs.
- Full support for Apple hardware with M1 or M2 chips.
- Add "seamless mode" for circular tiling of image. Generates beautiful effects.
- Memory optimizations for small-RAM cards. 512x512 now possible on 4 GB GPUs.
- Full support for Apple hardware with M1 or M2 chips.
- Add "seamless mode" for circular tiling of image. Generates beautiful effects.
([prixt](https://github.com/prixt)).
- Inpainting support.
- Improved web server GUI.
- Lots of code and documentation cleanups.
- Inpainting support.
- Improved web server GUI.
- Lots of code and documentation cleanups.
## v1.13 <small>(3 September 2022)</small>
- Support image variations (see [VARIATIONS](features/VARIATIONS.md)
- Support image variations (see [VARIATIONS](features/VARIATIONS.md)
([Kevin Gibbons](https://github.com/bakkot) and many contributors and reviewers)
- Supports a Google Colab notebook for a standalone server running on Google hardware
- Supports a Google Colab notebook for a standalone server running on Google hardware
[Arturo Mendivil](https://github.com/artmen1516)
- WebUI supports GFPGAN/ESRGAN facial reconstruction and upscaling
- WebUI supports GFPGAN/ESRGAN facial reconstruction and upscaling
[Kevin Gibbons](https://github.com/bakkot)
- WebUI supports incremental display of in-progress images during generation
- WebUI supports incremental display of in-progress images during generation
[Kevin Gibbons](https://github.com/bakkot)
- A new configuration file scheme that allows new models (including upcoming
- A new configuration file scheme that allows new models (including upcoming
stable-diffusion-v1.5) to be added without altering the code.
([David Wager](https://github.com/maddavid12))
- Can specify --grid on invoke.py command line as the default.
- Miscellaneous internal bug and stability fixes.
- Works on M1 Apple hardware.
- Multiple bug fixes.
- Can specify --grid on invoke.py command line as the default.
- Miscellaneous internal bug and stability fixes.
- Works on M1 Apple hardware.
- Multiple bug fixes.
---
@@ -88,7 +88,7 @@ title: Changelog
Seed memory only extends back to the previous command, but will work on all images generated with the -n# switch.
- Variant generation support temporarily disabled pending more general solution.
- Created a feature branch named **yunsaki-morphing-invoke** which adds experimental support for
iteratively modifying the prompt and its parameters. Please see[ Pull Request #86](https://github.com/lstein/stable-diffusion/pull/86)
iteratively modifying the prompt and its parameters. Please see[Pull Request #86](https://github.com/lstein/stable-diffusion/pull/86)
for a synopsis of how this works. Note that when this feature is eventually added to the main branch, it will may be modified
significantly.

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 KiB

View File

@@ -1,143 +0,0 @@
---
title: Changelog
---
# :octicons-log-16: Changelog
## v1.13
- Supports a Google Colab notebook for a standalone server running on Google
hardware [Arturo Mendivil](https://github.com/artmen1516)
- WebUI supports GFPGAN/ESRGAN facial reconstruction and upscaling
[Kevin Gibbons](https://github.com/bakkot)
- WebUI supports incremental display of in-progress images during generation
[Kevin Gibbons](https://github.com/bakkot)
- Output directory can be specified on the invoke> command line.
- The grid was displaying duplicated images when not enough images to fill the
final row [Muhammad Usama](https://github.com/SMUsamaShah)
- Can specify --grid on invoke.py command line as the default.
- Miscellaneous internal bug and stability fixes.
---
## v1.12 <small>(28 August 2022)</small>
- Improved file handling, including ability to read prompts from standard input.
(kudos to [Yunsaki](https://github.com/yunsaki)
- The web server is now integrated with the invoke.py script. Invoke by adding
--web to the invoke.py command arguments.
- Face restoration and upscaling via GFPGAN and Real-ESGAN are now automatically
enabled if the GFPGAN directory is located as a sibling to Stable Diffusion.
VRAM requirements are modestly reduced. Thanks to both
[Blessedcoolant](https://github.com/blessedcoolant) and
[Oceanswave](https://github.com/oceanswave) for their work on this.
- You can now swap samplers on the invoke> command line.
[Blessedcoolant](https://github.com/blessedcoolant)
---
## v1.11 <small>(26 August 2022)</small>
- NEW FEATURE: Support upscaling and face enhancement using the GFPGAN module.
(kudos to [Oceanswave](https://github.com/Oceanswave))
- You now can specify a seed of -1 to use the previous image's seed, -2 to use
the seed for the image generated before that, etc. Seed memory only extends
back to the previous command, but will work on all images generated with the
-n# switch.
- Variant generation support temporarily disabled pending more general solution.
- Created a feature branch named **yunsaki-morphing-invoke** which adds
experimental support for iteratively modifying the prompt and its parameters.
Please
see[ Pull Request #86](https://github.com/lstein/stable-diffusion/pull/86) for
a synopsis of how this works. Note that when this feature is eventually added
to the main branch, it will may be modified significantly.
---
## v1.10 <small>(25 August 2022)</small>
- A barebones but fully functional interactive web server for online generation
of txt2img and img2img.
---
## v1.09 <small>(24 August 2022)</small>
- A new -v option allows you to generate multiple variants of an initial image
in img2img mode. (kudos to [Oceanswave](https://github.com/Oceanswave).
- [See this discussion in the PR for examples and details on use](https://github.com/lstein/stable-diffusion/pull/71#issuecomment-1226700810))
- Added ability to personalize text to image generation (kudos to
[Oceanswave](https://github.com/Oceanswave) and
[nicolai256](https://github.com/nicolai256))
- Enabled all of the samplers from k_diffusion
---
## v1.08 <small>(24 August 2022)</small>
- Escape single quotes on the invoke> command before trying to parse. This avoids
parse errors.
- Removed instruction to get Python3.8 as first step in Windows install.
Anaconda3 does it for you.
- Added bounds checks for numeric arguments that could cause crashes.
- Cleaned up the copyright and license agreement files.
---
## v1.07 <small>(23 August 2022)</small>
- Image filenames will now never fill gaps in the sequence, but will be assigned
the next higher name in the chosen directory. This ensures that the alphabetic
and chronological sort orders are the same.
---
## v1.06 <small>(23 August 2022)</small>
- Added weighted prompt support contributed by
[xraxra](https://github.com/xraxra)
- Example of using weighted prompts to tweak a demonic figure contributed by
[bmaltais](https://github.com/bmaltais)
---
## v1.05 <small>(22 August 2022 - after the drop)</small>
- Filenames now use the following formats: 000010.95183149.png -- Two files
produced by the same command (e.g. -n2), 000010.26742632.png -- distinguished
by a different seed.
000011.455191342.01.png -- Two files produced by the same command using
000011.455191342.02.png -- a batch size>1 (e.g. -b2). They have the same seed.
000011.4160627868.grid#1-4.png -- a grid of four images (-g); the whole grid
can be regenerated with the indicated key
- It should no longer be possible for one image to overwrite another
- You can use the "cd" and "pwd" commands at the invoke> prompt to set and
retrieve the path of the output directory.
## v1.04 <small>(22 August 2022 - after the drop)</small>
- Updated README to reflect installation of the released weights.
- Suppressed very noisy and inconsequential warning when loading the frozen CLIP
tokenizer.
## v1.03 <small>(22 August 2022)</small>
- The original txt2img and img2img scripts from the CompViz repository have been
moved into a subfolder named "orig_scripts", to reduce confusion.
## v1.02 <small>(21 August 2022)</small>
- A copy of the prompt and all of its switches and options is now stored in the
corresponding image in a tEXt metadata field named "Dream". You can read the
prompt using scripts/images2prompt.py, or an image editor that allows you to
explore the full metadata. **Please run "conda env update -f environment.yaml"
to load the k_lms dependencies!!**
## v1.01 <small>(21 August 2022)</small>
- added k_lms sampling. **Please run "conda env update -f environment.yaml" to
load the k_lms dependencies!!**
- use half precision arithmetic by default, resulting in faster execution and
lower memory requirements Pass argument --full_precision to invoke.py to get
slower but more accurate image generation

View File

@@ -101,9 +101,7 @@ overridden on a per-prompt basis (see [List of prompt arguments](#list-of-prompt
| `--free_gpu_mem` | | `False` | Free GPU memory after sampling, to allow image decoding and saving in low VRAM conditions |
| `--precision` | | `auto` | Set model precision, default is selected by device. Options: auto, float32, float16, autocast |
!!! warning deprecated
These arguments are deprecated but still work:
!!! warning "These arguments are deprecated but still work"
<div align="center" markdown>
@@ -132,7 +130,7 @@ from text ([txt2img](#txt2img)), to embellish an existing image or sketch
### txt2img
!!! example
!!! example ""
```bash
invoke> waterfall and rainbow -W640 -H480
@@ -200,7 +198,7 @@ accepts additional options:
### inpainting
!!! example
!!! example ""
```bash
invoke> waterfall and rainbow -I./vacation-photo.png -M./vacation-mask.png -W640 -H480 --fit

View File

@@ -17,15 +17,15 @@ tree on a hill with a river, nature photograph, national geographic -I./test-pic
This will take the original image shown here:
<div align="center" markdown>
<figure markdown>
<img src="https://user-images.githubusercontent.com/50542132/193946000-c42a96d8-5a74-4f8a-b4c3-5213e6cadcce.png" width=350>
</div>
</figure>
and generate a new image based on it as shown here:
<div align="center" markdown>
<figure markdown>
<img src="https://user-images.githubusercontent.com/111189/194135515-53d4c060-e994-4016-8121-7c685e281ac9.png" width=350>
</div>
</figure>
The `--init_img` (`-I`) option gives the path to the seed picture. `--strength` (`-f`) controls how much
the original will be modified, ranging from `0.0` (keep the original intact), to `1.0` (ignore the
@@ -41,11 +41,10 @@ interesting variants.
Note that the prompt makes a big difference. For example, this slight variation on the prompt produces
a very different image:
`photograph of a tree on a hill with a river`
<div align="center" markdown>
<figure markdown>
<img src="https://user-images.githubusercontent.com/111189/194135220-16b62181-b60c-4248-8989-4834a8fd7fbd.png" width=350>
</div>
<caption markdown>photograph of a tree on a hill with a river</caption>
</figure>
!!! tip
@@ -79,9 +78,9 @@ gaussian noise and progressively refines it over the requested number of steps,
invoke> "fire" -s10 -W384 -H384 -S1592514025
```
<div align="center" markdown>
<figure markdown>
![latent steps](../assets/img2img/000019.steps.png)
</div>
</figure>
Put simply: starting from a frame of fuzz/static, SD finds details in each frame that it thinks look like "fire" and brings them a little bit more into focus, gradually scrubbing out the fuzz until a clear image remains.
@@ -91,21 +90,21 @@ Put simply: starting from a frame of fuzz/static, SD finds details in each frame
I want SD to draw a fire based on this hand-drawn image:
<div align="center" markdown>
<figure markdown>
![drawing of a fireplace](../assets/img2img/fire-drawing.png)
</div>
</figure>
Let's only do 10 steps, to make it easier to see what's happening. If strength is `0.7`, this is what the internal steps the algorithm has to take will look like:
<div align="center" markdown>
<figure markdown>
![gravity32](../assets/img2img/000032.steps.gravity.png)
</div>
</figure>
With strength `0.4`, the steps look more like this:
<div align="center" markdown>
<figure markdown>
![gravity30](../assets/img2img/000030.steps.gravity.png)
</div>
</figure>
Notice how much more fuzzy the starting image is for strength `0.7` compared to `0.4`, and notice also how much longer the sequence is with `0.7`:
@@ -137,9 +136,9 @@ Here's strength `0.4` (note step count `50`, which is `20 ÷ 0.4` to make sure S
invoke> "fire" -s50 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png -f 0.4
```
<div align="center" markdown>
<figure markdown>
![000035.1592514025](../assets/img2img/000035.1592514025.png)
</div>
</figure>
and here is strength `0.7` (note step count `30`, which is roughly `20 ÷ 0.7` to make sure SD does `20` steps from my image):
@@ -147,29 +146,38 @@ and here is strength `0.7` (note step count `30`, which is roughly `20 ÷ 0.7` t
invoke> "fire" -s30 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png -f 0.7
```
<div align="center" markdown>
<figure markdown>
![000046.1592514025](../assets/img2img/000046.1592514025.png)
</div>
</figure>
In both cases the image is nice and clean and "finished", but because at strength `0.7` Stable Diffusion has been give so much more freedom to improve on my badly-drawn flames, they've come out looking much better. You can really see the difference when looking at the latent steps. There's more noise on the first image with strength `0.7`:
<figure markdown>
![gravity46](../assets/img2img/000046.steps.gravity.png)
</figure>
than there is for strength `0.4`:
<figure markdown>
![gravity35](../assets/img2img/000035.steps.gravity.png)
</figure>
and that extra noise gives the algorithm more choices when it is evaluating how to denoise any particular pixel in the image.
Unfortunately, it seems that `img2img` is very sensitive to the step count. Here's strength `0.7` with a step count of `29` (SD did 19 steps from my image):
<div align="center" markdown>
<figure markdown>
![gravity45](../assets/img2img/000045.1592514025.png)
</div>
</figure>
By comparing the latents we can sort of see that something got interpreted differently enough on the third or fourth step to lead to a rather different interpretation of the flames.
<figure markdown>
![gravity46](../assets/img2img/000046.steps.gravity.png)
</figure>
<figure markdown>
![gravity45](../assets/img2img/000045.steps.gravity.png)
</figure>
This is the result of a difference in the de-noising "schedule" - basically the noise has to be cleaned by a certain degree each step or the model won't "converge" on the image properly (see [stable diffusion blog](https://huggingface.co/blog/stable_diffusion) for more about that). A different step count means a different schedule, which means things get interpreted slightly differently at every step.

View File

@@ -257,28 +257,40 @@ surrounding unmasked regions as well.
1. Open image in Photoshop
<div align="center" markdown>![step1](../assets/step1.png)</div>
<figure markdown>
![step1](../assets/step1.png)
</figure>
2. Use any of the selection tools (Marquee, Lasso, or Wand) to select the area you desire to inpaint.
<div align="center" markdown>![step2](../assets/step2.png)</div>
<figure markdown>
![step2](../assets/step2.png)
</figure>
3. Because we'll be applying a mask over the area we want to preserve, you should now select the inverse by using the ++shift+ctrl+i++ shortcut, or right clicking and using the "Select Inverse" option.
4. You'll now create a mask by selecting the image layer, and Masking the selection. Make sure that you don't delete any of the underlying image, or your inpainting results will be dramatically impacted.
<div align="center" markdown>![step4](../assets/step4.png)</div>
<figure markdown>
![step4](../assets/step4.png)
</figure>
5. Make sure to hide any background layers that are present. You should see the mask applied to your image layer, and the image on your canvas should display the checkered background.
<div align="center" markdown>![step5](../assets/step5.png)</div>
<figure markdown>
![step5](../assets/step5.png)
</figure>
6. Save the image as a transparent PNG by using `File`-->`Save a Copy` from the menu bar, or by using the keyboard shortcut ++alt+ctrl+s++
<div align="center" markdown>![step6](../assets/step6.png)</div>
<figure markdown>
![step6](../assets/step6.png)
</figure>
7. After following the inpainting instructions above (either through the CLI or the Web UI), marvel at your newfound ability to selectively invoke. Lookin' good!
<div align="center" markdown>![step7](../assets/step7.png)</div>
<figure markdown>
![step7](../assets/step7.png)
</figure>
8. In the export dialogue, Make sure the "Save colour values from transparent pixels" checkbox is selected.

View File

@@ -64,31 +64,32 @@ model](INPAINTING.md#using-the-runwayml-inpainting-model).
Consider this image:
<div align="center" markdown>
<figure markdown>
![curly_woman](../assets/outpainting/curly.png)
</div>
</figure>
Pretty nice, but it's annoying that the top of her head is cut
off. She's also a bit off center. Let's fix that!
```bash
invoke> !fix images/curly.png --outcrop top 64 right 64
invoke> !fix images/curly.png --outcrop top 128 right 64 bottom 64
```
This is saying to apply the `outcrop` extension by extending the top
of the image by 64 pixels, and the right of the image by the same
amount. You can use any combination of top|left|right|bottom, and
of the image by 128 pixels, and the right and bottom of the image by
64 pixels. You can use any combination of top|left|right|bottom, and
specify any number of pixels to extend. You can also abbreviate
`--outcrop` to `-c`.
The result looks like this:
<div align="center" markdown>
![curly_woman_outcrop](../assets/outpainting/curly-outcrop.png)
</div>
<figure markdown>
![curly_woman_outcrop](../assets/outpainting/curly-outcrop-2.png)
</figure>
The new image is actually slightly larger than the original (576x576,
because 64 pixels were added to the top and right sides.)
The new image is larger than the original (576x704)
because 64 pixels were added to the top and right sides. You will
need enough VRAM to process an image of this size.
A number of caveats:
@@ -103,3 +104,53 @@ you'll get a slightly different result. You can run it repeatedly
until you get an image you like. Unfortunately `!fix` does not
currently respect the `-n` (`--iterations`) argument.
3. Your results will be _much_ better if you use the `inpaint-1.5`
model released by runwayML and installed by default by
`scripts/preload_models.py`. This model was trained specifically to
harmoniously fill in image gaps. The standard model will work as well,
but you may notice color discontinuities at the border.
4. When using the `inpaint-1.5` model, you may notice subtle changes
to the area within the original image. This is because the model
performs an encoding/decoding on the image as a whole. This does not
occur with the standard model.
## Outpaint
The `outpaint` extension does the same thing, but with subtle
differences. Starting with the same image, here is how we would add an
additional 64 pixels to the top of the image:
```bash
invoke> !fix images/curly.png --out_direction top 64
```
(you can abbreviate `--out_direction` as `-D`.
The result is shown here:
<figure markdown>
![curly_woman_outpaint](../assets/outpainting/curly-outpaint.png)
</figure>
Although the effect is similar, there are significant differences from
outcropping:
- You can only specify one direction to extend at a time.
- The image is **not** resized. Instead, the image is shifted by the specified
number of pixels. If you look carefully, you'll see that less of the lady's
torso is visible in the image.
- Because the image dimensions remain the same, there's no rounding
to multiples of 64.
- Attempting to outpaint larger areas will frequently give rise to ugly
ghosting effects.
- For best results, try increasing the step number.
- If you don't specify a pixel value in `-D`, it will default to half
of the whole image, which is likely not what you want.
!!! tip
Neither `outpaint` nor `outcrop` are perfect, but we continue to tune
and improve them. If one doesn't work, try the other. You may also
wish to experiment with other `img2img` arguments, such as `-C`, `-f`
and `-s`.

View File

@@ -47,33 +47,33 @@ original prompt:
`#!bash "A fantastical translucent pony made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180`
<div align="center" markdown>
<figure markdown>
![step1](../assets/negative_prompt_walkthru/step1.png)
</div>
</figure>
That image has a woman, so if we want the horse without a rider, we can influence the image not to have a woman by putting [woman] in the prompt, like this:
`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180`
<div align="center" markdown>
<figure markdown>
![step2](../assets/negative_prompt_walkthru/step2.png)
</div>
</figure>
That's nice - but say we also don't want the image to be quite so blue. We can add "blue" to the list of negative prompts, so it's now [woman blue]:
`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180`
<div align="center" markdown>
<figure markdown>
![step3](../assets/negative_prompt_walkthru/step3.png)
</div>
</figure>
Getting close - but there's no sense in having a saddle when our horse doesn't have a rider, so we'll add one more negative prompt: [woman blue saddle].
`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue saddle]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180`
<div align="center" markdown>
<figure markdown>
![step4](../assets/negative_prompt_walkthru/step4.png)
</div>
</figure>
!!! notes "Notes about this feature:"
@@ -215,56 +215,56 @@ different results each time you run them.
---
<div align="center" markdown>
<figure markdown>
### "blue sphere, red cube, hybrid"
</div>
</figure>
This example doesn't use melding at all and represents the default way
of mixing concepts.
<div align="center" markdown>
<figure markdown>
![blue-sphere-red-cube-hyprid](../assets/prompt-blending/blue-sphere-red-cube-hybrid.png)
</div>
</figure>
It's interesting to see how the AI expressed the concept of "cube" as
the four quadrants of the enclosing frame. If you look closely, there
is depth there, so the enclosing frame is actually a cube.
<div align="center" markdown>
<figure markdown>
### "blue sphere:0.25 red cube:0.75 hybrid"
![blue-sphere-25-red-cube-75](../assets/prompt-blending/blue-sphere-0.25-red-cube-0.75-hybrid.png)
</div>
</figure>
Now that's interesting. We get neither a blue sphere nor a red cube,
but a red sphere embedded in a brick wall, which represents a melding
of concepts within the AI's "latent space" of semantic
representations. Where is Ludwig Wittgenstein when you need him?
<div align="center" markdown>
<figure markdown>
### "blue sphere:0.75 red cube:0.25 hybrid"
![blue-sphere-75-red-cube-25](../assets/prompt-blending/blue-sphere-0.75-red-cube-0.25-hybrid.png)
</div>
</figure>
Definitely more blue-spherey. The cube is gone entirely, but it's
really cool abstract art.
<div align="center" markdown>
<figure markdown>
### "blue sphere:0.5 red cube:0.5 hybrid"
![blue-sphere-5-red-cube-5-hybrid](../assets/prompt-blending/blue-sphere-0.5-red-cube-0.5-hybrid.png)
</div>
</figure>
Whoa...! I see blue and red, but no spheres or cubes. Is the word
"hybrid" summoning up the concept of some sort of scifi creature?
Let's find out.
<div align="center" markdown>
<figure markdown>
### "blue sphere:0.5 red cube:0.5"
![blue-sphere-5-red-cube-5](../assets/prompt-blending/blue-sphere-0.5-red-cube-0.5.png)
</div>
</figure>
Indeed, removing the word "hybrid" produces an image that is more like
what we'd expect.

View File

@@ -86,74 +86,57 @@ You wil need one of the following:
- At least 12 GB of free disk space for the machine learning model, Python, and all its dependencies.
!!! note
!!! info
If you are have a Nvidia 10xx series card (e.g. the 1080ti), please run the invoke script in
full-precision mode as shown below.
Similarly, specify full-precision mode on Apple M1 hardware.
To run in full-precision mode, start `invoke.py` with the `--full_precision` flag:
Precision is auto configured based on the device. If however you encounter errors like
`expected type Float but found Half` or `not implemented for Half` you can try starting
`invoke.py` with the `--precision=float32` flag:
```bash
(invokeai) ~/InvokeAI$ python scripts/invoke.py --full_precision
```
## :octicons-log-16: Latest Changes
### v2.0.1 <small>(13 October 2022)</small>
- fix noisy images at high step count when using k* samplers
- dream.py script now calls invoke.py module directly rather than
via a new python process (which could break the environment)
### v2.0.0 <small>(9 October 2022)</small>
- `dream.py` script renamed `invoke.py`. A `dream.py` script wrapper remains
for backward compatibility.
for backward compatibility.
- Completely new WebGUI - launch with `python3 scripts/invoke.py --web`
- Support for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/INPAINTING.md">inpainting</a> and <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/OUTPAINTING.md">outpainting</a>
- Support for <a href="https://invoke-ai.github.io/InvokeAI/features/INPAINTING/">inpainting</a> and <a href="https://invoke-ai.github.io/InvokeAI/features/OUTPAINTING/">outpainting</a>
- img2img runs on all k* samplers
- Support for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/PROMPTS.md#negative-and-unconditioned-prompts">negative prompts</a>
- Support for <a href="https://invoke-ai.github.io/InvokeAI/features/PROMPTS/#negative-and-unconditioned-prompts">negative prompts</a>
- Support for CodeFormer face reconstruction
- Support for Textual Inversion on Macintoshes
- Support in both WebGUI and CLI for <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/POSTPROCESS.md">post-processing of previously-generated images</a>
using facial reconstruction, ESRGAN upscaling, outcropping (similar to DALL-E infinite canvas),
and "embiggen" upscaling. See the `!fix` command.
- New `--hires` option on `invoke>` line allows <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CLI.m#this-is-an-example-of-txt2img">larger images to be created without duplicating elements</a>, at the cost of some performance.
- Support in both WebGUI and CLI for <a href="https://invoke-ai.github.io/InvokeAI/features/POSTPROCESS/">post-processing of previously-generated images</a>
using facial reconstruction, ESRGAN upscaling, outcropping (similar to DALL-E infinite canvas),
and "embiggen" upscaling. See the `!fix` command.
- New `--hires` option on `invoke>` line allows <a href="https://invoke-ai.github.io/InvokeAI/features/CLI/#txt2img">larger images to be created without duplicating elements</a>, at the cost of some performance.
- New `--perlin` and `--threshold` options allow you to add and control variation
during image generation (see <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/OTHER.md#thresholding-and-perlin-noise-initialization-options">Thresholding and Perlin Noise Initialization</a>
during image generation (see <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/OTHER.md#thresholding-and-perlin-noise-initialization-options">Thresholding and Perlin Noise Initialization</a>
- Extensive metadata now written into PNG files, allowing reliable regeneration of images
and tweaking of previous settings.
and tweaking of previous settings.
- Command-line completion in `invoke.py` now works on Windows, Linux and Mac platforms.
- Improved <a href="https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CLI.m">command-line completion behavior</a>.
New commands added:
* List command-line history with `!history`
* Search command-line history with `!search`
* Clear history with `!clear`
- Improved <a href="https://invoke-ai.github.io/InvokeAI/features/CLI/">command-line completion behavior</a>.
New commands added:
- List command-line history with `!history`
- Search command-line history with `!search`
- Clear history with `!clear`
- Deprecated `--full_precision` / `-F`. Simply omit it and `invoke.py` will auto
configure. To switch away from auto use the new flag like `--precision=float32`.
configure. To switch away from auto use the new flag like `--precision=float32`.
### v1.14 <small>(11 September 2022)</small>
- Memory optimizations for small-RAM cards. 512x512 now possible on 4 GB GPUs.
- Full support for Apple hardware with M1 or M2 chips.
- Add "seamless mode" for circular tiling of image. Generates beautiful effects.
([prixt](https://github.com/prixt)).
- Inpainting support.
- Improved web server GUI.
- Lots of code and documentation cleanups.
### v1.13 <small>(3 September 2022</small>
- Support image variations (see [VARIATIONS](features/VARIATIONS.md)
([Kevin Gibbons](https://github.com/bakkot) and many contributors and reviewers)
- Supports a Google Colab notebook for a standalone server running on Google hardware
[Arturo Mendivil](https://github.com/artmen1516)
- WebUI supports GFPGAN/ESRGAN facial reconstruction and upscaling
[Kevin Gibbons](https://github.com/bakkot)
- WebUI supports incremental display of in-progress images during generation
[Kevin Gibbons](https://github.com/bakkot)
- A new configuration file scheme that allows new models (including upcoming stable-diffusion-v1.5)
to be added without altering the code. ([David Wager](https://github.com/maddavid12))
- Can specify --grid on invoke.py command line as the default.
- Miscellaneous internal bug and stability fixes.
- Works on M1 Apple hardware.
- Multiple bug fixes.
For older changelogs, please visit the **[CHANGELOG](features/CHANGELOG.md)**.
For older changelogs, please visit the **[CHANGELOG](CHANGELOG.md#v114-11-september-2022)**.
## :material-target: Troubleshooting

View File

@@ -43,6 +43,7 @@ title: Manual Installation, Linux
environment named `invokeai` and activate the environment.
```bash
(base) rm -rf src # (this is a precaution in case there is already a src directory)
(base) ~/InvokeAI$ conda env create
(base) ~/InvokeAI$ conda activate invokeai
(invokeai) ~/InvokeAI$
@@ -51,50 +52,54 @@ title: Manual Installation, Linux
After these steps, your command prompt will be prefixed by `(invokeai)` as shown
above.
6. Load a couple of small machine-learning models required by stable diffusion:
6. Load the big stable diffusion weights files and a couple of smaller machine-learning models:
```bash
(invokeai) ~/InvokeAI$ python3 scripts/preload_models.py
```
!!! note
This script will lead you through the process of creating an account on Hugging Face,
accepting the terms and conditions of the Stable Diffusion model license, and
obtaining an access token for downloading. It will then download and install the
weights files for you.
This step is necessary because I modified the original just-in-time
model loading scheme to allow the script to work on GPU machines that are not
internet connected. See [Preload Models](../features/OTHER.md#preload-models)
Please see [../features/INSTALLING_MODELS.md] for a manual process for doing the
same thing.
7. Install the weights for the stable diffusion model.
7. Start generating images!
- Sign up at https://huggingface.co
- Go to the [Stable diffusion diffusion model page](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original)
- Accept the terms and click Access Repository
- Download [v1-5-pruned-emaonly.ckpt (4.27 GB)](https://huggingface.co/runwayml/stable-diffusion-v1-5/blob/main/v1-5-pruned-emaonly.ckpt)
and move it into this directory under `models/ldm/stable_diffusion_v1/v1-5-pruned-emaonly.ckpt`
# Command-line interface
(invokeai) python scripts/invoke.py
There are many other models that you can use. Please see [../features/INSTALLING_MODELS.md]
for details.
# or run the web interface on localhost:9090!
(invokeai) python scripts/invoke.py --web
8. Start generating images!
# or run the web interface on your machine's network interface!
(invokeai) python scripts/invoke.py --web --host 0.0.0.0
```bash
# for the pre-release weights use the -l or --liaon400m switch
(invokeai) ~/InvokeAI$ python3 scripts/invoke.py -l
To use an alternative model you may invoke the `!switch` command in
the CLI, or pass `--model <model_name>` during `invoke.py` launch for
either the CLI or the Web UI. See [Command Line
Client](../features/CLI.md#model-selection-and-importation). The
model names are defined in `configs/models.yaml`.
# for the post-release weights do not use the switch
(invokeai) ~/InvokeAI$ python3 scripts/invoke.py
# for additional configuration switches and arguments, use -h or --help
(invokeai) ~/InvokeAI$ python3 scripts/invoke.py -h
```
9. Subsequently, to relaunch the script, be sure to run "conda activate invokeai" (step 5, second command), enter the `InvokeAI` directory, and then launch the invoke script (step 8). If you forget to activate the 'invokeai' environment, the script will fail with multiple `ModuleNotFound` errors.
9. Subsequently, to relaunch the script, be sure to run "conda
activate invokeai" (step 5, second command), enter the `InvokeAI`
directory, and then launch the invoke script (step 8). If you forget
to activate the 'invokeai' environment, the script will fail with
multiple `ModuleNotFound` errors.
## Updating to newer versions of the script
This distribution is changing rapidly. If you used the `git clone` method (step 5) to download the InvokeAI directory, then to update to the latest and greatest version, launch the Anaconda window, enter `InvokeAI` and type:
This distribution is changing rapidly. If you used the `git clone`
method (step 5) to download the InvokeAI directory, then to update to
the latest and greatest version, launch the Anaconda window, enter
`InvokeAI` and type:
```bash
(invokeai) ~/InvokeAI$ git pull
(invokeai) ~/InvokeAI$ rm -rf src # prevents conda freezing errors
(invokeai) ~/InvokeAI$ conda env update -f environment.yml
```

View File

@@ -19,24 +19,9 @@ an issue on Github and we will do our best to help.
## Installation
First you need to download a large checkpoint file.
1. Sign up at https://huggingface.co
2. Go to the [Stable diffusion diffusion model page](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original)
3. Accept the terms and click Access Repository
4. Download [v1-5-pruned-emaonly.ckpt (4.27 GB)](https://huggingface.co/runwayml/stable-diffusion-v1-5/blob/main/v1-5-pruned-emaonly.ckpt)
and move it into this directory under `models/ldm/stable_diffusion_v1/v1-5-pruned-emaonly.ckpt`
There are many other models that you can try. Please see [../features/INSTALLING_MODELS.md]
for details.
While that is downloading, open Terminal and run the following
commands one at a time, reading the comments and taking care to run
the appropriate command for your Mac's architecture (Intel or M1).
!!! todo "Homebrew"
If you have no brew installation yet (otherwise skip):
First you will install the "brew" package manager. Skip this if brew is already installed.
```bash title="install brew (and Xcode command line tools)"
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
@@ -100,25 +85,6 @@ the appropriate command for your Mac's architecture (Intel or M1).
cd InvokeAI
```
!!! todo "Wait until the checkpoint-file download finished, then proceed"
We will leave the big checkpoint wherever you stashed it for long-term storage,
and make a link to it from the repo's folder. This allows you to use it for
other repos, or if you need to delete Invoke AI, you won't have to download it again.
```{.bash .annotate}
# Make the directory in the repo for the symlink
mkdir -p models/ldm/stable-diffusion-v1/
# This is the folder where you put the checkpoint file `sd-v1-4.ckpt`
PATH_TO_CKPT="$HOME/Downloads" # (1)!
# Create a link to the checkpoint
ln -s "$PATH_TO_CKPT/sd-v1-4.ckpt" models/ldm/stable-diffusion-v1/model.ckpt
```
1. replace `$HOME/Downloads` with the Location where you actually stored the Checkppoint (`sd-v1-4.ckpt`)
!!! todo "Create the environment & install packages"
=== "M1 Mac"
@@ -137,25 +103,40 @@ the appropriate command for your Mac's architecture (Intel or M1).
# Activate the environment (you need to do this every time you want to run SD)
conda activate invokeai
# This will download some bits and pieces and make take a while
(invokeai) python scripts/preload_models.py
# Run SD!
(invokeai) python scripts/dream.py
# or run the web interface!
(invokeai) python scripts/invoke.py --web
# The original scripts should work as well.
(invokeai) python scripts/orig_scripts/txt2img.py \
--prompt "a photograph of an astronaut riding a horse" \
--plms
```
!!! info
`export PIP_EXISTS_ACTION=w` is a precaution to fix `conda env
create -f environment-mac.yml` never finishing in some situations. So
it isn't required but wont hurt.
it isn't required but won't hurt.
!!! todo "Download the model weight files"
The `preload_models.py` script downloads and installs the model weight
files for you. It will lead you through the process of getting a Hugging Face
account, accepting the Stable Diffusion model weight license agreement, and
creating a download token:
# This will take some time, depending on the speed of your internet connection
# and will consume about 10GB of space
(invokeai) python scripts/preload_models.py
!! todo "Run InvokeAI!"
# Command-line interface
(invokeai) python scripts/invoke.py
# or run the web interface on localhost:9090!
(invokeai) python scripts/invoke.py --web
# or run the web interface on your machine's network interface!
(invokeai) python scripts/invoke.py --web --host 0.0.0.0
To use an alternative model you may invoke the `!switch` command in
the CLI, or pass `--model <model_name>` during `invoke.py` launch for
either the CLI or the Web UI. See [Command Line
Client](../features/CLI.md#model-selection-and-importation). The
model names are defined in `configs/models.yaml`.
---
## Common problems
@@ -238,7 +219,7 @@ There are several causes of these errors:
conda env remove -n invokeai
conda env create -f environment-mac.yml
```
4. If you have activated the invokeai virtual environment and tried rebuilding it,
maybe the problem could be that I have something installed that you don't and
you'll just need to manually install it. Make sure you activate the virtual

View File

@@ -69,40 +69,42 @@ in the wiki
environment file isn't specified, conda will default to `environment.yml`. You will need
to provide the `-f` option if you wish to load a different environment file at any point.
7. Run the command:
7. Load the big stable diffusion weights files and a couple of smaller machine-learning models:
```batch
python scripts\preload_models.py
```bash
(invokeai) ~/InvokeAI$ python3 scripts/preload_models.py
```
This installs several machine learning models that stable diffusion requires.
!!! note
This script will lead you through the process of creating an account on Hugging Face,
accepting the terms and conditions of the Stable Diffusion model license, and
obtaining an access token for downloading. It will then download and install the
weights files for you.
Note: This step is required. This was done because some users may might be
blocked by firewalls or have limited internet connectivity for the models to
be downloaded just-in-time.
Please see [../features/INSTALLING_MODELS.md] for a manual process for doing the
same thing.
8. Now you need to install the weights for the big stable diffusion model.
8. Start generating images!
- Sign up at https://huggingface.co
- Go to the [Stable diffusion diffusion model page](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original)
- Accept the terms and click Access Repository
- Download [v1-5-pruned-emaonly.ckpt (4.27 GB)](https://huggingface.co/runwayml/stable-diffusion-v1-5/blob/main/v1-5-pruned-emaonly.ckpt)
and move it into this directory under `models/ldm/stable_diffusion_v1/v1-5-pruned-emaonly.ckpt`
# Command-line interface
(invokeai) python scripts/invoke.py
There are many other models that you can use. Please see [../features/INSTALLING_MODELS.md]
for details.
# or run the web interface on localhost:9090!
(invokeai) python scripts/invoke.py --web
9. Start generating images!
# or run the web interface on your machine's network interface!
(invokeai) python scripts/invoke.py --web --host 0.0.0.0
```batch title="for the pre-release weights"
python scripts\invoke.py -l
```
To use an alternative model you may invoke the `!switch` command in
the CLI, or pass `--model <model_name>` during `invoke.py` launch for
either the CLI or the Web UI. See [Command Line
Client](../features/CLI.md#model-selection-and-importation). The
model names are defined in `configs/models.yaml`.
```batch title="for the post-release weights"
python scripts\invoke.py
```
10. Subsequently, to relaunch the script, first activate the Anaconda command window (step 3),enter the InvokeAI directory (step 5, `cd \path\to\InvokeAI`), run `conda activate invokeai` (step 6b), and then launch the invoke script (step 9).
9. Subsequently, to relaunch the script, first activate the Anaconda
command window (step 3),enter the InvokeAI directory (step 5, `cd
\path\to\InvokeAI`), run `conda activate invokeai` (step 6b), and then
launch the invoke script (step 9).
!!! tip "Tildebyte has written an alternative"

View File

@@ -11,6 +11,7 @@ dependencies:
- numpy=1.19
- imageio=2.9.0
- opencv=4.6.0
- getpass_asterisk
- pillow=8.*
- flask=2.1.*
- flask_cors=3.0.10

View File

@@ -51,6 +51,7 @@ dependencies:
- transformers=4.23.1
- torch-fidelity=0.3.0
- pip:
- getpass_asterisk
- dependency_injector==4.40.0
- realesrgan==0.2.5.0
- test-tube==0.7.5

View File

@@ -1,14 +1,16 @@
name: invokeai
channels:
- pytorch
- nvidia
- conda-forge
- defaults
dependencies:
- python>=3.9
- pip=22.2.2
- numpy=1.23.3
- torchvision=0.14.0
- pytorch-cuda=11.7
- torchvision=0.13.1
- torchaudio=0.12.1
- pytorch=1.12.1
- cudatoolkit=11.6
- pip:
- albumentations==0.4.3
- opencv-python==4.5.5.64
@@ -16,8 +18,7 @@ dependencies:
- imageio==2.9.0
- imageio-ffmpeg==0.4.2
- pytorch-lightning==1.7.7
- omegaconf==2.1.1
- realesrgan==0.2.5.0
- omegaconf==2.2.3
- test-tube>=0.7.5
- streamlit==1.12.0
- send2trash==1.8.0
@@ -33,10 +34,12 @@ dependencies:
- flask_cors==3.0.10
- dependency_injector==4.40.0
- eventlet
- getpass_asterisk
- kornia==0.6.0
- -e git+https://github.com/openai/CLIP.git@main#egg=clip
- -e git+https://github.com/CompVis/taming-transformers.git@master#egg=taming-transformers
- -e git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k_diffusion
- -e git+https://github.com/TencentARC/GFPGAN.git#egg=gfpgan
- -e git+https://github.com/invoke-ai/Real-ESRGAN.git#egg=realesrgan
- -e git+https://github.com/invoke-ai/GFPGAN.git#egg=gfpgan
- -e git+https://github.com/invoke-ai/clipseg.git@models-rename#egg=clipseg
- -e .

File diff suppressed because one or more lines are too long

501
frontend/dist/assets/index.1fc0290b.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

829
frontend/dist/assets/index.4488003f.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

690
frontend/dist/assets/index.ae92a637.js vendored Normal file

File diff suppressed because one or more lines are too long

517
frontend/dist/assets/index.cc049b93.js vendored Normal file

File diff suppressed because one or more lines are too long

517
frontend/dist/assets/index.e2832fd4.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
<script type="module" crossorigin src="./assets/index.044a626e.js"></script>
<link rel="stylesheet" href="./assets/index.52c8231e.css">
<script type="module" crossorigin src="./assets/index.1fc0290b.js"></script>
<link rel="stylesheet" href="./assets/index.40a72c80.css">
</head>
<body>

10651
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import ProgressBar from '../features/system/ProgressBar';
import SiteHeader from '../features/system/SiteHeader';
import Console from '../features/system/Console';
import Loading from '../Loading';
import { useAppDispatch } from './store';
import { requestSystemConfig } from './socketio/actions';
import { keepGUIAlive } from './utils';
@@ -79,17 +78,14 @@ const appSelector = createSelector(
const App = () => {
const dispatch = useAppDispatch();
const [isReady, setIsReady] = useState<boolean>(false);
const { shouldShowGalleryButton, shouldShowOptionsPanelButton } =
useAppSelector(appSelector);
useEffect(() => {
dispatch(requestSystemConfig());
setIsReady(true);
}, [dispatch]);
return isReady ? (
return (
<div className="App">
<ImageUploader>
<ProgressBar />
@@ -104,8 +100,6 @@ const App = () => {
{shouldShowOptionsPanelButton && <FloatingOptionsPanelButtons />}
</ImageUploader>
</div>
) : (
<Loading />
);
};

View File

@@ -1,5 +1,7 @@
// TODO: use Enums?
import { InProgressImageType } from '../features/system/systemSlice';
// Valid samplers
export const SAMPLERS: Array<string> = [
'ddim',
@@ -37,3 +39,12 @@ export const NUMPY_RAND_MIN = 0;
export const NUMPY_RAND_MAX = 4294967295;
export const FACETOOL_TYPES = ['gfpgan', 'codeformer'] as const;
export const IN_PROGRESS_IMAGE_TYPES: Array<{
key: string;
value: InProgressImageType;
}> = [
{ key: 'None', value: 'none' },
{ key: 'Fast', value: 'latents' },
{ key: 'Accurate', value: 'full-res' },
];

View File

@@ -115,7 +115,8 @@ export declare type Image = {
metadata?: Metadata;
width: number;
height: number;
category: GalleryCategory;
category: GalleryCategory;
isBase64: boolean;
};
// GalleryImages is an array of Image.

View File

@@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { RootState } from '../../app/store';
import { RootState } from '../store';
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
import { OptionsState } from '../../features/options/optionsSlice';
@@ -25,7 +25,7 @@ export const readinessSelector = createSelector(
prompt,
shouldGenerateVariations,
seedWeights,
maskPath,
// maskPath,
initialImage,
seed,
} = options;
@@ -34,33 +34,45 @@ export const readinessSelector = createSelector(
const { imageToInpaint } = inpainting;
let isReady = true;
const reasonsWhyNotReady: string[] = [];
// Cannot generate without a prompt
if (!prompt || Boolean(prompt.match(/^[\s\r\n]+$/))) {
return false;
isReady = false;
reasonsWhyNotReady.push('Missing prompt');
}
if (activeTabName === 'img2img' && !initialImage) {
return false;
isReady = false;
reasonsWhyNotReady.push('No initial image selected');
}
if (activeTabName === 'inpainting' && !imageToInpaint) {
return false;
isReady = false;
reasonsWhyNotReady.push('No inpainting image selected');
}
// Cannot generate with a mask without img2img
if (maskPath && !initialImage) {
return false;
}
// // We don't use mask paths now.
// // Cannot generate with a mask without img2img
// if (maskPath && !initialImage) {
// isReady = false;
// reasonsWhyNotReady.push(
// 'On ImageToImage tab, but no mask is provided.'
// );
// }
// TODO: job queue
// Cannot generate if already processing an image
if (isProcessing) {
return false;
isReady = false;
reasonsWhyNotReady.push('System Busy');
}
// Cannot generate if not connected
if (!isConnected) {
return false;
isReady = false;
reasonsWhyNotReady.push('System Disconnected');
}
// Cannot generate variations without valid seed weights
@@ -68,11 +80,12 @@ export const readinessSelector = createSelector(
shouldGenerateVariations &&
(!(validateSeedWeights(seedWeights) || seedWeights === '') || seed === -1)
) {
return false;
isReady = false;
reasonsWhyNotReady.push('Seed-Weights badly formatted.');
}
// All good
return true;
return { isReady, reasonsWhyNotReady };
},
{
memoizeOptions: {

View File

@@ -146,12 +146,14 @@ const makeSocketIOListeners = (
...data,
})
);
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image generated: ${data.url}`,
})
);
if (!data.isBase64) {
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image generated: ${data.url}`,
})
);
}
} catch (e) {
console.error(e);
}
@@ -261,18 +263,20 @@ const makeSocketIOListeners = (
const { intermediateImage } = getState().gallery;
if (intermediateImage) {
dispatch(
addImage({
category: 'result',
image: intermediateImage,
})
);
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image saved: ${intermediateImage.url}`,
})
);
if (!intermediateImage.isBase64) {
dispatch(
addImage({
category: 'result',
image: intermediateImage,
})
);
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image saved: ${intermediateImage.url}`,
})
);
}
dispatch(clearIntermediateImage());
}

View File

@@ -5,12 +5,16 @@ import type { TypedUseSelectorHook } from 'react-redux';
import { persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
import optionsReducer from '../features/options/optionsSlice';
import galleryReducer from '../features/gallery/gallerySlice';
import inpaintingReducer from '../features/tabs/Inpainting/inpaintingSlice';
import optionsReducer, { OptionsState } from '../features/options/optionsSlice';
import galleryReducer, { GalleryState } from '../features/gallery/gallerySlice';
import inpaintingReducer, {
InpaintingState,
} from '../features/tabs/Inpainting/inpaintingSlice';
import systemReducer from '../features/system/systemSlice';
import systemReducer, { SystemState } from '../features/system/systemSlice';
import { socketioMiddleware } from './socketio/middleware';
import autoMergeLevel2 from 'redux-persist/es/stateReconciler/autoMergeLevel2';
import { PersistPartial } from 'redux-persist/es/persistReducer';
/**
* redux-persist provides an easy and reliable way to persist state across reloads.
@@ -33,12 +37,14 @@ import { socketioMiddleware } from './socketio/middleware';
const rootPersistConfig = {
key: 'root',
storage,
stateReconciler: autoMergeLevel2,
blacklist: ['gallery', 'system', 'inpainting'],
};
const systemPersistConfig = {
key: 'system',
storage,
stateReconciler: autoMergeLevel2,
blacklist: [
'isCancelable',
'isConnected',
@@ -58,6 +64,7 @@ const systemPersistConfig = {
const galleryPersistConfig = {
key: 'gallery',
storage,
stateReconciler: autoMergeLevel2,
whitelist: [
'galleryWidth',
'shouldPinGallery',
@@ -71,17 +78,26 @@ const galleryPersistConfig = {
const inpaintingPersistConfig = {
key: 'inpainting',
storage,
stateReconciler: autoMergeLevel2,
blacklist: ['pastLines', 'futuresLines', 'cursorPosition'],
};
const reducers = combineReducers({
options: optionsReducer,
gallery: persistReducer(galleryPersistConfig, galleryReducer),
system: persistReducer(systemPersistConfig, systemReducer),
inpainting: persistReducer(inpaintingPersistConfig, inpaintingReducer),
gallery: persistReducer<GalleryState>(galleryPersistConfig, galleryReducer),
system: persistReducer<SystemState>(systemPersistConfig, systemReducer),
inpainting: persistReducer<InpaintingState>(
inpaintingPersistConfig,
inpaintingReducer
),
});
const persistedReducer = persistReducer(rootPersistConfig, reducers);
const persistedReducer = persistReducer<{
options: OptionsState;
gallery: GalleryState & PersistPartial;
system: SystemState & PersistPartial;
inpainting: InpaintingState & PersistPartial;
}>(rootPersistConfig, reducers);
// Continue with store setup
export const store = configureStore({

View File

@@ -0,0 +1,3 @@
.invokeai__button {
justify-content: space-between;
}

View File

@@ -1,23 +1,32 @@
import { Button, ButtonProps, Tooltip } from '@chakra-ui/react';
import {
Button,
ButtonProps,
forwardRef,
Tooltip,
TooltipProps,
} from '@chakra-ui/react';
import { ReactNode } from 'react';
export interface IAIButtonProps extends ButtonProps {
label: string;
tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>;
styleClass?: string;
children: ReactNode;
}
/**
* Reusable customized button component.
*/
const IAIButton = (props: IAIButtonProps) => {
const { label, tooltip = '', styleClass, ...rest } = props;
const IAIButton = forwardRef((props: IAIButtonProps, forwardedRef) => {
const { children, tooltip = '', tooltipProps, styleClass, ...rest } = props;
return (
<Tooltip label={tooltip}>
<Button className={styleClass ? styleClass : ''} {...rest}>
{label}
<Tooltip label={tooltip} {...tooltipProps}>
<Button
ref={forwardedRef}
className={['invokeai__button', styleClass].join(' ')}
{...rest}
>
{children}
</Button>
</Tooltip>
);
};
});
export default IAIButton;

View File

@@ -1,6 +1,6 @@
@use '../../styles/Mixins/' as *;
.icon-button {
.invokeai__icon-button {
background-color: var(--btn-grey);
cursor: pointer;
@@ -8,13 +8,68 @@
background-color: var(--btn-grey-hover);
}
&[data-selected=true] {
&[data-selected='true'] {
background-color: var(--accent-color);
&:hover {
background-color: var(--accent-color-hover);
}
}
&[disabled] {
cursor: not-allowed;
}
&[data-variant='link'] {
background: none !important;
&:hover {
background: none !important;
}
}
&[data-selected='true'] {
border-color: var(--accent-color);
&:hover {
border-color: var(--accent-color-hover);
}
}
&[data-alert='true'] {
animation-name: pulseColor;
animation-duration: 1s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
&:hover {
animation: none;
background-color: var(--accent-color-hover);
}
}
&[data-as-checkbox='true'] {
background-color: var(--btn-grey);
border: 3px solid var(--btn-grey);
svg {
fill: var(--text-color);
}
&:hover {
background-color: var(--btn-grey);
border-color: var(--btn-checkbox-border-hover);
svg {
fill: var(--text-color);
}
}
}
}
@keyframes pulseColor {
0% {
background-color: var(--accent-color);
}
50% {
background-color: var(--accent-color-dim);
}
100% {
background-color: var(--accent-color);
}
}

View File

@@ -2,38 +2,40 @@ import {
IconButtonProps,
IconButton,
Tooltip,
PlacementWithLogical,
TooltipProps,
forwardRef,
} from '@chakra-ui/react';
interface Props extends IconButtonProps {
tooltip?: string;
tooltipPlacement?: PlacementWithLogical | undefined;
export type IAIIconButtonProps = IconButtonProps & {
styleClass?: string;
}
tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>;
asCheckbox?: boolean;
isChecked?: boolean;
};
/**
* Reusable customized button component. Originally was more customized - now probably unecessary.
*/
const IAIIconButton = (props: Props) => {
const IAIIconButton = forwardRef((props: IAIIconButtonProps, forwardedRef) => {
const {
tooltip = '',
tooltipPlacement = 'bottom',
styleClass,
onClick,
cursor,
tooltipProps,
asCheckbox,
isChecked,
...rest
} = props;
return (
<Tooltip label={tooltip} hasArrow placement={tooltipPlacement}>
<Tooltip label={tooltip} hasArrow {...tooltipProps}>
<IconButton
className={`icon-button ${styleClass}`}
ref={forwardedRef}
className={`invokeai__icon-button ${styleClass}`}
data-as-checkbox={asCheckbox}
data-selected={isChecked !== undefined ? isChecked : undefined}
style={props.onClick ? { cursor: 'pointer' } : {}}
{...rest}
cursor={cursor ? cursor : onClick ? 'pointer' : 'unset'}
onClick={onClick}
/>
</Tooltip>
);
};
});
export default IAIIconButton;

View File

@@ -1,7 +1,6 @@
.invokeai__number-input-form-control {
display: grid;
grid-template-columns: max-content auto;
column-gap: 1rem;
align-items: center;
.invokeai__number-input-form-label {
@@ -11,6 +10,7 @@
margin-bottom: 0;
flex-grow: 2;
white-space: nowrap;
padding-right: 1rem;
&[data-focus] + .invokeai__number-input-root {
outline: none;

View File

@@ -123,13 +123,15 @@ const IAINumberInput = (props: Props) => {
}
{...formControlProps}
>
<FormLabel
className="invokeai__number-input-form-label"
style={{ display: label ? 'block' : 'none' }}
{...formLabelProps}
>
{label}
</FormLabel>
{label && (
<FormLabel
className="invokeai__number-input-form-label"
style={{ display: label ? 'block' : 'none' }}
{...formLabelProps}
>
{label}
</FormLabel>
)}
<NumberInput
className="invokeai__number-input-root"
value={valueAsString}
@@ -145,19 +147,18 @@ const IAINumberInput = (props: Props) => {
textAlign={textAlign}
{...numberInputFieldProps}
/>
<div
className="invokeai__number-input-stepper"
style={showStepper ? { display: 'block' } : { display: 'none' }}
>
<NumberIncrementStepper
{...numberInputStepperProps}
className="invokeai__number-input-stepper-button"
/>
<NumberDecrementStepper
{...numberInputStepperProps}
className="invokeai__number-input-stepper-button"
/>
</div>
{showStepper && (
<div className="invokeai__number-input-stepper">
<NumberIncrementStepper
{...numberInputStepperProps}
className="invokeai__number-input-stepper-button"
/>
<NumberDecrementStepper
{...numberInputStepperProps}
className="invokeai__number-input-stepper-button"
/>
</div>
)}
</NumberInput>
</FormControl>
</Tooltip>

View File

@@ -3,13 +3,14 @@ import {
PopoverArrow,
PopoverContent,
PopoverTrigger,
Box,
BoxProps,
} from '@chakra-ui/react';
import { PopoverProps } from '@chakra-ui/react';
import { ReactNode } from 'react';
type IAIPopoverProps = PopoverProps & {
triggerComponent: ReactNode;
triggerContainerProps?: BoxProps;
children: ReactNode;
styleClass?: string;
hasArrow?: boolean;
@@ -23,11 +24,10 @@ const IAIPopover = (props: IAIPopoverProps) => {
hasArrow = true,
...rest
} = props;
return (
<Popover {...rest}>
<PopoverTrigger>
<Box>{triggerComponent}</Box>
</PopoverTrigger>
<PopoverTrigger>{triggerComponent}</PopoverTrigger>
<PopoverContent className={`invokeai__popover-content ${styleClass}`}>
{hasArrow && <PopoverArrow className={'invokeai__popover-arrow'} />}
{children}

View File

@@ -1,8 +1,7 @@
@use '../../styles/Mixins/' as *;
.invokeai__select {
display: grid;
grid-template-columns: repeat(2, max-content);
display: flex;
column-gap: 1rem;
align-items: center;
width: max-content;

View File

@@ -1,17 +1,17 @@
import { FormControl, FormLabel, Select, SelectProps } from '@chakra-ui/react';
import { MouseEvent } from 'react';
interface Props extends SelectProps {
type IAISelectProps = SelectProps & {
label: string;
styleClass?: string;
validValues:
| Array<number | string>
| Array<{ key: string; value: string | number }>;
}
};
/**
* Customized Chakra FormControl + Select multi-part component.
*/
const IAISelect = (props: Props) => {
const IAISelect = (props: IAISelectProps) => {
const {
label,
isDisabled,
@@ -33,19 +33,19 @@ const IAISelect = (props: Props) => {
}}
>
<FormLabel
className="invokeai__select-label"
fontSize={fontSize}
marginBottom={1}
flexGrow={2}
whiteSpace="nowrap"
className="invokeai__select-label"
>
{label}
</FormLabel>
<Select
className="invokeai__select-picker"
fontSize={fontSize}
size={size}
{...rest}
className="invokeai__select-picker"
>
{validValues.map((opt) => {
return typeof opt === 'string' || typeof opt === 'number' ? (
@@ -53,7 +53,11 @@ const IAISelect = (props: Props) => {
{opt}
</option>
) : (
<option key={opt.value} value={opt.value}>
<option
key={opt.value}
value={opt.value}
className="invokeai__select-option"
>
{opt.key}
</option>
);

View File

@@ -22,8 +22,6 @@ const IAISwitch = (props: Props) => {
const {
label,
isDisabled = false,
// fontSize = 'md',
// size = 'md',
width = 'auto',
formControlProps,
formLabelProps,
@@ -39,17 +37,11 @@ const IAISwitch = (props: Props) => {
>
<FormLabel
className="invokeai__switch-form-label"
// fontSize={fontSize}
whiteSpace="nowrap"
{...formLabelProps}
>
{label}
<Switch
className="invokeai__switch-root"
// size={size}
// className="switch-button"
{...rest}
/>
<Switch className="invokeai__switch-root" {...rest} />
</FormLabel>
</FormControl>
);

View File

@@ -0,0 +1,39 @@
import { Heading } from '@chakra-ui/react';
import { useHotkeys } from 'react-hotkeys-hook';
type ImageUploadOverlayProps = {
isDragAccept: boolean;
isDragReject: boolean;
overlaySecondaryText: string;
setIsHandlingUpload: (isHandlingUpload: boolean) => void;
};
const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
const {
isDragAccept,
isDragReject,
overlaySecondaryText,
setIsHandlingUpload,
} = props;
useHotkeys('esc', () => {
setIsHandlingUpload(false);
});
return (
<div className="dropzone-container">
{isDragAccept && (
<div className="dropzone-overlay is-drag-accept">
<Heading size={'lg'}>Upload Image{overlaySecondaryText}</Heading>
</div>
)}
{isDragReject && (
<div className="dropzone-overlay is-drag-reject">
<Heading size={'lg'}>Invalid Upload</Heading>
<Heading size={'md'}>Must be single JPEG or PNG image</Heading>
</div>
)}
</div>
);
};
export default ImageUploadOverlay;

View File

@@ -16,9 +16,10 @@
row-gap: 1rem;
align-items: center;
justify-content: center;
background-color: var(--background-color);
&.is-drag-accept {
box-shadow: inset 0 0 20rem 1rem var(--status-good-color);
box-shadow: inset 0 0 20rem 1rem var(--accent-color);
}
&.is-drag-reject {
@@ -32,6 +33,7 @@
}
.image-uploader-button-outer {
min-width: 20rem;
width: 100%;
height: 100%;
display: flex;

View File

@@ -1,11 +1,13 @@
import { useCallback, ReactNode, useState, useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '../../app/store';
import { FileRejection, useDropzone } from 'react-dropzone';
import { Heading, Spinner, useToast } from '@chakra-ui/react';
import { useToast } from '@chakra-ui/react';
import { uploadImage } from '../../app/socketio/actions';
import { ImageUploadDestination, UploadImagePayload } from '../../app/invokeai';
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
import { tabDict } from '../../features/tabs/InvokeTabs';
import ImageUploadOverlay from './ImageUploadOverlay';
type ImageUploaderProps = {
children: ReactNode;
@@ -71,6 +73,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
noClick: true,
onDrop,
onDragOver: () => setIsHandlingUpload(true),
maxFiles: 1,
});
@@ -128,30 +131,22 @@ const ImageUploader = (props: ImageUploaderProps) => {
};
}, [dispatch, toast, activeTabName]);
const overlaySecondaryText = ['img2img', 'inpainting'].includes(activeTabName)
? ` to ${tabDict[activeTabName as keyof typeof tabDict].tooltip}`
: ``;
return (
<ImageUploaderTriggerContext.Provider value={open}>
<div {...getRootProps({ style: {} })}>
<input {...getInputProps()} />
{children}
{isDragActive && (
<div className="dropzone-container">
{isDragAccept && (
<div className="dropzone-overlay is-drag-accept">
<Heading size={'lg'}>Drop Images</Heading>
</div>
)}
{isDragReject && (
<div className="dropzone-overlay is-drag-reject">
<Heading size={'lg'}>Invalid Upload</Heading>
<Heading size={'md'}>Must be single JPEG or PNG image</Heading>
</div>
)}
{isHandlingUpload && (
<div className="dropzone-overlay is-handling-upload">
<Spinner />
</div>
)}
</div>
{isDragActive && isHandlingUpload && (
<ImageUploadOverlay
isDragAccept={isDragAccept}
isDragReject={isDragReject}
overlaySecondaryText={overlaySecondaryText}
setIsHandlingUpload={setIsHandlingUpload}
/>
)}
</div>
</ImageUploaderTriggerContext.Provider>

View File

@@ -0,0 +1,19 @@
import { useContext } from 'react';
import { FaUpload } from 'react-icons/fa';
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
import IAIIconButton from './IAIIconButton';
const ImageUploaderIconButton = () => {
const openImageUploader = useContext(ImageUploaderTriggerContext);
return (
<IAIIconButton
aria-label="Upload Image"
tooltip="Upload Image"
icon={<FaUpload />}
onClick={openImageUploader || undefined}
/>
);
};
export default ImageUploaderIconButton;

View File

@@ -62,11 +62,13 @@ export const frontendToBackendParameters = (
shouldRandomizeSeed,
} = optionsState;
const { shouldDisplayInProgress } = systemState;
const { shouldDisplayInProgressType, saveIntermediatesInterval } =
systemState;
const generationParameters: { [k: string]: any } = {
prompt,
iterations,
iterations:
shouldRandomizeSeed || shouldGenerateVariations ? iterations : 1,
steps,
cfg_scale: cfgScale,
threshold,
@@ -75,7 +77,9 @@ export const frontendToBackendParameters = (
width,
sampler_name: sampler,
seed,
progress_images: shouldDisplayInProgress,
progress_images: shouldDisplayInProgressType === 'full-res',
progress_latents: shouldDisplayInProgressType === 'latents',
save_intermediates: saveIntermediatesInterval,
};
generationParameters.seed = shouldRandomizeSeed

View File

@@ -0,0 +1,25 @@
.current-image-options {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
column-gap: 0.5em;
.current-image-send-to-popover,
.current-image-postprocessing-popover {
display: flex;
flex-direction: column;
row-gap: 0.5rem;
max-width: 25rem;
}
.chakra-popover__popper {
z-index: 11;
}
.delete-image-btn {
svg {
fill: var(--btn-delete-image);
}
}
}

View File

@@ -1,8 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash';
import * as InvokeAI from '../../app/invokeai';
import { useAppDispatch, useAppSelector } from '../../app/store';
import { RootState } from '../../app/store';
import {
@@ -10,6 +8,7 @@ import {
setActiveTab,
setAllParameters,
setInitialImage,
setPrompt,
setSeed,
setShouldShowImageDetails,
} from '../options/optionsSlice';
@@ -18,39 +17,42 @@ import { SystemState } from '../system/systemSlice';
import IAIButton from '../../common/components/IAIButton';
import { runESRGAN, runFacetool } from '../../app/socketio/actions';
import IAIIconButton from '../../common/components/IAIIconButton';
import { MdDelete, MdFace, MdHd, MdImage, MdInfo } from 'react-icons/md';
import InvokePopover from './InvokePopover';
import UpscaleOptions from '../options/AdvancedOptions/Upscale/UpscaleOptions';
import FaceRestoreOptions from '../options/AdvancedOptions/FaceRestore/FaceRestoreOptions';
import { useHotkeys } from 'react-hotkeys-hook';
import { useToast } from '@chakra-ui/react';
import { FaCopy, FaPaintBrush, FaSeedling } from 'react-icons/fa';
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
import { ButtonGroup, Link, useClipboard, useToast } from '@chakra-ui/react';
import {
FaAsterisk,
FaCode,
FaCopy,
FaDownload,
FaExpandArrowsAlt,
FaGrinStars,
FaQuoteRight,
FaSeedling,
FaShare,
FaShareAlt,
FaTrash,
} from 'react-icons/fa';
import {
setImageToInpaint,
setNeedsCache,
} from '../tabs/Inpainting/inpaintingSlice';
import { GalleryState } from './gallerySlice';
import { activeTabNameSelector } from '../options/optionsSelectors';
const intermediateImageSelector = createSelector(
(state: RootState) => state.gallery,
(gallery: GalleryState) => gallery.intermediateImage,
{
memoizeOptions: {
resultEqualityCheck: (a, b) =>
(a === undefined && b === undefined) || a.uuid === b.uuid,
},
}
);
import IAIPopover from '../../common/components/IAIPopover';
const systemSelector = createSelector(
[
(state: RootState) => state.system,
(state: RootState) => state.options,
intermediateImageSelector,
(state: RootState) => state.gallery,
activeTabNameSelector,
],
(
system: SystemState,
options: OptionsState,
intermediateImage,
gallery: GalleryState,
activeTabName
) => {
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
@@ -59,6 +61,8 @@ const systemSelector = createSelector(
const { upscalingLevel, facetoolStrength, shouldShowImageDetails } =
options;
const { intermediateImage, currentImage } = gallery;
return {
isProcessing,
isConnected,
@@ -66,7 +70,8 @@ const systemSelector = createSelector(
isESRGANAvailable,
upscalingLevel,
facetoolStrength,
intermediateImage,
shouldDisableToolbarButtons: Boolean(intermediateImage) || !currentImage,
currentImage,
shouldShowImageDetails,
activeTabName,
};
@@ -78,15 +83,11 @@ const systemSelector = createSelector(
}
);
type CurrentImageButtonsProps = {
image: InvokeAI.Image;
};
/**
* Row of buttons for common actions:
* Use as init image, use all params, use seed, upscale, fix faces, details, delete.
*/
const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
const CurrentImageButtons = () => {
const dispatch = useAppDispatch();
const {
isProcessing,
@@ -95,22 +96,37 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
isESRGANAvailable,
upscalingLevel,
facetoolStrength,
intermediateImage,
shouldDisableToolbarButtons,
shouldShowImageDetails,
activeTabName,
currentImage,
} = useAppSelector(systemSelector);
const { onCopy } = useClipboard(
currentImage ? window.location.toString() + currentImage.url : ''
);
const toast = useToast();
const handleClickUseAsInitialImage = () => {
dispatch(setInitialImage(image));
dispatch(setActiveTab(1));
if (!currentImage) return;
dispatch(setInitialImage(currentImage));
dispatch(setActiveTab('img2img'));
};
const handleCopyImageLink = () => {
onCopy();
toast({
title: 'Image Link Copied',
status: 'success',
duration: 2500,
isClosable: true,
});
};
useHotkeys(
'shift+i',
() => {
if (image) {
if (currentImage) {
handleClickUseAsInitialImage();
toast({
title: 'Sent To Image To Image',
@@ -128,16 +144,20 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
});
}
},
[image]
[currentImage]
);
const handleClickUseAllParameters = () =>
image.metadata && dispatch(setAllParameters(image.metadata));
const handleClickUseAllParameters = () => {
if (!currentImage) return;
currentImage.metadata && dispatch(setAllParameters(currentImage.metadata));
};
useHotkeys(
'a',
() => {
if (['txt2img', 'img2img'].includes(image?.metadata?.image?.type)) {
if (
['txt2img', 'img2img'].includes(currentImage?.metadata?.image?.type)
) {
handleClickUseAllParameters();
toast({
title: 'Parameters Set',
@@ -155,15 +175,18 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
});
}
},
[image]
[currentImage]
);
const handleClickUseSeed = () =>
image.metadata && dispatch(setSeed(image.metadata.image.seed));
const handleClickUseSeed = () => {
currentImage?.metadata &&
dispatch(setSeed(currentImage.metadata.image.seed));
};
useHotkeys(
's',
() => {
if (image?.metadata?.image?.seed) {
if (currentImage?.metadata?.image?.seed) {
handleClickUseSeed();
toast({
title: 'Seed Set',
@@ -181,16 +204,47 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
});
}
},
[image]
[currentImage]
);
const handleClickUpscale = () => dispatch(runESRGAN(image));
const handleClickUsePrompt = () =>
currentImage?.metadata?.image?.prompt &&
dispatch(setPrompt(currentImage.metadata.image.prompt));
useHotkeys(
'p',
() => {
if (currentImage?.metadata?.image?.prompt) {
handleClickUsePrompt();
toast({
title: 'Prompt Set',
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'Prompt Not Set',
description: 'Could not find prompt for this image.',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[currentImage]
);
const handleClickUpscale = () => {
currentImage && dispatch(runESRGAN(currentImage));
};
useHotkeys(
'u',
() => {
if (
isESRGANAvailable &&
Boolean(!intermediateImage) &&
!shouldDisableToolbarButtons &&
isConnected &&
!isProcessing &&
upscalingLevel
@@ -206,23 +260,25 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
}
},
[
image,
currentImage,
isESRGANAvailable,
intermediateImage,
shouldDisableToolbarButtons,
isConnected,
isProcessing,
upscalingLevel,
]
);
const handleClickFixFaces = () => dispatch(runFacetool(image));
const handleClickFixFaces = () => {
currentImage && dispatch(runFacetool(currentImage));
};
useHotkeys(
'r',
() => {
if (
isGFPGANAvailable &&
Boolean(!intermediateImage) &&
!shouldDisableToolbarButtons &&
isConnected &&
!isProcessing &&
facetoolStrength
@@ -238,9 +294,9 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
}
},
[
image,
currentImage,
isGFPGANAvailable,
intermediateImage,
shouldDisableToolbarButtons,
isConnected,
isProcessing,
facetoolStrength,
@@ -251,10 +307,13 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
dispatch(setShouldShowImageDetails(!shouldShowImageDetails));
const handleSendToInpainting = () => {
dispatch(setImageToInpaint(image));
if (activeTabName !== 'inpainting') {
dispatch(setActiveTab('inpainting'));
}
if (!currentImage) return;
dispatch(setImageToInpaint(currentImage));
dispatch(setActiveTab('inpainting'));
dispatch(setNeedsCache(true));
toast({
title: 'Sent to Inpainting',
status: 'success',
@@ -266,7 +325,7 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
useHotkeys(
'i',
() => {
if (image) {
if (currentImage) {
handleClickShowImageDetails();
} else {
toast({
@@ -277,111 +336,141 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
});
}
},
[image, shouldShowImageDetails]
[currentImage, shouldShowImageDetails]
);
return (
<div className="current-image-options">
<IAIIconButton
icon={<MdImage />}
tooltip="Send To Image To Image"
aria-label="Send To Image To Image"
onClick={handleClickUseAsInitialImage}
/>
<ButtonGroup isAttached={true}>
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton aria-label="Send to..." icon={<FaShareAlt />} />
}
>
<div className="current-image-send-to-popover">
<IAIButton
size={'sm'}
onClick={handleClickUseAsInitialImage}
leftIcon={<FaShare />}
>
Send to Image to Image
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleSendToInpainting}
leftIcon={<FaShare />}
>
Send to Inpainting
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleCopyImageLink}
leftIcon={<FaCopy />}
>
Copy Link to Image
</IAIButton>
<IAIButton leftIcon={<FaDownload />} size={'sm'}>
<Link download={true} href={currentImage?.url}>
Download Image
</Link>
</IAIButton>
</div>
</IAIPopover>
</ButtonGroup>
<ButtonGroup isAttached={true}>
<IAIIconButton
icon={<FaQuoteRight />}
tooltip="Use Prompt"
aria-label="Use Prompt"
isDisabled={!currentImage?.metadata?.image?.prompt}
onClick={handleClickUsePrompt}
/>
<IAIIconButton
icon={<FaSeedling />}
tooltip="Use Seed"
aria-label="Use Seed"
isDisabled={!currentImage?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/>
<IAIIconButton
icon={<FaAsterisk />}
tooltip="Use All"
aria-label="Use All"
isDisabled={
!['txt2img', 'img2img'].includes(
currentImage?.metadata?.image?.type
)
}
onClick={handleClickUseAllParameters}
/>
</ButtonGroup>
<ButtonGroup isAttached={true}>
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton icon={<FaGrinStars />} aria-label="Restore Faces" />
}
>
<div className="current-image-postprocessing-popover">
<FaceRestoreOptions />
<IAIButton
isDisabled={
!isGFPGANAvailable ||
!currentImage ||
!(isConnected && !isProcessing) ||
!facetoolStrength
}
onClick={handleClickFixFaces}
>
Restore Faces
</IAIButton>
</div>
</IAIPopover>
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton icon={<FaExpandArrowsAlt />} aria-label="Upscale" />
}
>
<div className="current-image-postprocessing-popover">
<UpscaleOptions />
<IAIButton
isDisabled={
!isESRGANAvailable ||
!currentImage ||
!(isConnected && !isProcessing) ||
!upscalingLevel
}
onClick={handleClickUpscale}
>
Upscale Image
</IAIButton>
</div>
</IAIPopover>
</ButtonGroup>
<IAIIconButton
icon={<FaPaintBrush />}
tooltip="Send To Inpainting"
aria-label="Send To Inpainting"
onClick={handleSendToInpainting}
/>
<IAIIconButton
icon={<FaCopy />}
tooltip="Use All"
aria-label="Use All"
isDisabled={
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
}
onClick={handleClickUseAllParameters}
/>
<IAIIconButton
icon={<FaSeedling />}
tooltip="Use Seed"
aria-label="Use Seed"
isDisabled={!image?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/>
{/* <IAIButton
label="Use All"
isDisabled={
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
}
onClick={handleClickUseAllParameters}
/>
<IAIButton
label="Use Seed"
isDisabled={!image?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/> */}
<InvokePopover
title="Restore Faces"
popoverOptions={<FaceRestoreOptions />}
actionButton={
<IAIButton
label={'Restore Faces'}
isDisabled={
!isGFPGANAvailable ||
Boolean(intermediateImage) ||
!(isConnected && !isProcessing) ||
!facetoolStrength
}
onClick={handleClickFixFaces}
/>
}
>
<IAIIconButton icon={<MdFace />} aria-label="Restore Faces" />
</InvokePopover>
<InvokePopover
title="Upscale"
styleClass="upscale-popover"
popoverOptions={<UpscaleOptions />}
actionButton={
<IAIButton
label={'Upscale Image'}
isDisabled={
!isESRGANAvailable ||
Boolean(intermediateImage) ||
!(isConnected && !isProcessing) ||
!upscalingLevel
}
onClick={handleClickUpscale}
/>
}
>
<IAIIconButton icon={<MdHd />} aria-label="Upscale" />
</InvokePopover>
<IAIIconButton
icon={<MdInfo />}
icon={<FaCode />}
tooltip="Details"
aria-label="Details"
data-selected={shouldShowImageDetails}
onClick={handleClickShowImageDetails}
/>
<DeleteImageModal image={image}>
<DeleteImageModal image={currentImage}>
<IAIIconButton
icon={<MdDelete />}
icon={<FaTrash />}
tooltip="Delete Image"
aria-label="Delete Image"
isDisabled={
Boolean(intermediateImage) || !isConnected || isProcessing
}
isDisabled={!currentImage || !isConnected || isProcessing}
className="delete-image-btn"
/>
</DeleteImageModal>
</div>

View File

@@ -9,18 +9,6 @@
border-radius: 0.5rem;
}
.current-image-options {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
column-gap: 0.5rem;
.chakra-popover__popper {
z-index: 11;
}
}
.current-image-preview {
position: relative;
justify-content: center;
@@ -50,6 +38,7 @@
justify-content: space-between;
z-index: 1;
height: 100%;
width: 100%;
pointer-events: none;
}

View File

@@ -19,10 +19,9 @@ export const currentImageDisplaySelector = createSelector(
const { shouldShowImageDetails } = options;
return {
currentImage,
intermediateImage,
activeTabName,
shouldShowImageDetails,
hasAnImageToDisplay: currentImage || intermediateImage,
};
},
{
@@ -36,18 +35,16 @@ export const currentImageDisplaySelector = createSelector(
* Displays the current image if there is one, plus associated actions.
*/
const CurrentImageDisplay = () => {
const { currentImage, intermediateImage, activeTabName } = useAppSelector(
const { hasAnImageToDisplay, activeTabName } = useAppSelector(
currentImageDisplaySelector
);
const imageToDisplay = intermediateImage || currentImage;
return (
<div className="current-image-area" data-tab-name={activeTabName}>
{imageToDisplay ? (
{hasAnImageToDisplay ? (
<>
<CurrentImageButtons image={imageToDisplay} />
<CurrentImagePreview imageToDisplay={imageToDisplay} />
<CurrentImageButtons />
<CurrentImagePreview />
</>
) : (
<div className="current-image-display-placeholder">

View File

@@ -2,8 +2,12 @@ import { IconButton, Image } from '@chakra-ui/react';
import { useState } from 'react';
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
import { GalleryState, selectNextImage, selectPrevImage } from './gallerySlice';
import * as InvokeAI from '../../app/invokeai';
import {
GalleryCategory,
GalleryState,
selectNextImage,
selectPrevImage,
} from './gallerySlice';
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { OptionsState } from '../options/optionsSlice';
@@ -12,20 +16,29 @@ import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
export const imagesSelector = createSelector(
[(state: RootState) => state.gallery, (state: RootState) => state.options],
(gallery: GalleryState, options: OptionsState) => {
const { currentCategory } = gallery;
const { currentCategory, currentImage, intermediateImage } = gallery;
const { shouldShowImageDetails } = options;
const tempImages = gallery.categories[currentCategory].images;
const tempImages =
gallery.categories[
currentImage ? (currentImage.category as GalleryCategory) : 'result'
].images;
const currentImageIndex = tempImages.findIndex(
(i) => i.uuid === gallery?.currentImage?.uuid
);
const imagesLength = tempImages.length;
return {
imageToDisplay: intermediateImage ? intermediateImage : currentImage,
isIntermediate: intermediateImage,
currentCategory,
isOnFirstImage: currentImageIndex === 0,
isOnLastImage:
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
shouldShowImageDetails,
shouldShowPrevImageButton: currentImageIndex === 0,
shouldShowNextImageButton:
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
};
},
{
@@ -35,16 +48,16 @@ export const imagesSelector = createSelector(
}
);
interface CurrentImagePreviewProps {
imageToDisplay: InvokeAI.Image;
}
export default function CurrentImagePreview(props: CurrentImagePreviewProps) {
const { imageToDisplay } = props;
export default function CurrentImagePreview() {
const dispatch = useAppDispatch();
const { isOnFirstImage, isOnLastImage, shouldShowImageDetails } =
useAppSelector(imagesSelector);
const {
isOnFirstImage,
isOnLastImage,
shouldShowImageDetails,
imageToDisplay,
isIntermediate,
} = useAppSelector(imagesSelector);
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
useState<boolean>(false);
@@ -67,11 +80,13 @@ export default function CurrentImagePreview(props: CurrentImagePreviewProps) {
return (
<div className={'current-image-preview'}>
<Image
src={imageToDisplay.url}
width={imageToDisplay.width}
height={imageToDisplay.height}
/>
{imageToDisplay && (
<Image
src={imageToDisplay.url}
width={isIntermediate ? imageToDisplay.width : undefined}
height={isIntermediate ? imageToDisplay.height : undefined}
/>
)}
{!shouldShowImageDetails && (
<div className="current-image-next-prev-buttons">
<div
@@ -104,7 +119,7 @@ export default function CurrentImagePreview(props: CurrentImagePreviewProps) {
</div>
</div>
)}
{shouldShowImageDetails && (
{shouldShowImageDetails && imageToDisplay && (
<ImageMetadataViewer
image={imageToDisplay}
styleClass="current-image-metadata"

View File

@@ -28,12 +28,18 @@ import { RootState } from '../../app/store';
import { setShouldConfirmOnDelete, SystemState } from '../system/systemSlice';
import * as InvokeAI from '../../app/invokeai';
import { useHotkeys } from 'react-hotkeys-hook';
import _ from 'lodash';
const systemSelector = createSelector(
(state: RootState) => state.system,
(system: SystemState) => {
const { shouldConfirmOnDelete, isConnected, isProcessing } = system;
return { shouldConfirmOnDelete, isConnected, isProcessing };
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
interface DeleteImageModalProps {
@@ -44,7 +50,7 @@ interface DeleteImageModalProps {
/**
* The image to delete.
*/
image: InvokeAI.Image;
image?: InvokeAI.Image;
}
/**
@@ -67,7 +73,7 @@ const DeleteImageModal = forwardRef(
};
const handleDelete = () => {
if (isConnected && !isProcessing) {
if (isConnected && !isProcessing && image) {
dispatch(deleteImage(image));
}
onClose();
@@ -89,7 +95,7 @@ const DeleteImageModal = forwardRef(
<>
{cloneElement(children, {
// TODO: This feels wrong.
onClick: handleClickDelete,
onClick: image ? handleClickDelete : undefined,
ref: ref,
})}

View File

@@ -19,8 +19,6 @@
}
.image-gallery-wrapper {
z-index: 100;
&[data-pinned='false'] {
position: fixed;
height: 100vh;

View File

@@ -69,9 +69,9 @@ export default function ImageGallery() {
if (!shouldPinGallery) return;
if (activeTabName === 'inpainting') {
dispatch(setGalleryWidth(220));
setGalleryMinWidth(220);
setGalleryMaxWidth(220);
dispatch(setGalleryWidth(190));
setGalleryMinWidth(190);
setGalleryMaxWidth(190);
} else if (activeTabName === 'img2img') {
dispatch(
setGalleryWidth(Math.min(Math.max(Number(galleryWidth), 0), 490))
@@ -163,6 +163,15 @@ export default function ImageGallery() {
[shouldPinGallery]
);
useHotkeys(
'esc',
() => {
if (shouldPinGallery) return;
dispatch(setShouldShowGallery(false));
},
[shouldPinGallery]
);
const IMAGE_SIZE_STEP = 32;
useHotkeys(
@@ -261,6 +270,7 @@ export default function ImageGallery() {
>
<div
className="image-gallery-wrapper"
style={{ zIndex: shouldPinGallery ? 1 : 100 }}
data-pinned={shouldPinGallery}
ref={galleryRef}
onMouseLeave={!shouldPinGallery ? setCloseGalleryTimer : undefined}

View File

@@ -32,10 +32,12 @@ import {
setUpscalingStrength,
setWidth,
setInitialImage,
setShouldShowImageDetails,
} from '../../options/optionsSlice';
import promptToString from '../../../common/util/promptToString';
import { seedWeightsToString } from '../../../common/util/seedWeightPairs';
import { FaCopy } from 'react-icons/fa';
import { useHotkeys } from 'react-hotkeys-hook';
type MetadataItemProps = {
isLink?: boolean;
@@ -107,7 +109,10 @@ const memoEqualityCheck = (
const ImageMetadataViewer = memo(
({ image, styleClass }: ImageMetadataViewerProps) => {
const dispatch = useAppDispatch();
// const jsonBgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
useHotkeys('esc', () => {
dispatch(setShouldShowImageDetails(false));
});
const metadata = image?.metadata?.image || {};
const {

View File

@@ -1,35 +0,0 @@
.popover-content {
background-color: var(--background-color-secondary) !important;
border: none !important;
border-top: 0px;
background-color: var(--tab-hover-color);
border-radius: 0 0 0.4rem 0.4rem;
}
.popover-arrow {
background: var(--tab-hover-color) !important;
box-shadow: none;
}
.popover-options {
background: var(--tab-panel-bg);
border-radius: 0 0 0.4rem 0.4rem;
border: 2px solid var(--tab-hover-color);
padding: 0.75rem 1rem 0.75rem 1rem;
display: grid;
grid-auto-rows: max-content;
grid-row-gap: 0.5rem;
justify-content: space-between;
}
.popover-header {
background: var(--tab-hover-color);
border-radius: 0.4rem 0.4rem 0 0;
font-weight: bold;
border: none;
padding-left: 1rem !important;
}
.upscale-popover {
width: 23rem !important;
}

View File

@@ -1,45 +0,0 @@
import {
Box,
Popover,
PopoverArrow,
PopoverContent,
PopoverHeader,
PopoverTrigger,
} from '@chakra-ui/react';
import React, { ReactNode } from 'react';
type PopoverProps = {
title?: string;
delay?: number;
styleClass?: string;
popoverOptions?: ReactNode;
actionButton?: ReactNode;
children: ReactNode;
};
const InvokePopover = ({
title = 'Popup',
styleClass,
delay = 50,
popoverOptions,
actionButton,
children,
}: PopoverProps) => {
return (
<Popover trigger={'hover'} closeDelay={delay}>
<PopoverTrigger>
<Box>{children}</Box>
</PopoverTrigger>
<PopoverContent className={`popover-content ${styleClass}`}>
<PopoverArrow className="popover-arrow" />
<PopoverHeader className="popover-header">{title}</PopoverHeader>
<div className="popover-options">
{popoverOptions ? popoverOptions : null}
{actionButton}
</div>
</PopoverContent>
</Popover>
);
};
export default InvokePopover;

View File

@@ -4,6 +4,7 @@ import { activeTabNameSelector } from '../options/optionsSelectors';
import { OptionsState } from '../options/optionsSlice';
import { SystemState } from '../system/systemSlice';
import { GalleryState } from './gallerySlice';
import _ from 'lodash';
export const imageGallerySelector = createSelector(
[
@@ -43,6 +44,11 @@ export const imageGallerySelector = createSelector(
currentCategory,
galleryWidth,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
@@ -65,5 +71,10 @@ export const hoverableImageSelector = createSelector(
galleryImageMinimumWidth: gallery.galleryImageMinimumWidth,
activeTabName,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);

View File

@@ -1,205 +0,0 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { BiHide, BiReset, BiShow } from 'react-icons/bi';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../app/store';
import IAICheckbox from '../../../../common/components/IAICheckbox';
import IAIIconButton from '../../../../common/components/IAIIconButton';
import IAINumberInput from '../../../../common/components/IAINumberInput';
import IAISlider from '../../../../common/components/IAISlider';
import { roundDownToMultiple } from '../../../../common/util/roundDownToMultiple';
import {
InpaintingState,
setBoundingBoxDimensions,
setShouldLockBoundingBox,
setShouldShowBoundingBox,
setShouldShowBoundingBoxFill,
} from '../../../tabs/Inpainting/inpaintingSlice';
const boundingBoxDimensionsSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const {
canvasDimensions,
boundingBoxDimensions,
shouldShowBoundingBox,
shouldShowBoundingBoxFill,
pastLines,
futureLines,
shouldLockBoundingBox,
} = inpainting;
return {
canvasDimensions,
boundingBoxDimensions,
shouldShowBoundingBox,
shouldShowBoundingBoxFill,
pastLines,
futureLines,
shouldLockBoundingBox,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const BoundingBoxSettings = () => {
const dispatch = useAppDispatch();
const {
canvasDimensions,
boundingBoxDimensions,
shouldShowBoundingBox,
shouldShowBoundingBoxFill,
shouldLockBoundingBox,
} = useAppSelector(boundingBoxDimensionsSelector);
const handleChangeBoundingBoxWidth = (v: number) => {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
width: Math.floor(v),
})
);
};
const handleChangeBoundingBoxHeight = (v: number) => {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
height: Math.floor(v),
})
);
};
const handleChangeShouldShowBoundingBoxFill = () => {
dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill));
};
const handleChangeShouldLockBoundingBox = () => {
dispatch(setShouldLockBoundingBox(!shouldLockBoundingBox));
};
const handleResetWidth = () => {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
width: Math.floor(canvasDimensions.width),
})
);
};
const handleResetHeight = () => {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
height: Math.floor(canvasDimensions.height),
})
);
};
const handleShowBoundingBox = () =>
dispatch(setShouldShowBoundingBox(!shouldShowBoundingBox));
return (
<div className="inpainting-bounding-box-settings">
<div className="inpainting-bounding-box-header">
<p>Inpaint Box</p>
<IAIIconButton
aria-label="Toggle Bounding Box Visibility"
icon={
shouldShowBoundingBox ? <BiShow size={22} /> : <BiHide size={22} />
}
onClick={handleShowBoundingBox}
background={'none'}
padding={0}
/>
</div>
<div className="inpainting-bounding-box-settings-items">
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
<IAISlider
label="Box W"
min={64}
max={roundDownToMultiple(canvasDimensions.width, 64)}
step={64}
value={boundingBoxDimensions.width}
onChange={handleChangeBoundingBoxWidth}
width={'5rem'}
/>
<IAINumberInput
value={boundingBoxDimensions.width}
onChange={handleChangeBoundingBoxWidth}
min={64}
max={roundDownToMultiple(canvasDimensions.width, 64)}
step={64}
width={'5rem'}
/>
<IAIIconButton
size={'sm'}
aria-label={'Reset Width'}
tooltip={'Reset Width'}
onClick={handleResetWidth}
icon={<BiReset />}
styleClass="inpainting-bounding-box-reset-icon-btn"
isDisabled={canvasDimensions.width === boundingBoxDimensions.width}
/>
</div>
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
<IAISlider
label="Box H"
min={64}
max={roundDownToMultiple(canvasDimensions.height, 64)}
step={64}
value={boundingBoxDimensions.height}
onChange={handleChangeBoundingBoxHeight}
width={'5rem'}
/>
<IAINumberInput
value={boundingBoxDimensions.height}
onChange={handleChangeBoundingBoxHeight}
min={64}
max={roundDownToMultiple(canvasDimensions.height, 64)}
step={64}
padding="0"
width={'5rem'}
/>
<IAIIconButton
size={'sm'}
aria-label={'Reset Height'}
tooltip={'Reset Height'}
onClick={handleResetHeight}
icon={<BiReset />}
styleClass="inpainting-bounding-box-reset-icon-btn"
isDisabled={
canvasDimensions.height === boundingBoxDimensions.height
}
/>
</div>
<Flex alignItems={'center'} justifyContent={'space-between'}>
<IAICheckbox
label="Darken Outside Box"
isChecked={shouldShowBoundingBoxFill}
onChange={handleChangeShouldShowBoundingBoxFill}
styleClass="inpainting-bounding-box-darken"
/>
<IAICheckbox
label="Lock Bounding Box"
isChecked={shouldLockBoundingBox}
onChange={handleChangeShouldLockBoundingBox}
styleClass="inpainting-bounding-box-darken"
/>
</Flex>
</div>
</div>
);
};
export default BoundingBoxSettings;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../../app/store';
import IAICheckbox from '../../../../../common/components/IAICheckbox';
import { setShouldShowBoundingBoxFill } from '../../../../tabs/Inpainting/inpaintingSlice';
export default function BoundingBoxDarkenOutside() {
const dispatch = useAppDispatch();
const shouldShowBoundingBoxFill = useAppSelector(
(state: RootState) => state.inpainting.shouldShowBoundingBoxFill
);
const handleChangeShouldShowBoundingBoxFill = () => {
dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill));
};
return (
<IAICheckbox
label="Darken Outside Box"
isChecked={shouldShowBoundingBoxFill}
onChange={handleChangeShouldShowBoundingBoxFill}
styleClass="inpainting-bounding-box-darken"
/>
);
}

View File

@@ -0,0 +1,128 @@
import React from 'react';
import IAISlider from '../../../../../common/components/IAISlider';
import IAINumberInput from '../../../../../common/components/IAINumberInput';
import IAIIconButton from '../../../../../common/components/IAIIconButton';
import { BiReset } from 'react-icons/bi';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../../app/store';
import { createSelector } from '@reduxjs/toolkit';
import {
InpaintingState,
setBoundingBoxDimensions,
} from '../../../../tabs/Inpainting/inpaintingSlice';
import { roundDownToMultiple } from '../../../../../common/util/roundDownToMultiple';
import _ from 'lodash';
const boundingBoxDimensionsSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const { canvasDimensions, boundingBoxDimensions, shouldLockBoundingBox } =
inpainting;
return {
canvasDimensions,
boundingBoxDimensions,
shouldLockBoundingBox,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
type BoundingBoxDimensionSlidersType = {
dimension: 'width' | 'height';
};
export default function BoundingBoxDimensionSlider(
props: BoundingBoxDimensionSlidersType
) {
const { dimension } = props;
const dispatch = useAppDispatch();
const { shouldLockBoundingBox, canvasDimensions, boundingBoxDimensions } =
useAppSelector(boundingBoxDimensionsSelector);
const canvasDimension = canvasDimensions[dimension];
const boundingBoxDimension = boundingBoxDimensions[dimension];
const handleBoundingBoxDimension = (v: number) => {
if (dimension == 'width') {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
width: Math.floor(v),
})
);
}
if (dimension == 'height') {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
height: Math.floor(v),
})
);
}
};
const handleResetDimension = () => {
if (dimension == 'width') {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
width: Math.floor(canvasDimension),
})
);
}
if (dimension == 'height') {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
height: Math.floor(canvasDimension),
})
);
}
};
return (
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
<IAISlider
isDisabled={shouldLockBoundingBox}
label="Box H"
min={64}
max={roundDownToMultiple(canvasDimension, 64)}
step={64}
value={boundingBoxDimension}
onChange={handleBoundingBoxDimension}
width={'5rem'}
/>
<IAINumberInput
isDisabled={shouldLockBoundingBox}
value={boundingBoxDimension}
onChange={handleBoundingBoxDimension}
min={64}
max={roundDownToMultiple(canvasDimension, 64)}
step={64}
padding="0"
width={'5rem'}
/>
<IAIIconButton
size={'sm'}
aria-label={'Reset Height'}
tooltip={'Reset Height'}
onClick={handleResetDimension}
icon={<BiReset />}
styleClass="inpainting-bounding-box-reset-icon-btn"
isDisabled={
shouldLockBoundingBox || canvasDimension === boundingBoxDimension
}
/>
</div>
);
}

View File

@@ -0,0 +1,27 @@
import React from 'react';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../../app/store';
import IAICheckbox from '../../../../../common/components/IAICheckbox';
import { setShouldLockBoundingBox } from '../../../../tabs/Inpainting/inpaintingSlice';
export default function BoundingBoxLock() {
const shouldLockBoundingBox = useAppSelector(
(state: RootState) => state.inpainting.shouldLockBoundingBox
);
const dispatch = useAppDispatch();
const handleChangeShouldLockBoundingBox = () => {
dispatch(setShouldLockBoundingBox(!shouldLockBoundingBox));
};
return (
<IAICheckbox
label="Lock Bounding Box"
isChecked={shouldLockBoundingBox}
onChange={handleChangeShouldLockBoundingBox}
styleClass="inpainting-bounding-box-darken"
/>
);
}

View File

@@ -0,0 +1,26 @@
import { Flex } from '@chakra-ui/react';
import BoundingBoxDarkenOutside from './BoundingBoxDarkenOutside';
import BoundingBoxDimensionSlider from './BoundingBoxDimensionSlider';
import BoundingBoxLock from './BoundingBoxLock';
import BoundingBoxVisibility from './BoundingBoxVisibility';
const BoundingBoxSettings = () => {
return (
<div className="inpainting-bounding-box-settings">
<div className="inpainting-bounding-box-header">
<p>Inpaint Box</p>
<BoundingBoxVisibility />
</div>
<div className="inpainting-bounding-box-settings-items">
<BoundingBoxDimensionSlider dimension="width" />
<BoundingBoxDimensionSlider dimension="height" />
<Flex alignItems={'center'} justifyContent={'space-between'}>
<BoundingBoxDarkenOutside />
<BoundingBoxLock />
</Flex>
</div>
</div>
);
};
export default BoundingBoxSettings;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { BiHide, BiShow } from 'react-icons/bi';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../../app/store';
import IAIIconButton from '../../../../../common/components/IAIIconButton';
import { setShouldShowBoundingBox } from '../../../../tabs/Inpainting/inpaintingSlice';
export default function BoundingBoxVisibility() {
const shouldShowBoundingBox = useAppSelector(
(state: RootState) => state.inpainting.shouldShowBoundingBox
);
const dispatch = useAppDispatch();
const handleShowBoundingBox = () =>
dispatch(setShouldShowBoundingBox(!shouldShowBoundingBox));
return (
<IAIIconButton
aria-label="Toggle Bounding Box Visibility"
icon={shouldShowBoundingBox ? <BiShow size={22} /> : <BiHide size={22} />}
onClick={handleShowBoundingBox}
background={'none'}
padding={0}
/>
);
}

View File

@@ -0,0 +1,56 @@
import { useToast } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../app/store';
import IAIButton from '../../../../common/components/IAIButton';
import {
InpaintingState,
setClearBrushHistory,
} from '../../../tabs/Inpainting/inpaintingSlice';
import _ from 'lodash';
const clearBrushHistorySelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const { pastLines, futureLines } = inpainting;
return {
mayClearBrushHistory:
futureLines.length > 0 || pastLines.length > 0 ? false : true,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function ClearBrushHistory() {
const dispatch = useAppDispatch();
const toast = useToast();
const { mayClearBrushHistory } = useAppSelector(clearBrushHistorySelector);
const handleClearBrushHistory = () => {
dispatch(setClearBrushHistory());
toast({
title: 'Brush Stroke History Cleared',
status: 'success',
duration: 2500,
isClosable: true,
});
};
return (
<IAIButton
onClick={handleClearBrushHistory}
tooltip="Clears brush stroke history"
disabled={mayClearBrushHistory}
styleClass="inpainting-options-btn"
>
Clear Brush History
</IAIButton>
);
}

View File

@@ -0,0 +1,70 @@
import React, { ChangeEvent } from 'react';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../app/store';
import IAINumberInput from '../../../../common/components/IAINumberInput';
import _ from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
import {
InpaintingState,
setInpaintReplace,
setShouldUseInpaintReplace,
} from '../../../tabs/Inpainting/inpaintingSlice';
import IAISwitch from '../../../../common/components/IAISwitch';
const inpaintReplaceSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const { inpaintReplace, shouldUseInpaintReplace } = inpainting;
return {
inpaintReplace,
shouldUseInpaintReplace,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function InpaintReplace() {
const { inpaintReplace, shouldUseInpaintReplace } = useAppSelector(
inpaintReplaceSelector
);
const dispatch = useAppDispatch();
return (
<div
style={{
display: 'flex',
alignItems: 'center',
padding: '0 1rem 0 0.2rem',
}}
>
<IAINumberInput
label="Inpaint Replace"
value={inpaintReplace}
min={0}
max={1.0}
step={0.05}
width={'auto'}
formControlProps={{ style: { paddingRight: '1rem' } }}
isInteger={false}
isDisabled={!shouldUseInpaintReplace}
onChange={(v: number) => {
dispatch(setInpaintReplace(v));
}}
/>
<IAISwitch
isChecked={shouldUseInpaintReplace}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseInpaintReplace(e.target.checked))
}
/>
</div>
);
}

View File

@@ -1,96 +1,13 @@
import { useToast } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import React, { ChangeEvent } from 'react';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../app/store';
import IAIButton from '../../../../common/components/IAIButton';
import {
InpaintingState,
setClearBrushHistory,
setInpaintReplace,
setShouldUseInpaintReplace,
} from '../../../tabs/Inpainting/inpaintingSlice';
import BoundingBoxSettings from './BoundingBoxSettings';
import _ from 'lodash';
import IAINumberInput from '../../../../common/components/IAINumberInput';
import IAISwitch from '../../../../common/components/IAISwitch';
const inpaintingSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const { pastLines, futureLines, inpaintReplace, shouldUseInpaintReplace } =
inpainting;
return {
pastLines,
futureLines,
inpaintReplace,
shouldUseInpaintReplace,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
import BoundingBoxSettings from './BoundingBoxSettings/BoundingBoxSettings';
import InpaintReplace from './InpaintReplace';
import ClearBrushHistory from './ClearBrushHistory';
export default function InpaintingSettings() {
const dispatch = useAppDispatch();
const toast = useToast();
const { pastLines, futureLines, inpaintReplace, shouldUseInpaintReplace } =
useAppSelector(inpaintingSelector);
const handleClearBrushHistory = () => {
dispatch(setClearBrushHistory());
toast({
title: 'Brush Stroke History Cleared',
status: 'success',
duration: 2500,
isClosable: true,
});
};
return (
<>
<div
style={{
display: 'flex',
alignItems: 'center',
padding: '0 1rem 0 0.2rem',
}}
>
<IAINumberInput
label="Inpaint Replace"
value={inpaintReplace}
min={0}
max={1.0}
step={0.05}
width={'auto'}
formControlProps={{ style: { paddingRight: '1rem' } }}
isInteger={false}
isDisabled={!shouldUseInpaintReplace}
onChange={(v: number) => {
dispatch(setInpaintReplace(v));
}}
/>
<IAISwitch
isChecked={shouldUseInpaintReplace}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseInpaintReplace(e.target.checked))
}
/>
</div>
<InpaintReplace />
<BoundingBoxSettings />
<IAIButton
label="Clear Brush History"
onClick={handleClearBrushHistory}
tooltip="Clears brush stroke history"
disabled={futureLines.length > 0 || pastLines.length > 0 ? false : true}
styleClass="inpainting-options-btn"
/>
<ClearBrushHistory />
</>
);
}

View File

@@ -7,7 +7,7 @@ import { setHeight } from '../optionsSlice';
import { fontSize } from './MainOptions';
export default function MainHeight() {
const { height } = useAppSelector((state: RootState) => state.options);
const height = useAppSelector((state: RootState) => state.options.height);
const activeTabName = useAppSelector(activeTabNameSelector);
const dispatch = useAppDispatch();

View File

@@ -1,13 +1,33 @@
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import React from 'react';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
import IAINumberInput from '../../../common/components/IAINumberInput';
import { setIterations } from '../optionsSlice';
import { mayGenerateMultipleImagesSelector } from '../optionsSelectors';
import { OptionsState, setIterations } from '../optionsSlice';
import { fontSize, inputWidth } from './MainOptions';
const mainIterationsSelector = createSelector(
[(state: RootState) => state.options, mayGenerateMultipleImagesSelector],
(options: OptionsState, mayGenerateMultipleImages) => {
const { iterations } = options;
return {
iterations,
mayGenerateMultipleImages,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function MainIterations() {
const dispatch = useAppDispatch();
const iterations = useAppSelector(
(state: RootState) => state.options.iterations
const { iterations, mayGenerateMultipleImages } = useAppSelector(
mainIterationsSelector
);
const handleChangeIterations = (v: number) => dispatch(setIterations(v));
@@ -18,6 +38,7 @@ export default function MainIterations() {
step={1}
min={1}
max={9999}
isDisabled={!mayGenerateMultipleImages}
onChange={handleChangeIterations}
value={iterations}
width={inputWidth}

View File

@@ -19,6 +19,7 @@
.main-option-block {
border-radius: 0.5rem;
display: grid !important;
grid-template-columns: auto !important;
row-gap: 0.4rem;

View File

@@ -2,14 +2,14 @@ import React, { ChangeEvent } from 'react';
import { WIDTHS } from '../../../app/constants';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
import IAISelect from '../../../common/components/IAISelect';
import { tabMap } from '../../tabs/InvokeTabs';
import { activeTabNameSelector } from '../optionsSelectors';
import { setWidth } from '../optionsSlice';
import { fontSize } from './MainOptions';
export default function MainWidth() {
const { width, activeTab } = useAppSelector(
(state: RootState) => state.options
);
const width = useAppSelector((state: RootState) => state.options.width);
const activeTabName = useAppSelector(activeTabNameSelector);
const dispatch = useAppDispatch();
const handleChangeWidth = (e: ChangeEvent<HTMLSelectElement>) =>
@@ -17,7 +17,7 @@ export default function MainWidth() {
return (
<IAISelect
isDisabled={tabMap[activeTab] === 'inpainting'}
isDisabled={activeTabName === 'inpainting'}
label="Width"
value={width}
flexGrow={1}

View File

@@ -1,12 +1,13 @@
import { MdCancel } from 'react-icons/md';
import { cancelProcessing } from '../../../app/socketio/actions';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
import IAIIconButton from '../../../common/components/IAIIconButton';
import IAIIconButton, {
IAIIconButtonProps,
} from '../../../common/components/IAIIconButton';
import { useHotkeys } from 'react-hotkeys-hook';
import { createSelector } from '@reduxjs/toolkit';
import { SystemState } from '../../system/systemSlice';
import _ from 'lodash';
import { IAIButtonProps } from '../../../common/components/IAIButton';
const cancelButtonSelector = createSelector(
(state: RootState) => state.system,
@@ -24,7 +25,9 @@ const cancelButtonSelector = createSelector(
}
);
export default function CancelButton(props: Omit<IAIButtonProps, 'label'>) {
export default function CancelButton(
props: Omit<IAIIconButtonProps, 'aria-label'>
) {
const { ...rest } = props;
const dispatch = useAppDispatch();
const { isProcessing, isConnected, isCancelable } =

View File

@@ -1,3 +1,4 @@
import { ListItem, UnorderedList } from '@chakra-ui/react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaPlay } from 'react-icons/fa';
import { readinessSelector } from '../../../app/selectors/readinessSelector';
@@ -6,17 +7,21 @@ import { useAppDispatch, useAppSelector } from '../../../app/store';
import IAIButton, {
IAIButtonProps,
} from '../../../common/components/IAIButton';
import IAIIconButton from '../../../common/components/IAIIconButton';
import IAIIconButton, {
IAIIconButtonProps,
} from '../../../common/components/IAIIconButton';
import IAIPopover from '../../../common/components/IAIPopover';
import { activeTabNameSelector } from '../optionsSelectors';
interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
interface InvokeButton
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
iconButton?: boolean;
}
export default function InvokeButton(props: InvokeButton) {
const { iconButton = false, ...rest } = props;
const dispatch = useAppDispatch();
const isReady = useAppSelector(readinessSelector);
const { isReady, reasonsWhyNotReady } = useAppSelector(readinessSelector);
const activeTabName = useAppSelector(activeTabNameSelector);
const handleClickGenerate = () => {
@@ -33,27 +38,62 @@ export default function InvokeButton(props: InvokeButton) {
[isReady, activeTabName]
);
return iconButton ? (
<IAIIconButton
aria-label="Invoke"
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn invoke"
tooltip="Invoke"
tooltipPlacement="bottom"
{...rest}
/>
) : (
<IAIButton
label="Invoke"
aria-label="Invoke"
type="submit"
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn"
{...rest}
/>
const buttonComponent = (
<div style={{ flexGrow: 4 }}>
{iconButton ? (
<IAIIconButton
aria-label="Invoke"
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn invoke"
tooltip="Invoke"
tooltipProps={{ placement: 'bottom' }}
{...rest}
/>
) : (
<IAIButton
aria-label="Invoke"
type="submit"
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn"
{...rest}
>
Invoke
</IAIButton>
)}
</div>
);
return isReady ? (
buttonComponent
) : (
<IAIPopover trigger="hover" triggerComponent={buttonComponent}>
{reasonsWhyNotReady && (
<UnorderedList>
{reasonsWhyNotReady.map((reason, i) => (
<ListItem key={i}>{reason}</ListItem>
))}
</UnorderedList>
)}
</IAIPopover>
);
// return isReady ? (
// buttonComponent
// ) : (
// <IAIPopover trigger="hover" triggerComponent={buttonComponent}>
// {reasonsWhyNotReady ? (
// <UnorderedList>
// {reasonsWhyNotReady.map((reason, i) => (
// <ListItem key={i}>{reason}</ListItem>
// ))}
// </UnorderedList>
// ) : (
// 'test'
// )}
// </IAIPopover>
// );
}

View File

@@ -15,9 +15,11 @@ const LoopbackButton = () => {
return (
<IAIIconButton
aria-label="Loopback"
tooltip="Loopback"
data-selected={shouldLoopback}
aria-label="Toggle Loopback"
tooltip="Toggle Loopback"
styleClass="loopback-btn"
asCheckbox={true}
isChecked={shouldLoopback}
icon={<FaRecycle />}
onClick={() => {
dispatch(setShouldLoopback(!shouldLoopback));

View File

@@ -7,6 +7,7 @@
.invoke-btn {
flex-grow: 1;
width: 100%;
svg {
width: 18px !important;
height: 18px !important;
@@ -25,3 +26,34 @@
// $btn-width: 3rem
);
}
.loopback-btn {
&[data-as-checkbox='true'] {
background-color: var(--btn-grey);
border: 3px solid var(--btn-grey);
svg {
fill: var(--text-color);
}
&:hover {
background-color: var(--btn-grey);
border-color: var(--btn-checkbox-border-hover);
svg {
fill: var(--text-color);
}
}
&[data-selected='true'] {
border-color: var(--accent-color);
background-color: var(--btn-grey);
svg {
fill: var(--text-color);
}
&:hover {
border-color: var(--accent-color);
background-color: var(--btn-grey);
svg {
fill: var(--text-color);
}
}
}
}
}

View File

@@ -31,7 +31,7 @@ const promptInputSelector = createSelector(
const PromptInput = () => {
const dispatch = useAppDispatch();
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
const isReady = useAppSelector(readinessSelector);
const { isReady } = useAppSelector(readinessSelector);
const promptRef = useRef<HTMLTextAreaElement>(null);

View File

@@ -13,3 +13,17 @@ export const activeTabNameSelector = createSelector(
},
}
);
export const mayGenerateMultipleImagesSelector = createSelector(
(state: RootState) => state.options,
(options: OptionsState) => {
const { shouldRandomizeSeed, shouldGenerateVariations } = options;
return shouldRandomizeSeed || shouldGenerateVariations;
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);

View File

@@ -1,10 +1,3 @@
.console-resizable {
display: flex;
position: fixed;
left: 0;
bottom: 0;
}
.console {
width: 100vw;
display: flex;
@@ -48,7 +41,7 @@
position: fixed !important;
left: 0.5rem;
bottom: 0.5rem;
z-index: 21;
z-index: 10000;
&:hover {
background: var(--console-icon-button-bg-color-hover) !important;
@@ -67,7 +60,7 @@
position: fixed !important;
left: 0.5rem;
bottom: 3rem;
z-index: 21;
z-index: 10000;
&:hover {
background: var(--console-icon-button-bg-color-hover) !important;

View File

@@ -75,6 +75,10 @@ const Console = () => {
[shouldShowLogViewer]
);
useHotkeys('esc', () => {
dispatch(setShouldShowLogViewer(false));
});
const handleOnScroll = () => {
if (!viewerRef.current) return;
if (
@@ -99,7 +103,7 @@ const Console = () => {
position: 'fixed',
left: 0,
bottom: 0,
zIndex: 20,
zIndex: 9999,
}}
maxHeight={'90vh'}
>

View File

@@ -73,15 +73,20 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
const generalHotkeys = [
{
title: 'Set Parameters',
desc: 'Use all parameters of the current image',
hotkey: 'A',
title: 'Set Prompt',
desc: 'Use the prompt of the current image',
hotkey: 'P',
},
{
title: 'Set Seed',
desc: 'Use the seed of the current image',
hotkey: 'S',
},
{
title: 'Set Parameters',
desc: 'Use all parameters of the current image',
hotkey: 'A',
},
{ title: 'Restore Faces', desc: 'Restore the current image', hotkey: 'R' },
{ title: 'Upscale', desc: 'Upscale the current image', hotkey: 'U' },
{
@@ -95,6 +100,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
hotkey: 'Shift+I',
},
{ title: 'Delete Image', desc: 'Delete the current image', hotkey: 'Del' },
{ title: 'Close Panels', desc: 'Closes open panels', hotkey: 'Esc' },
];
const galleryHotkeys = [
@@ -194,12 +200,12 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
{
title: 'Lock Bounding Box',
desc: 'Locks the bounding box',
hotkey: 'M',
hotkey: 'Shift+Q',
},
{
title: 'Quick Toggle Lock Bounding Box',
desc: 'Hold to toggle locking the bounding box',
hotkey: 'Space',
hotkey: 'Q',
},
{
title: 'Expand Inpainting Area',

View File

@@ -1,24 +1,37 @@
.model-list {
.chakra-accordion {
display: grid;
row-gap: 0.5rem;
}
// .chakra-accordion {
// display: grid;
// row-gap: 0.5rem;
// }
.chakra-accordion__item {
border: none;
border-radius: 0.3rem;
background-color: var(--tab-hover-color);
}
// .chakra-accordion__item {
// border: none;
// }
// button {
// border-radius: 0.3rem !important;
// &[aria-expanded='true'] {
// // background-color: var(--tab-hover-color);
// border-radius: 0.3rem;
// }
// }
.model-list-accordion {
outline: none;
padding: 0.25rem;
button {
border-radius: 0.3rem !important;
&[aria-expanded='true'] {
background-color: var(--tab-hover-color);
border-radius: 0.3rem;
padding: 0;
margin: 0;
&:hover {
background-color: unset;
}
}
div {
border: none !important;
}
.model-list-button {
display: flex;
flex-direction: row;
@@ -64,6 +77,9 @@
}
}
.model-list-item-load-btn {
button {
padding: 0.5rem;
}
}
}
}

View File

@@ -73,31 +73,33 @@ const ModelList = () => {
const { models } = useAppSelector(modelListSelector);
return (
<div className="model-list">
<Accordion allowToggle>
<AccordionItem>
<AccordionButton>
<div className="model-list-button">
<h2>Models</h2>
<AccordionIcon />
</div>
</AccordionButton>
<Accordion
allowToggle
className="model-list-accordion"
variant={'unstyled'}
>
<AccordionItem>
<AccordionButton>
<div className="model-list-button">
<h2>Models</h2>
<AccordionIcon />
</div>
</AccordionButton>
<AccordionPanel>
<div className="model-list-list">
{models.map((model, i) => (
<ModelListItem
key={i}
name={model.name}
status={model.status}
description={model.description}
/>
))}
</div>
</AccordionPanel>
</AccordionItem>
</Accordion>
</div>
<AccordionPanel>
<div className="model-list-list">
{models.map((model, i) => (
<ModelListItem
key={i}
name={model.name}
status={model.status}
description={model.description}
/>
))}
</div>
</AccordionPanel>
</AccordionItem>
</Accordion>
);
};

View File

@@ -14,29 +14,34 @@ import {
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import _, { isEqual } from 'lodash';
import { cloneElement, ReactElement } from 'react';
import { RootState, useAppSelector } from '../../../app/store';
import { ChangeEvent, cloneElement, ReactElement } from 'react';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
import { persistor } from '../../../main';
import {
InProgressImageType,
setSaveIntermediatesInterval,
setShouldConfirmOnDelete,
setShouldDisplayGuides,
setShouldDisplayInProgress,
setShouldDisplayInProgressType,
SystemState,
} from '../systemSlice';
import ModelList from './ModelList';
import SettingsModalItem from './SettingsModalItem';
import { IN_PROGRESS_IMAGE_TYPES } from '../../../app/constants';
import IAISwitch from '../../../common/components/IAISwitch';
import IAISelect from '../../../common/components/IAISelect';
import IAINumberInput from '../../../common/components/IAINumberInput';
const systemSelector = createSelector(
(state: RootState) => state.system,
(system: SystemState) => {
const {
shouldDisplayInProgress,
shouldDisplayInProgressType,
shouldConfirmOnDelete,
shouldDisplayGuides,
model_list,
} = system;
return {
shouldDisplayInProgress,
shouldDisplayInProgressType,
shouldConfirmOnDelete,
shouldDisplayGuides,
models: _.map(model_list, (_model, key) => key),
@@ -59,6 +64,14 @@ type SettingsModalProps = {
* Secondary post-reset modal is included here.
*/
const SettingsModal = ({ children }: SettingsModalProps) => {
const dispatch = useAppDispatch();
const saveIntermediatesInterval = useAppSelector(
(state: RootState) => state.system.saveIntermediatesInterval
);
const steps = useAppSelector((state: RootState) => state.options.steps);
const {
isOpen: isSettingsModalOpen,
onOpen: onSettingsModalOpen,
@@ -72,7 +85,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
} = useDisclosure();
const {
shouldDisplayInProgress,
shouldDisplayInProgressType,
shouldConfirmOnDelete,
shouldDisplayGuides,
} = useAppSelector(systemSelector);
@@ -88,6 +101,12 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
});
};
const handleChangeIntermediateSteps = (value: number) => {
if (value > steps) value = steps;
if (value < 1) value = 1;
dispatch(setSaveIntermediatesInterval(value));
};
return (
<>
{cloneElement(children, {
@@ -100,29 +119,62 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
<ModalHeader className="settings-modal-header">Settings</ModalHeader>
<ModalCloseButton />
<ModalBody className="settings-modal-content">
<ModelList />
<div className="settings-modal-items">
<SettingsModalItem
settingTitle="Display In-Progress Images (slower)"
isChecked={shouldDisplayInProgress}
dispatcher={setShouldDisplayInProgress}
/>
<SettingsModalItem
settingTitle="Confirm on Delete"
<div className="settings-modal-item">
<ModelList />
</div>
<div
className="settings-modal-item"
style={{ gridAutoFlow: 'row', rowGap: '0.5rem' }}
>
<IAISelect
label={'Display In-Progress Images'}
validValues={IN_PROGRESS_IMAGE_TYPES}
value={shouldDisplayInProgressType}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
dispatch(
setShouldDisplayInProgressType(
e.target.value as InProgressImageType
)
)
}
/>
{shouldDisplayInProgressType === 'full-res' && (
<IAINumberInput
label="Save images every n steps"
min={1}
max={steps}
step={1}
onChange={handleChangeIntermediateSteps}
value={saveIntermediatesInterval}
width="auto"
textAlign="center"
/>
)}
</div>
<IAISwitch
styleClass="settings-modal-item"
label={'Confirm on Delete'}
isChecked={shouldConfirmOnDelete}
dispatcher={setShouldConfirmOnDelete}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldConfirmOnDelete(e.target.checked))
}
/>
<SettingsModalItem
settingTitle="Display Help Icons"
<IAISwitch
styleClass="settings-modal-item"
label={'Display Help Icons'}
isChecked={shouldDisplayGuides}
dispatcher={setShouldDisplayGuides}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldDisplayGuides(e.target.checked))
}
/>
</div>
<div className="settings-modal-reset">
<Heading size={'md'}>Reset Web UI</Heading>
<Button colorScheme="red" onClick={handleClickResetWebUI}>
Reset Web UI
</Button>
<Text>
Resetting the web UI only resets the browser's local cache of
your images and remembered settings. It does not delete any
@@ -133,9 +185,6 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
isn't working, please try resetting before submitting an issue
on GitHub.
</Text>
<Button colorScheme="red" onClick={handleClickResetWebUI}>
Reset Web UI
</Button>
</div>
</ModalBody>

View File

@@ -1,7 +1,8 @@
import { useAppDispatch } from '../../../app/store';
import IAISelect from '../../../common/components/IAISelect';
import IAISwitch from '../../../common/components/IAISwitch';
export default function SettingsModalItem({
export function SettingsModalItem({
settingTitle,
isChecked,
dispatcher,
@@ -20,3 +21,30 @@ export default function SettingsModalItem({
/>
);
}
export function SettingsModalSelectItem({
settingTitle,
validValues,
defaultValue,
dispatcher,
}: {
settingTitle: string;
validValues:
Array<number | string>
| Array<{ key: string; value: string | number }>;
defaultValue: string;
dispatcher: any;
}) {
const dispatch = useAppDispatch();
return (
<IAISelect
styleClass="settings-modal-item"
label={settingTitle}
validValues={validValues}
defaultValue={defaultValue}
onChange={(e) => dispatch(dispatcher(e.target.value))}
/>
);
}

View File

@@ -1,11 +1,19 @@
import { IconButton, Link, Tooltip, useColorMode } from '@chakra-ui/react';
import { Link, useColorMode } from '@chakra-ui/react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaSun, FaMoon, FaGithub, FaDiscord } from 'react-icons/fa';
import { MdHelp, MdKeyboard, MdSettings } from 'react-icons/md';
import {
FaSun,
FaMoon,
FaGithub,
FaDiscord,
FaBug,
FaKeyboard,
FaWrench,
} from 'react-icons/fa';
import InvokeAILogo from '../../assets/images/logo.png';
import IAIIconButton from '../../common/components/IAIIconButton';
import HotkeysModal from './HotkeysModal/HotkeysModal';
@@ -26,11 +34,6 @@ const SiteHeader = () => {
[colorMode, toggleColorMode]
);
const colorModeIcon = colorMode == 'light' ? <FaMoon /> : <FaSun />;
// Make FaMoon and FaSun icon apparent size consistent
const colorModeIconFontSize = colorMode == 'light' ? 18 : 20;
return (
<div className="site-header">
<div className="site-header-left-side">
@@ -44,78 +47,79 @@ const SiteHeader = () => {
<StatusIndicator />
<HotkeysModal>
<IconButton
<IAIIconButton
aria-label="Hotkeys"
variant="link"
fontSize={24}
tooltip="Hotkeys"
size={'sm'}
icon={<MdKeyboard />}
variant="link"
data-variant="link"
fontSize={20}
icon={<FaKeyboard />}
/>
</HotkeysModal>
<Tooltip hasArrow label="Theme" placement={'bottom'}>
<IconButton
aria-label="Toggle Dark Mode"
onClick={toggleColorMode}
variant="link"
size={'sm'}
fontSize={colorModeIconFontSize}
icon={colorModeIcon}
/>
</Tooltip>
<IAIIconButton
aria-label="Toggle Dark Mode"
tooltip="Dark Mode"
onClick={toggleColorMode}
variant="link"
data-variant="link"
fontSize={20}
size={'sm'}
icon={colorMode === 'light' ? <FaMoon /> : <FaSun />}
/>
<Tooltip hasArrow label="Report Bug" placement={'bottom'}>
<IconButton
aria-label="Link to Github Issues"
variant="link"
fontSize={23}
size={'sm'}
icon={
<Link
isExternal
href="http://github.com/invoke-ai/InvokeAI/issues"
>
<MdHelp />
</Link>
}
/>
</Tooltip>
<IAIIconButton
aria-label="Report Bug"
tooltip="Report Bug"
variant="link"
data-variant="link"
fontSize={20}
size={'sm'}
icon={
<Link isExternal href="http://github.com/invoke-ai/InvokeAI/issues">
<FaBug />
</Link>
}
/>
<Tooltip hasArrow label="Github" placement={'bottom'}>
<IconButton
aria-label="Link to Github Repo"
variant="link"
fontSize={20}
size={'sm'}
icon={
<Link isExternal href="http://github.com/invoke-ai/InvokeAI">
<FaGithub />
</Link>
}
/>
</Tooltip>
<IAIIconButton
aria-label="Link to Github Repo"
tooltip="Github"
variant="link"
data-variant="link"
fontSize={20}
size={'sm'}
icon={
<Link isExternal href="http://github.com/invoke-ai/InvokeAI">
<FaGithub />
</Link>
}
/>
<Tooltip hasArrow label="Discord" placement={'bottom'}>
<IconButton
aria-label="Link to Discord Server"
variant="link"
fontSize={20}
size={'sm'}
icon={
<Link isExternal href="https://discord.gg/ZmtBAhwWhy">
<FaDiscord />
</Link>
}
/>
</Tooltip>
<IAIIconButton
aria-label="Link to Discord Server"
tooltip="Discord"
variant="link"
data-variant="link"
fontSize={20}
size={'sm'}
icon={
<Link isExternal href="https://discord.gg/ZmtBAhwWhy">
<FaDiscord />
</Link>
}
/>
<SettingsModal>
<IconButton
<IAIIconButton
aria-label="Settings"
tooltip="Settings"
variant="link"
fontSize={24}
data-variant="link"
fontSize={20}
size={'sm'}
icon={<MdSettings />}
icon={<FaWrench />}
/>
</SettingsModal>
</div>

View File

@@ -15,10 +15,17 @@ export interface Log {
[index: number]: LogEntry;
}
export type ReadinessPayload = {
isReady: boolean;
reasonsWhyNotReady: string[];
};
export type InProgressImageType = 'none' | 'full-res' | 'latents';
export interface SystemState
extends InvokeAI.SystemStatus,
InvokeAI.SystemConfig {
shouldDisplayInProgress: boolean;
shouldDisplayInProgressType: InProgressImageType;
log: Array<LogEntry>;
shouldShowLogViewer: boolean;
isGFPGANAvailable: boolean;
@@ -36,14 +43,15 @@ export interface SystemState
shouldDisplayGuides: boolean;
wasErrorSeen: boolean;
isCancelable: boolean;
saveIntermediatesInterval: number;
}
const initialSystemState = {
const initialSystemState: SystemState = {
isConnected: false,
isProcessing: false,
log: [],
shouldShowLogViewer: false,
shouldDisplayInProgress: false,
shouldDisplayInProgressType: 'latents',
shouldDisplayGuides: true,
isGFPGANAvailable: true,
isESRGANAvailable: true,
@@ -65,16 +73,18 @@ const initialSystemState = {
hasError: false,
wasErrorSeen: true,
isCancelable: true,
saveIntermediatesInterval: 5,
};
const initialState: SystemState = initialSystemState;
export const systemSlice = createSlice({
name: 'system',
initialState,
initialState: initialSystemState,
reducers: {
setShouldDisplayInProgress: (state, action: PayloadAction<boolean>) => {
state.shouldDisplayInProgress = action.payload;
setShouldDisplayInProgressType: (
state,
action: PayloadAction<InProgressImageType>
) => {
state.shouldDisplayInProgressType = action.payload;
},
setIsProcessing: (state, action: PayloadAction<boolean>) => {
state.isProcessing = action.payload;
@@ -178,11 +188,14 @@ export const systemSlice = createSlice({
state.isProcessing = true;
state.currentStatusHasSteps = false;
},
setSaveIntermediatesInterval: (state, action: PayloadAction<number>) => {
state.saveIntermediatesInterval = action.payload;
},
},
});
export const {
setShouldDisplayInProgress,
setShouldDisplayInProgressType,
setIsProcessing,
addLogEntry,
setShouldShowLogViewer,
@@ -200,6 +213,7 @@ export const {
setModelList,
setIsCancelable,
modelChangeRequested,
setSaveIntermediatesInterval,
} = systemSlice.actions;
export default systemSlice.reducer;

View File

@@ -13,7 +13,7 @@ const FloatingGalleryButton = () => {
return (
<IAIIconButton
tooltip="Show Gallery (G)"
tooltipPlacement="top"
tooltipProps={{ placement: 'top' }}
aria-label="Show Gallery"
styleClass="floating-show-hide-button right"
onMouseOver={handleShowGallery}

View File

@@ -36,7 +36,7 @@ const FloatingOptionsPanelButtons = () => {
<div className="show-hide-button-options">
<IAIIconButton
tooltip="Show Options Panel (O)"
tooltipPlacement="top"
tooltipProps={{ placement: 'top' }}
aria-label="Show Options Panel"
onClick={handleShowOptionsPanel}
>

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