Merge branch 'invoke-ai:development' into development
2
.github/workflows/mkdocs-material.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
- name: install requirements
|
- name: install requirements
|
||||||
run: |
|
run: |
|
||||||
python -m \
|
python -m \
|
||||||
pip install -r requirements-mkdocs.txt
|
pip install -r docs/requirements-mkdocs.txt
|
||||||
|
|
||||||
- name: confirm buildability
|
- name: confirm buildability
|
||||||
run: |
|
run: |
|
||||||
|
5
.github/workflows/test-invoke-conda.yml
vendored
@ -13,7 +13,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
matrix:
|
matrix:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
matrix:
|
||||||
stable-diffusion-model:
|
stable-diffusion-model:
|
||||||
- 'stable-diffusion-1.5'
|
- 'stable-diffusion-1.5'
|
||||||
@ -89,7 +88,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
cache-name: cache-${{ matrix.stable-diffusion-model }}
|
cache-name: cache-${{ matrix.stable-diffusion-model }}
|
||||||
with:
|
with:
|
||||||
path: ${{ matrix.stable-diffusion-model-dl-path }}
|
path: ${{ env.INVOKEAI_ROOT }}/${{ matrix.stable-diffusion-model-dl-path }}
|
||||||
key: ${{ env.cache-name }}
|
key: ${{ env.cache-name }}
|
||||||
|
|
||||||
- name: Download ${{ matrix.stable-diffusion-model }}
|
- name: Download ${{ matrix.stable-diffusion-model }}
|
||||||
@ -127,4 +126,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: results_${{ matrix.requirements-file }}_${{ matrix.python-version }}
|
name: results_${{ matrix.requirements-file }}_${{ matrix.python-version }}
|
||||||
path: ${{ env.INVOKEAI_ROOT }}/outputs/img-samples
|
path: ${{ env.INVOKEAI_ROOT }}/outputs
|
||||||
|
11
.github/workflows/test-invoke-pip.yml
vendored
@ -12,7 +12,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
matrix:
|
matrix:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
matrix:
|
||||||
stable-diffusion-model:
|
stable-diffusion-model:
|
||||||
- stable-diffusion-1.5
|
- stable-diffusion-1.5
|
||||||
@ -21,7 +20,7 @@ jobs:
|
|||||||
- requirements-lin-amd.txt
|
- requirements-lin-amd.txt
|
||||||
- requirements-mac-mps-cpu.txt
|
- requirements-mac-mps-cpu.txt
|
||||||
python-version:
|
python-version:
|
||||||
- '3.9'
|
# - '3.9'
|
||||||
- '3.10'
|
- '3.10'
|
||||||
include:
|
include:
|
||||||
- requirements-file: requirements-lin-cuda.txt
|
- requirements-file: requirements-lin-cuda.txt
|
||||||
@ -80,9 +79,7 @@ jobs:
|
|||||||
# run: ${{ env.pythonLocation }}/bin/pip install --upgrade pip setuptools wheel
|
# run: ${{ env.pythonLocation }}/bin/pip install --upgrade pip setuptools wheel
|
||||||
|
|
||||||
- name: install requirements
|
- name: install requirements
|
||||||
run: |
|
run: ${{ env.pythonLocation }}/bin/pip install -r '${{ matrix.requirements-file }}'
|
||||||
${{ env.pythonLocation }}/bin/pip install --upgrade -r '${{ matrix.requirements-file }}'
|
|
||||||
${{ env.pythonLocation }}/bin/pip install -e .
|
|
||||||
|
|
||||||
- name: Use Cached Stable Diffusion Model
|
- name: Use Cached Stable Diffusion Model
|
||||||
id: cache-sd-model
|
id: cache-sd-model
|
||||||
@ -90,7 +87,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
cache-name: cache-${{ matrix.stable-diffusion-model }}
|
cache-name: cache-${{ matrix.stable-diffusion-model }}
|
||||||
with:
|
with:
|
||||||
path: ${{ matrix.stable-diffusion-model-dl-path }}
|
path: ${{ env.INVOKEAI_ROOT }}/${{ matrix.stable-diffusion-model-dl-path }}
|
||||||
key: ${{ env.cache-name }}
|
key: ${{ env.cache-name }}
|
||||||
|
|
||||||
- name: Download ${{ matrix.stable-diffusion-model }}
|
- name: Download ${{ matrix.stable-diffusion-model }}
|
||||||
@ -122,4 +119,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: results_${{ matrix.requirements-file }}_${{ matrix.python-version }}
|
name: results_${{ matrix.requirements-file }}_${{ matrix.python-version }}
|
||||||
path: ${{ env.INVOKEAI_ROOT }}/outputs/img-samples
|
path: ${{ env.INVOKEAI_ROOT }}/outputs
|
||||||
|
4
.gitignore
vendored
@ -194,10 +194,6 @@ checkpoints
|
|||||||
|
|
||||||
# Let the frontend manage its own gitignore
|
# Let the frontend manage its own gitignore
|
||||||
!frontend/*
|
!frontend/*
|
||||||
frontend/apt-get
|
|
||||||
frontend/dist
|
|
||||||
frontend/sudo
|
|
||||||
frontend/update
|
|
||||||
|
|
||||||
# Scratch folder
|
# Scratch folder
|
||||||
.scratch/
|
.scratch/
|
||||||
|
117
backend/modules/get_canvas_generation_mode.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
from PIL import Image, ImageChops
|
||||||
|
from PIL.Image import Image as ImageType
|
||||||
|
from typing import Union, Literal
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/43864101/python-pil-check-if-image-is-transparent
|
||||||
|
def check_for_any_transparency(img: Union[ImageType, str]) -> bool:
|
||||||
|
if type(img) is str:
|
||||||
|
img = Image.open(str)
|
||||||
|
|
||||||
|
if img.info.get("transparency", None) is not None:
|
||||||
|
return True
|
||||||
|
if img.mode == "P":
|
||||||
|
transparent = img.info.get("transparency", -1)
|
||||||
|
for _, index in img.getcolors():
|
||||||
|
if index == transparent:
|
||||||
|
return True
|
||||||
|
elif img.mode == "RGBA":
|
||||||
|
extrema = img.getextrema()
|
||||||
|
if extrema[3][0] < 255:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_canvas_generation_mode(
|
||||||
|
init_img: Union[ImageType, str], init_mask: Union[ImageType, str]
|
||||||
|
) -> Literal["txt2img", "outpainting", "inpainting", "img2img",]:
|
||||||
|
if type(init_img) is str:
|
||||||
|
init_img = Image.open(init_img)
|
||||||
|
|
||||||
|
if type(init_mask) is str:
|
||||||
|
init_mask = Image.open(init_mask)
|
||||||
|
|
||||||
|
init_img = init_img.convert("RGBA")
|
||||||
|
|
||||||
|
# Get alpha from init_img
|
||||||
|
init_img_alpha = init_img.split()[-1]
|
||||||
|
init_img_alpha_mask = init_img_alpha.convert("L")
|
||||||
|
init_img_has_transparency = check_for_any_transparency(init_img)
|
||||||
|
|
||||||
|
if init_img_has_transparency:
|
||||||
|
init_img_is_fully_transparent = (
|
||||||
|
True if init_img_alpha_mask.getbbox() is None else False
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Mask images are white in areas where no change should be made, black where changes
|
||||||
|
should be made.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Fit the mask to init_img's size and convert it to greyscale
|
||||||
|
init_mask = init_mask.resize(init_img.size).convert("L")
|
||||||
|
|
||||||
|
"""
|
||||||
|
PIL.Image.getbbox() returns the bounding box of non-zero areas of the image, so we first
|
||||||
|
invert the mask image so that masked areas are white and other areas black == zero.
|
||||||
|
getbbox() now tells us if the are any masked areas.
|
||||||
|
"""
|
||||||
|
init_mask_bbox = ImageChops.invert(init_mask).getbbox()
|
||||||
|
init_mask_exists = False if init_mask_bbox is None else True
|
||||||
|
|
||||||
|
if init_img_has_transparency:
|
||||||
|
if init_img_is_fully_transparent:
|
||||||
|
return "txt2img"
|
||||||
|
else:
|
||||||
|
return "outpainting"
|
||||||
|
else:
|
||||||
|
if init_mask_exists:
|
||||||
|
return "inpainting"
|
||||||
|
else:
|
||||||
|
return "img2img"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Testing
|
||||||
|
init_img_opaque = "test_images/init-img_opaque.png"
|
||||||
|
init_img_partial_transparency = "test_images/init-img_partial_transparency.png"
|
||||||
|
init_img_full_transparency = "test_images/init-img_full_transparency.png"
|
||||||
|
init_mask_no_mask = "test_images/init-mask_no_mask.png"
|
||||||
|
init_mask_has_mask = "test_images/init-mask_has_mask.png"
|
||||||
|
|
||||||
|
print(
|
||||||
|
"OPAQUE IMAGE, NO MASK, expect img2img, got ",
|
||||||
|
get_canvas_generation_mode(init_img_opaque, init_mask_no_mask),
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"IMAGE WITH TRANSPARENCY, NO MASK, expect outpainting, got ",
|
||||||
|
get_canvas_generation_mode(
|
||||||
|
init_img_partial_transparency, init_mask_no_mask
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"FULLY TRANSPARENT IMAGE NO MASK, expect txt2img, got ",
|
||||||
|
get_canvas_generation_mode(init_img_full_transparency, init_mask_no_mask),
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"OPAQUE IMAGE, WITH MASK, expect inpainting, got ",
|
||||||
|
get_canvas_generation_mode(init_img_opaque, init_mask_has_mask),
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"IMAGE WITH TRANSPARENCY, WITH MASK, expect outpainting, got ",
|
||||||
|
get_canvas_generation_mode(
|
||||||
|
init_img_partial_transparency, init_mask_has_mask
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"FULLY TRANSPARENT IMAGE WITH MASK, expect txt2img, got ",
|
||||||
|
get_canvas_generation_mode(init_img_full_transparency, init_mask_has_mask),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
BIN
backend/modules/test_images/init-img_full_transparency.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
backend/modules/test_images/init-img_opaque.png
Normal file
After Width: | Height: | Size: 292 KiB |
BIN
backend/modules/test_images/init-img_partial_transparency.png
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
backend/modules/test_images/init-mask_has_mask.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
backend/modules/test_images/init-mask_no_mask.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
803
configs/sd-concepts.txt
Normal file
@ -0,0 +1,803 @@
|
|||||||
|
sd-concepts-library/001glitch-core
|
||||||
|
sd-concepts-library/2814-roth
|
||||||
|
sd-concepts-library/3d-female-cyborgs
|
||||||
|
sd-concepts-library/4tnght
|
||||||
|
sd-concepts-library/80s-anime-ai
|
||||||
|
sd-concepts-library/80s-anime-ai-being
|
||||||
|
sd-concepts-library/852style-girl
|
||||||
|
sd-concepts-library/8bit
|
||||||
|
sd-concepts-library/8sconception
|
||||||
|
sd-concepts-library/Aflac-duck
|
||||||
|
sd-concepts-library/Akitsuki
|
||||||
|
sd-concepts-library/Atako
|
||||||
|
sd-concepts-library/Exodus-Styling
|
||||||
|
sd-concepts-library/RINGAO
|
||||||
|
sd-concepts-library/a-female-hero-from-the-legend-of-mir
|
||||||
|
sd-concepts-library/a-hat-kid
|
||||||
|
sd-concepts-library/a-tale-of-two-empires
|
||||||
|
sd-concepts-library/aadhav-face
|
||||||
|
sd-concepts-library/aavegotchi
|
||||||
|
sd-concepts-library/abby-face
|
||||||
|
sd-concepts-library/abstract-concepts
|
||||||
|
sd-concepts-library/accurate-angel
|
||||||
|
sd-concepts-library/agm-style-nao
|
||||||
|
sd-concepts-library/aj-fosik
|
||||||
|
sd-concepts-library/alberto-mielgo
|
||||||
|
sd-concepts-library/alex-portugal
|
||||||
|
sd-concepts-library/alex-thumbnail-object-2000-steps
|
||||||
|
sd-concepts-library/aleyna-tilki
|
||||||
|
sd-concepts-library/alf
|
||||||
|
sd-concepts-library/alicebeta
|
||||||
|
sd-concepts-library/alien-avatar
|
||||||
|
sd-concepts-library/alisa
|
||||||
|
sd-concepts-library/all-rings-albuns
|
||||||
|
sd-concepts-library/altvent
|
||||||
|
sd-concepts-library/altyn-helmet
|
||||||
|
sd-concepts-library/amine
|
||||||
|
sd-concepts-library/amogus
|
||||||
|
sd-concepts-library/anders-zorn
|
||||||
|
sd-concepts-library/angus-mcbride-style
|
||||||
|
sd-concepts-library/animalve3-1500seq
|
||||||
|
sd-concepts-library/anime-background-style
|
||||||
|
sd-concepts-library/anime-background-style-v2
|
||||||
|
sd-concepts-library/anime-boy
|
||||||
|
sd-concepts-library/anime-girl
|
||||||
|
sd-concepts-library/anyXtronXredshift
|
||||||
|
sd-concepts-library/anya-forger
|
||||||
|
sd-concepts-library/apex-wingman
|
||||||
|
sd-concepts-library/apulian-rooster-v0-1
|
||||||
|
sd-concepts-library/arcane-face
|
||||||
|
sd-concepts-library/arcane-style-jv
|
||||||
|
sd-concepts-library/arcimboldo-style
|
||||||
|
sd-concepts-library/armando-reveron-style
|
||||||
|
sd-concepts-library/armor-concept
|
||||||
|
sd-concepts-library/arq-render
|
||||||
|
sd-concepts-library/art-brut
|
||||||
|
sd-concepts-library/arthur1
|
||||||
|
sd-concepts-library/artist-yukiko-kanagai
|
||||||
|
sd-concepts-library/arwijn
|
||||||
|
sd-concepts-library/ashiok
|
||||||
|
sd-concepts-library/at-wolf-boy-object
|
||||||
|
sd-concepts-library/atm-ant
|
||||||
|
sd-concepts-library/atm-ant-2
|
||||||
|
sd-concepts-library/axe-tattoo
|
||||||
|
sd-concepts-library/ayush-spider-spr
|
||||||
|
sd-concepts-library/azura-from-vibrant-venture
|
||||||
|
sd-concepts-library/ba-shiroko
|
||||||
|
sd-concepts-library/babau
|
||||||
|
sd-concepts-library/babs-bunny
|
||||||
|
sd-concepts-library/babushork
|
||||||
|
sd-concepts-library/backrooms
|
||||||
|
sd-concepts-library/bad_Hub_Hugh
|
||||||
|
sd-concepts-library/bada-club
|
||||||
|
sd-concepts-library/baldi
|
||||||
|
sd-concepts-library/baluchitherian
|
||||||
|
sd-concepts-library/bamse
|
||||||
|
sd-concepts-library/bamse-og-kylling
|
||||||
|
sd-concepts-library/bee
|
||||||
|
sd-concepts-library/beholder
|
||||||
|
sd-concepts-library/beldam
|
||||||
|
sd-concepts-library/belen
|
||||||
|
sd-concepts-library/bella-goth
|
||||||
|
sd-concepts-library/belle-delphine
|
||||||
|
sd-concepts-library/bert-muppet
|
||||||
|
sd-concepts-library/better-collage3
|
||||||
|
sd-concepts-library/between2-mt-fade
|
||||||
|
sd-concepts-library/birb-style
|
||||||
|
sd-concepts-library/black-and-white-design
|
||||||
|
sd-concepts-library/black-waifu
|
||||||
|
sd-concepts-library/bloo
|
||||||
|
sd-concepts-library/blue-haired-boy
|
||||||
|
sd-concepts-library/blue-zombie
|
||||||
|
sd-concepts-library/blue-zombiee
|
||||||
|
sd-concepts-library/bluebey
|
||||||
|
sd-concepts-library/bluebey-2
|
||||||
|
sd-concepts-library/bobs-burgers
|
||||||
|
sd-concepts-library/boissonnard
|
||||||
|
sd-concepts-library/bonzi-monkey
|
||||||
|
sd-concepts-library/borderlands
|
||||||
|
sd-concepts-library/bored-ape-textual-inversion
|
||||||
|
sd-concepts-library/boris-anderson
|
||||||
|
sd-concepts-library/bozo-22
|
||||||
|
sd-concepts-library/breakcore
|
||||||
|
sd-concepts-library/brittney-williams-art
|
||||||
|
sd-concepts-library/bruma
|
||||||
|
sd-concepts-library/brunnya
|
||||||
|
sd-concepts-library/buddha-statue
|
||||||
|
sd-concepts-library/bullvbear
|
||||||
|
sd-concepts-library/button-eyes
|
||||||
|
sd-concepts-library/canadian-goose
|
||||||
|
sd-concepts-library/canary-cap
|
||||||
|
sd-concepts-library/cancer_style
|
||||||
|
sd-concepts-library/captain-haddock
|
||||||
|
sd-concepts-library/captainkirb
|
||||||
|
sd-concepts-library/car-toy-rk
|
||||||
|
sd-concepts-library/carasibana
|
||||||
|
sd-concepts-library/carlitos-el-mago
|
||||||
|
sd-concepts-library/carrascharacter
|
||||||
|
sd-concepts-library/cartoona-animals
|
||||||
|
sd-concepts-library/cat-toy
|
||||||
|
sd-concepts-library/centaur
|
||||||
|
sd-concepts-library/cgdonny1
|
||||||
|
sd-concepts-library/cham
|
||||||
|
sd-concepts-library/chandra-nalaar
|
||||||
|
sd-concepts-library/char-con
|
||||||
|
sd-concepts-library/character-pingu
|
||||||
|
sd-concepts-library/cheburashka
|
||||||
|
sd-concepts-library/chen-1
|
||||||
|
sd-concepts-library/child-zombie
|
||||||
|
sd-concepts-library/chillpill
|
||||||
|
sd-concepts-library/chonkfrog
|
||||||
|
sd-concepts-library/chop
|
||||||
|
sd-concepts-library/christo-person
|
||||||
|
sd-concepts-library/chuck-walton
|
||||||
|
sd-concepts-library/chucky
|
||||||
|
sd-concepts-library/chungus-poodl-pet
|
||||||
|
sd-concepts-library/cindlop
|
||||||
|
sd-concepts-library/collage-cutouts
|
||||||
|
sd-concepts-library/collage14
|
||||||
|
sd-concepts-library/collage3
|
||||||
|
sd-concepts-library/collage3-hubcity
|
||||||
|
sd-concepts-library/cologne
|
||||||
|
sd-concepts-library/color-page
|
||||||
|
sd-concepts-library/colossus
|
||||||
|
sd-concepts-library/command-and-conquer-remastered-cameos
|
||||||
|
sd-concepts-library/concept-art
|
||||||
|
sd-concepts-library/conner-fawcett-style
|
||||||
|
sd-concepts-library/conway-pirate
|
||||||
|
sd-concepts-library/coop-himmelblau
|
||||||
|
sd-concepts-library/coraline
|
||||||
|
sd-concepts-library/cornell-box
|
||||||
|
sd-concepts-library/cortana
|
||||||
|
sd-concepts-library/covid-19-rapid-test
|
||||||
|
sd-concepts-library/cow-uwu
|
||||||
|
sd-concepts-library/cowboy
|
||||||
|
sd-concepts-library/crazy-1
|
||||||
|
sd-concepts-library/crazy-2
|
||||||
|
sd-concepts-library/crb-portraits
|
||||||
|
sd-concepts-library/crb-surrealz
|
||||||
|
sd-concepts-library/crbart
|
||||||
|
sd-concepts-library/crested-gecko
|
||||||
|
sd-concepts-library/crinos-form-garou
|
||||||
|
sd-concepts-library/cry-baby-style
|
||||||
|
sd-concepts-library/crybaby-style-2-0
|
||||||
|
sd-concepts-library/csgo-awp-object
|
||||||
|
sd-concepts-library/csgo-awp-texture-map
|
||||||
|
sd-concepts-library/cubex
|
||||||
|
sd-concepts-library/cumbia-peruana
|
||||||
|
sd-concepts-library/cute-bear
|
||||||
|
sd-concepts-library/cute-cat
|
||||||
|
sd-concepts-library/cute-game-style
|
||||||
|
sd-concepts-library/cyberpunk-lucy
|
||||||
|
sd-concepts-library/dabotap
|
||||||
|
sd-concepts-library/dan-mumford
|
||||||
|
sd-concepts-library/dan-seagrave-art-style
|
||||||
|
sd-concepts-library/dark-penguin-pinguinanimations
|
||||||
|
sd-concepts-library/darkpenguinanimatronic
|
||||||
|
sd-concepts-library/darkplane
|
||||||
|
sd-concepts-library/david-firth-artstyle
|
||||||
|
sd-concepts-library/david-martinez-cyberpunk
|
||||||
|
sd-concepts-library/david-martinez-edgerunners
|
||||||
|
sd-concepts-library/david-moreno-architecture
|
||||||
|
sd-concepts-library/daycare-attendant-sun-fnaf
|
||||||
|
sd-concepts-library/ddattender
|
||||||
|
sd-concepts-library/degods
|
||||||
|
sd-concepts-library/degodsheavy
|
||||||
|
sd-concepts-library/depthmap
|
||||||
|
sd-concepts-library/depthmap-style
|
||||||
|
sd-concepts-library/design
|
||||||
|
sd-concepts-library/detectivedinosaur1
|
||||||
|
sd-concepts-library/diaosu-toy
|
||||||
|
sd-concepts-library/dicoo
|
||||||
|
sd-concepts-library/dicoo2
|
||||||
|
sd-concepts-library/dishonored-portrait-styles
|
||||||
|
sd-concepts-library/disquieting-muses
|
||||||
|
sd-concepts-library/ditko
|
||||||
|
sd-concepts-library/dlooak
|
||||||
|
sd-concepts-library/doc
|
||||||
|
sd-concepts-library/doener-red-line-art
|
||||||
|
sd-concepts-library/dog
|
||||||
|
sd-concepts-library/dog-django
|
||||||
|
sd-concepts-library/doge-pound
|
||||||
|
sd-concepts-library/dong-ho
|
||||||
|
sd-concepts-library/dong-ho2
|
||||||
|
sd-concepts-library/doose-s-realistic-art-style
|
||||||
|
sd-concepts-library/dq10-anrushia
|
||||||
|
sd-concepts-library/dr-livesey
|
||||||
|
sd-concepts-library/dr-strange
|
||||||
|
sd-concepts-library/dragonborn
|
||||||
|
sd-concepts-library/dreamcore
|
||||||
|
sd-concepts-library/dreamy-painting
|
||||||
|
sd-concepts-library/drive-scorpion-jacket
|
||||||
|
sd-concepts-library/dsmuses
|
||||||
|
sd-concepts-library/dtv-pkmn
|
||||||
|
sd-concepts-library/dullboy-caricature
|
||||||
|
sd-concepts-library/duranduran
|
||||||
|
sd-concepts-library/durer-style
|
||||||
|
sd-concepts-library/dyoudim-style
|
||||||
|
sd-concepts-library/early-mishima-kurone
|
||||||
|
sd-concepts-library/eastward
|
||||||
|
sd-concepts-library/eddie
|
||||||
|
sd-concepts-library/edgerunners-style
|
||||||
|
sd-concepts-library/edgerunners-style-v2
|
||||||
|
sd-concepts-library/el-salvador-style-style
|
||||||
|
sd-concepts-library/elegant-flower
|
||||||
|
sd-concepts-library/elspeth-tirel
|
||||||
|
sd-concepts-library/eru-chitanda-casual
|
||||||
|
sd-concepts-library/erwin-olaf-style
|
||||||
|
sd-concepts-library/ettblackteapot
|
||||||
|
sd-concepts-library/explosions-cat
|
||||||
|
sd-concepts-library/eye-of-agamotto
|
||||||
|
sd-concepts-library/f-22
|
||||||
|
sd-concepts-library/facadeplace
|
||||||
|
sd-concepts-library/fairy-tale-painting-style
|
||||||
|
sd-concepts-library/fairytale
|
||||||
|
sd-concepts-library/fang-yuan-001
|
||||||
|
sd-concepts-library/faraon-love-shady
|
||||||
|
sd-concepts-library/fasina
|
||||||
|
sd-concepts-library/felps
|
||||||
|
sd-concepts-library/female-kpop-singer
|
||||||
|
sd-concepts-library/fergal-cat
|
||||||
|
sd-concepts-library/filename-2
|
||||||
|
sd-concepts-library/fileteado-porteno
|
||||||
|
sd-concepts-library/final-fantasy-logo
|
||||||
|
sd-concepts-library/fireworks-over-water
|
||||||
|
sd-concepts-library/fish
|
||||||
|
sd-concepts-library/flag-ussr
|
||||||
|
sd-concepts-library/flatic
|
||||||
|
sd-concepts-library/floral
|
||||||
|
sd-concepts-library/fluid-acrylic-jellyfish-creatures-style-of-carl-ingram-art
|
||||||
|
sd-concepts-library/fnf-boyfriend
|
||||||
|
sd-concepts-library/fold-structure
|
||||||
|
sd-concepts-library/fox-purple
|
||||||
|
sd-concepts-library/fractal
|
||||||
|
sd-concepts-library/fractal-flame
|
||||||
|
sd-concepts-library/fractal-temple-style
|
||||||
|
sd-concepts-library/frank-frazetta
|
||||||
|
sd-concepts-library/franz-unterberger
|
||||||
|
sd-concepts-library/freddy-fazbear
|
||||||
|
sd-concepts-library/freefonix-style
|
||||||
|
sd-concepts-library/furrpopasthetic
|
||||||
|
sd-concepts-library/fursona
|
||||||
|
sd-concepts-library/fzk
|
||||||
|
sd-concepts-library/galaxy-explorer
|
||||||
|
sd-concepts-library/ganyu-genshin-impact
|
||||||
|
sd-concepts-library/garcon-the-cat
|
||||||
|
sd-concepts-library/garfield-pizza-plush
|
||||||
|
sd-concepts-library/garfield-pizza-plush-v2
|
||||||
|
sd-concepts-library/gba-fe-class-cards
|
||||||
|
sd-concepts-library/gba-pokemon-sprites
|
||||||
|
sd-concepts-library/geggin
|
||||||
|
sd-concepts-library/ggplot2
|
||||||
|
sd-concepts-library/ghost-style
|
||||||
|
sd-concepts-library/ghostproject-men
|
||||||
|
sd-concepts-library/gibasachan-v0
|
||||||
|
sd-concepts-library/gim
|
||||||
|
sd-concepts-library/gio
|
||||||
|
sd-concepts-library/giygas
|
||||||
|
sd-concepts-library/glass-pipe
|
||||||
|
sd-concepts-library/glass-prism-cube
|
||||||
|
sd-concepts-library/glow-forest
|
||||||
|
sd-concepts-library/goku
|
||||||
|
sd-concepts-library/gram-tops
|
||||||
|
sd-concepts-library/green-blue-shanshui
|
||||||
|
sd-concepts-library/green-tent
|
||||||
|
sd-concepts-library/grifter
|
||||||
|
sd-concepts-library/grisstyle
|
||||||
|
sd-concepts-library/grit-toy
|
||||||
|
sd-concepts-library/gt-color-paint-2
|
||||||
|
sd-concepts-library/gta5-artwork
|
||||||
|
sd-concepts-library/guttestreker
|
||||||
|
sd-concepts-library/gymnastics-leotard-v2
|
||||||
|
sd-concepts-library/half-life-2-dog
|
||||||
|
sd-concepts-library/handstand
|
||||||
|
sd-concepts-library/hanfu-anime-style
|
||||||
|
sd-concepts-library/happy-chaos
|
||||||
|
sd-concepts-library/happy-person12345
|
||||||
|
sd-concepts-library/happy-person12345-assets
|
||||||
|
sd-concepts-library/harley-quinn
|
||||||
|
sd-concepts-library/harmless-ai-1
|
||||||
|
sd-concepts-library/harmless-ai-house-style-1
|
||||||
|
sd-concepts-library/hd-emoji
|
||||||
|
sd-concepts-library/heather
|
||||||
|
sd-concepts-library/henjo-techno-show
|
||||||
|
sd-concepts-library/herge-style
|
||||||
|
sd-concepts-library/hiten-style-nao
|
||||||
|
sd-concepts-library/hitokomoru-style-nao
|
||||||
|
sd-concepts-library/hiyuki-chan
|
||||||
|
sd-concepts-library/hk-bamboo
|
||||||
|
sd-concepts-library/hk-betweenislands
|
||||||
|
sd-concepts-library/hk-bicycle
|
||||||
|
sd-concepts-library/hk-blackandwhite
|
||||||
|
sd-concepts-library/hk-breakfast
|
||||||
|
sd-concepts-library/hk-buses
|
||||||
|
sd-concepts-library/hk-clouds
|
||||||
|
sd-concepts-library/hk-goldbuddha
|
||||||
|
sd-concepts-library/hk-goldenlantern
|
||||||
|
sd-concepts-library/hk-hkisland
|
||||||
|
sd-concepts-library/hk-leaves
|
||||||
|
sd-concepts-library/hk-market
|
||||||
|
sd-concepts-library/hk-oldcamera
|
||||||
|
sd-concepts-library/hk-opencamera
|
||||||
|
sd-concepts-library/hk-peach
|
||||||
|
sd-concepts-library/hk-phonevax
|
||||||
|
sd-concepts-library/hk-streetpeople
|
||||||
|
sd-concepts-library/hk-vintage
|
||||||
|
sd-concepts-library/hoi4
|
||||||
|
sd-concepts-library/hoi4-leaders
|
||||||
|
sd-concepts-library/homestuck-sprite
|
||||||
|
sd-concepts-library/homestuck-troll
|
||||||
|
sd-concepts-library/hours-sentry-fade
|
||||||
|
sd-concepts-library/hours-style
|
||||||
|
sd-concepts-library/hrgiger-drmacabre
|
||||||
|
sd-concepts-library/huang-guang-jian
|
||||||
|
sd-concepts-library/huatli
|
||||||
|
sd-concepts-library/huayecai820-greyscale
|
||||||
|
sd-concepts-library/hub-city
|
||||||
|
sd-concepts-library/hubris-oshri
|
||||||
|
sd-concepts-library/huckleberry
|
||||||
|
sd-concepts-library/hydrasuit
|
||||||
|
sd-concepts-library/i-love-chaos
|
||||||
|
sd-concepts-library/ibere-thenorio
|
||||||
|
sd-concepts-library/ic0n
|
||||||
|
sd-concepts-library/ie-gravestone
|
||||||
|
sd-concepts-library/ikea-fabler
|
||||||
|
sd-concepts-library/illustration-style
|
||||||
|
sd-concepts-library/ilo-kunst
|
||||||
|
sd-concepts-library/ilya-shkipin
|
||||||
|
sd-concepts-library/im-poppy
|
||||||
|
sd-concepts-library/ina-art
|
||||||
|
sd-concepts-library/indian-watercolor-portraits
|
||||||
|
sd-concepts-library/indiana
|
||||||
|
sd-concepts-library/ingmar-bergman
|
||||||
|
sd-concepts-library/insidewhale
|
||||||
|
sd-concepts-library/interchanges
|
||||||
|
sd-concepts-library/inuyama-muneto-style-nao
|
||||||
|
sd-concepts-library/irasutoya
|
||||||
|
sd-concepts-library/iridescent-illustration-style
|
||||||
|
sd-concepts-library/iridescent-photo-style
|
||||||
|
sd-concepts-library/isabell-schulte-pv-pvii-3000steps
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-1-image-style
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-1024px-1500-steps-style
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-12tiles-3000steps-style
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-4-tiles-1-lr-3000-steps-style
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-4-tiles-3-lr-5000-steps-style
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-4tiles-500steps
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-4tiles-6000steps
|
||||||
|
sd-concepts-library/isabell-schulte-pviii-style
|
||||||
|
sd-concepts-library/isometric-tile-test
|
||||||
|
sd-concepts-library/jacqueline-the-unicorn
|
||||||
|
sd-concepts-library/james-web-space-telescope
|
||||||
|
sd-concepts-library/jamie-hewlett-style
|
||||||
|
sd-concepts-library/jamiels
|
||||||
|
sd-concepts-library/jang-sung-rak-style
|
||||||
|
sd-concepts-library/jetsetdreamcastcovers
|
||||||
|
sd-concepts-library/jin-kisaragi
|
||||||
|
sd-concepts-library/jinjoon-lee-they
|
||||||
|
sd-concepts-library/jm-bergling-monogram
|
||||||
|
sd-concepts-library/joe-mad
|
||||||
|
sd-concepts-library/joe-whiteford-art-style
|
||||||
|
sd-concepts-library/joemad
|
||||||
|
sd-concepts-library/john-blanche
|
||||||
|
sd-concepts-library/johnny-silverhand
|
||||||
|
sd-concepts-library/jojo-bizzare-adventure-manga-lineart
|
||||||
|
sd-concepts-library/jos-de-kat
|
||||||
|
sd-concepts-library/junji-ito-artstyle
|
||||||
|
sd-concepts-library/kaleido
|
||||||
|
sd-concepts-library/kaneoya-sachiko
|
||||||
|
sd-concepts-library/kanovt
|
||||||
|
sd-concepts-library/kanv1
|
||||||
|
sd-concepts-library/karan-gloomy
|
||||||
|
sd-concepts-library/karl-s-lzx-1
|
||||||
|
sd-concepts-library/kasumin
|
||||||
|
sd-concepts-library/kawaii-colors
|
||||||
|
sd-concepts-library/kawaii-girl-plus-object
|
||||||
|
sd-concepts-library/kawaii-girl-plus-style
|
||||||
|
sd-concepts-library/kawaii-girl-plus-style-v1-1
|
||||||
|
sd-concepts-library/kay
|
||||||
|
sd-concepts-library/kaya-ghost-assasin
|
||||||
|
sd-concepts-library/ki
|
||||||
|
sd-concepts-library/kinda-sus
|
||||||
|
sd-concepts-library/kings-quest-agd
|
||||||
|
sd-concepts-library/kiora
|
||||||
|
sd-concepts-library/kira-sensei
|
||||||
|
sd-concepts-library/kirby
|
||||||
|
sd-concepts-library/klance
|
||||||
|
sd-concepts-library/kodakvision500t
|
||||||
|
sd-concepts-library/kogatan-shiny
|
||||||
|
sd-concepts-library/kogecha
|
||||||
|
sd-concepts-library/kojima-ayami
|
||||||
|
sd-concepts-library/koko-dog
|
||||||
|
sd-concepts-library/kuvshinov
|
||||||
|
sd-concepts-library/kysa-v-style
|
||||||
|
sd-concepts-library/laala-character
|
||||||
|
sd-concepts-library/larrette
|
||||||
|
sd-concepts-library/lavko
|
||||||
|
sd-concepts-library/lazytown-stephanie
|
||||||
|
sd-concepts-library/ldr
|
||||||
|
sd-concepts-library/ldrs
|
||||||
|
sd-concepts-library/led-toy
|
||||||
|
sd-concepts-library/lego-astronaut
|
||||||
|
sd-concepts-library/leica
|
||||||
|
sd-concepts-library/leif-jones
|
||||||
|
sd-concepts-library/lex
|
||||||
|
sd-concepts-library/liliana
|
||||||
|
sd-concepts-library/liliana-vess
|
||||||
|
sd-concepts-library/liminal-spaces-2-0
|
||||||
|
sd-concepts-library/liminalspaces
|
||||||
|
sd-concepts-library/line-art
|
||||||
|
sd-concepts-library/line-style
|
||||||
|
sd-concepts-library/linnopoke
|
||||||
|
sd-concepts-library/liquid-light
|
||||||
|
sd-concepts-library/liqwid-aquafarmer
|
||||||
|
sd-concepts-library/lizardman
|
||||||
|
sd-concepts-library/loab-character
|
||||||
|
sd-concepts-library/loab-style
|
||||||
|
sd-concepts-library/lofa
|
||||||
|
sd-concepts-library/logo-with-face-on-shield
|
||||||
|
sd-concepts-library/lolo
|
||||||
|
sd-concepts-library/looney-anime
|
||||||
|
sd-concepts-library/lost-rapper
|
||||||
|
sd-concepts-library/lphr-style
|
||||||
|
sd-concepts-library/lucario
|
||||||
|
sd-concepts-library/lucky-luke
|
||||||
|
sd-concepts-library/lugal-ki-en
|
||||||
|
sd-concepts-library/luinv2
|
||||||
|
sd-concepts-library/lula-13
|
||||||
|
sd-concepts-library/lumio
|
||||||
|
sd-concepts-library/lxj-o4
|
||||||
|
sd-concepts-library/m-geo
|
||||||
|
sd-concepts-library/m-geoo
|
||||||
|
sd-concepts-library/madhubani-art
|
||||||
|
sd-concepts-library/mafalda-character
|
||||||
|
sd-concepts-library/magic-pengel
|
||||||
|
sd-concepts-library/malika-favre-art-style
|
||||||
|
sd-concepts-library/manga-style
|
||||||
|
sd-concepts-library/marbling-art
|
||||||
|
sd-concepts-library/margo
|
||||||
|
sd-concepts-library/marty
|
||||||
|
sd-concepts-library/marty6
|
||||||
|
sd-concepts-library/mass
|
||||||
|
sd-concepts-library/masyanya
|
||||||
|
sd-concepts-library/masyunya
|
||||||
|
sd-concepts-library/mate
|
||||||
|
sd-concepts-library/matthew-stone
|
||||||
|
sd-concepts-library/mattvidpro
|
||||||
|
sd-concepts-library/maurice-quentin-de-la-tour-style
|
||||||
|
sd-concepts-library/maus
|
||||||
|
sd-concepts-library/max-foley
|
||||||
|
sd-concepts-library/mayor-richard-irvin
|
||||||
|
sd-concepts-library/mechasoulall
|
||||||
|
sd-concepts-library/medazzaland
|
||||||
|
sd-concepts-library/memnarch-mtg
|
||||||
|
sd-concepts-library/metagabe
|
||||||
|
sd-concepts-library/meyoco
|
||||||
|
sd-concepts-library/meze-audio-elite-headphones
|
||||||
|
sd-concepts-library/midjourney-style
|
||||||
|
sd-concepts-library/mikako-method
|
||||||
|
sd-concepts-library/mikako-methodi2i
|
||||||
|
sd-concepts-library/miko-3-robot
|
||||||
|
sd-concepts-library/milady
|
||||||
|
sd-concepts-library/mildemelwe-style
|
||||||
|
sd-concepts-library/million-live-akane-15k
|
||||||
|
sd-concepts-library/million-live-akane-3k
|
||||||
|
sd-concepts-library/million-live-akane-shifuku-3k
|
||||||
|
sd-concepts-library/million-live-spade-q-object-3k
|
||||||
|
sd-concepts-library/million-live-spade-q-style-3k
|
||||||
|
sd-concepts-library/minecraft-concept-art
|
||||||
|
sd-concepts-library/mishima-kurone
|
||||||
|
sd-concepts-library/mizkif
|
||||||
|
sd-concepts-library/moeb-style
|
||||||
|
sd-concepts-library/moebius
|
||||||
|
sd-concepts-library/mokoko
|
||||||
|
sd-concepts-library/mokoko-seed
|
||||||
|
sd-concepts-library/monster-girl
|
||||||
|
sd-concepts-library/monster-toy
|
||||||
|
sd-concepts-library/monte-novo
|
||||||
|
sd-concepts-library/moo-moo
|
||||||
|
sd-concepts-library/morino-hon-style
|
||||||
|
sd-concepts-library/moxxi
|
||||||
|
sd-concepts-library/msg
|
||||||
|
sd-concepts-library/mtg-card
|
||||||
|
sd-concepts-library/mtl-longsky
|
||||||
|
sd-concepts-library/mu-sadr
|
||||||
|
sd-concepts-library/munch-leaks-style
|
||||||
|
sd-concepts-library/museum-by-coop-himmelblau
|
||||||
|
sd-concepts-library/muxoyara
|
||||||
|
sd-concepts-library/my-hero-academia-style
|
||||||
|
sd-concepts-library/my-mug
|
||||||
|
sd-concepts-library/mycat
|
||||||
|
sd-concepts-library/mystical-nature
|
||||||
|
sd-concepts-library/naf
|
||||||
|
sd-concepts-library/nahiri
|
||||||
|
sd-concepts-library/namine-ritsu
|
||||||
|
sd-concepts-library/naoki-saito
|
||||||
|
sd-concepts-library/nard-style
|
||||||
|
sd-concepts-library/naruto
|
||||||
|
sd-concepts-library/natasha-johnston
|
||||||
|
sd-concepts-library/nathan-wyatt
|
||||||
|
sd-concepts-library/naval-portrait
|
||||||
|
sd-concepts-library/nazuna
|
||||||
|
sd-concepts-library/nebula
|
||||||
|
sd-concepts-library/ned-flanders
|
||||||
|
sd-concepts-library/neon-pastel
|
||||||
|
sd-concepts-library/new-priests
|
||||||
|
sd-concepts-library/nic-papercuts
|
||||||
|
sd-concepts-library/nikodim
|
||||||
|
sd-concepts-library/nissa-revane
|
||||||
|
sd-concepts-library/nixeu
|
||||||
|
sd-concepts-library/noggles
|
||||||
|
sd-concepts-library/nomad
|
||||||
|
sd-concepts-library/nouns-glasses
|
||||||
|
sd-concepts-library/obama-based-on-xi
|
||||||
|
sd-concepts-library/obama-self-2
|
||||||
|
sd-concepts-library/og-mox-style
|
||||||
|
sd-concepts-library/ohisashiburi-style
|
||||||
|
sd-concepts-library/oleg-kuvaev
|
||||||
|
sd-concepts-library/olli-olli
|
||||||
|
sd-concepts-library/on-kawara
|
||||||
|
sd-concepts-library/one-line-drawing
|
||||||
|
sd-concepts-library/onepunchman
|
||||||
|
sd-concepts-library/onzpo
|
||||||
|
sd-concepts-library/orangejacket
|
||||||
|
sd-concepts-library/ori
|
||||||
|
sd-concepts-library/ori-toor
|
||||||
|
sd-concepts-library/orientalist-art
|
||||||
|
sd-concepts-library/osaka-jyo
|
||||||
|
sd-concepts-library/osaka-jyo2
|
||||||
|
sd-concepts-library/osrsmini2
|
||||||
|
sd-concepts-library/osrstiny
|
||||||
|
sd-concepts-library/other-mother
|
||||||
|
sd-concepts-library/ouroboros
|
||||||
|
sd-concepts-library/outfit-items
|
||||||
|
sd-concepts-library/overprettified
|
||||||
|
sd-concepts-library/owl-house
|
||||||
|
sd-concepts-library/painted-by-silver-of-999
|
||||||
|
sd-concepts-library/painted-by-silver-of-999-2
|
||||||
|
sd-concepts-library/painted-student
|
||||||
|
sd-concepts-library/painting
|
||||||
|
sd-concepts-library/pantone-milk
|
||||||
|
sd-concepts-library/paolo-bonolis
|
||||||
|
sd-concepts-library/party-girl
|
||||||
|
sd-concepts-library/pascalsibertin
|
||||||
|
sd-concepts-library/pastelartstyle
|
||||||
|
sd-concepts-library/paul-noir
|
||||||
|
sd-concepts-library/pen-ink-portraits-bennorthen
|
||||||
|
sd-concepts-library/phan
|
||||||
|
sd-concepts-library/phan-s-collage
|
||||||
|
sd-concepts-library/phc
|
||||||
|
sd-concepts-library/phoenix-01
|
||||||
|
sd-concepts-library/pineda-david
|
||||||
|
sd-concepts-library/pink-beast-pastelae-style
|
||||||
|
sd-concepts-library/pintu
|
||||||
|
sd-concepts-library/pion-by-august-semionov
|
||||||
|
sd-concepts-library/piotr-jablonski
|
||||||
|
sd-concepts-library/pixel-mania
|
||||||
|
sd-concepts-library/pixel-toy
|
||||||
|
sd-concepts-library/pjablonski-style
|
||||||
|
sd-concepts-library/plant-style
|
||||||
|
sd-concepts-library/plen-ki-mun
|
||||||
|
sd-concepts-library/pokemon-conquest-sprites
|
||||||
|
sd-concepts-library/pool-test
|
||||||
|
sd-concepts-library/poolrooms
|
||||||
|
sd-concepts-library/poring-ragnarok-online
|
||||||
|
sd-concepts-library/poutine-dish
|
||||||
|
sd-concepts-library/princess-knight-art
|
||||||
|
sd-concepts-library/progress-chip
|
||||||
|
sd-concepts-library/puerquis-toy
|
||||||
|
sd-concepts-library/purplefishli
|
||||||
|
sd-concepts-library/pyramidheadcosplay
|
||||||
|
sd-concepts-library/qpt-atrium
|
||||||
|
sd-concepts-library/quiesel
|
||||||
|
sd-concepts-library/r-crumb-style
|
||||||
|
sd-concepts-library/rahkshi-bionicle
|
||||||
|
sd-concepts-library/raichu
|
||||||
|
sd-concepts-library/rail-scene
|
||||||
|
sd-concepts-library/rail-scene-style
|
||||||
|
sd-concepts-library/ralph-mcquarrie
|
||||||
|
sd-concepts-library/ransom
|
||||||
|
sd-concepts-library/rayne-weynolds
|
||||||
|
sd-concepts-library/rcrumb-portraits-style
|
||||||
|
sd-concepts-library/rd-chaos
|
||||||
|
sd-concepts-library/rd-paintings
|
||||||
|
sd-concepts-library/red-glasses
|
||||||
|
sd-concepts-library/reeducation-camp
|
||||||
|
sd-concepts-library/reksio-dog
|
||||||
|
sd-concepts-library/rektguy
|
||||||
|
sd-concepts-library/remert
|
||||||
|
sd-concepts-library/renalla
|
||||||
|
sd-concepts-library/repeat
|
||||||
|
sd-concepts-library/retro-girl
|
||||||
|
sd-concepts-library/retro-mecha-rangers
|
||||||
|
sd-concepts-library/retropixelart-pinguin
|
||||||
|
sd-concepts-library/rex-deno
|
||||||
|
sd-concepts-library/rhizomuse-machine-bionic-sculpture
|
||||||
|
sd-concepts-library/ricar
|
||||||
|
sd-concepts-library/rickyart
|
||||||
|
sd-concepts-library/rico-face
|
||||||
|
sd-concepts-library/riker-doll
|
||||||
|
sd-concepts-library/rikiart
|
||||||
|
sd-concepts-library/rikiboy-art
|
||||||
|
sd-concepts-library/rilakkuma
|
||||||
|
sd-concepts-library/rishusei-style
|
||||||
|
sd-concepts-library/rj-palmer
|
||||||
|
sd-concepts-library/rl-pkmn-test
|
||||||
|
sd-concepts-library/road-to-ruin
|
||||||
|
sd-concepts-library/robertnava
|
||||||
|
sd-concepts-library/roblox-avatar
|
||||||
|
sd-concepts-library/roy-lichtenstein
|
||||||
|
sd-concepts-library/ruan-jia
|
||||||
|
sd-concepts-library/russian
|
||||||
|
sd-concepts-library/s1m-naoto-ohshima
|
||||||
|
sd-concepts-library/saheeli-rai
|
||||||
|
sd-concepts-library/sakimi-style
|
||||||
|
sd-concepts-library/salmonid
|
||||||
|
sd-concepts-library/sam-yang
|
||||||
|
sd-concepts-library/sanguo-guanyu
|
||||||
|
sd-concepts-library/sas-style
|
||||||
|
sd-concepts-library/scarlet-witch
|
||||||
|
sd-concepts-library/schloss-mosigkau
|
||||||
|
sd-concepts-library/scrap-style
|
||||||
|
sd-concepts-library/scratch-project
|
||||||
|
sd-concepts-library/sculptural-style
|
||||||
|
sd-concepts-library/sd-concepts-library-uma-meme
|
||||||
|
sd-concepts-library/seamless-ground
|
||||||
|
sd-concepts-library/selezneva-alisa
|
||||||
|
sd-concepts-library/sem-mac2n
|
||||||
|
sd-concepts-library/senneca
|
||||||
|
sd-concepts-library/seraphimmoonshadow-art
|
||||||
|
sd-concepts-library/sewerslvt
|
||||||
|
sd-concepts-library/she-hulk-law-art
|
||||||
|
sd-concepts-library/she-mask
|
||||||
|
sd-concepts-library/sherhook-painting
|
||||||
|
sd-concepts-library/sherhook-painting-v2
|
||||||
|
sd-concepts-library/shev-linocut
|
||||||
|
sd-concepts-library/shigure-ui-style
|
||||||
|
sd-concepts-library/shiny-polyman
|
||||||
|
sd-concepts-library/shrunken-head
|
||||||
|
sd-concepts-library/shu-doll
|
||||||
|
sd-concepts-library/shvoren-style
|
||||||
|
sd-concepts-library/sims-2-portrait
|
||||||
|
sd-concepts-library/singsing
|
||||||
|
sd-concepts-library/singsing-doll
|
||||||
|
sd-concepts-library/sintez-ico
|
||||||
|
sd-concepts-library/skyfalls
|
||||||
|
sd-concepts-library/slm
|
||||||
|
sd-concepts-library/smarties
|
||||||
|
sd-concepts-library/smiling-friend-style
|
||||||
|
sd-concepts-library/smooth-pencils
|
||||||
|
sd-concepts-library/smurf-style
|
||||||
|
sd-concepts-library/smw-map
|
||||||
|
sd-concepts-library/society-finch
|
||||||
|
sd-concepts-library/sorami-style
|
||||||
|
sd-concepts-library/spider-gwen
|
||||||
|
sd-concepts-library/spritual-monsters
|
||||||
|
sd-concepts-library/stable-diffusion-conceptualizer
|
||||||
|
sd-concepts-library/star-tours-posters
|
||||||
|
sd-concepts-library/stardew-valley-pixel-art
|
||||||
|
sd-concepts-library/starhavenmachinegods
|
||||||
|
sd-concepts-library/sterling-archer
|
||||||
|
sd-concepts-library/stretch-re1-robot
|
||||||
|
sd-concepts-library/stuffed-penguin-toy
|
||||||
|
sd-concepts-library/style-of-marc-allante
|
||||||
|
sd-concepts-library/summie-style
|
||||||
|
sd-concepts-library/sunfish
|
||||||
|
sd-concepts-library/super-nintendo-cartridge
|
||||||
|
sd-concepts-library/supitcha-mask
|
||||||
|
sd-concepts-library/sushi-pixel
|
||||||
|
sd-concepts-library/swamp-choe-2
|
||||||
|
sd-concepts-library/t-skrang
|
||||||
|
sd-concepts-library/takuji-kawano
|
||||||
|
sd-concepts-library/tamiyo
|
||||||
|
sd-concepts-library/tangles
|
||||||
|
sd-concepts-library/tb303
|
||||||
|
sd-concepts-library/tcirle
|
||||||
|
sd-concepts-library/teelip-ir-landscape
|
||||||
|
sd-concepts-library/teferi
|
||||||
|
sd-concepts-library/tela-lenca
|
||||||
|
sd-concepts-library/tela-lenca2
|
||||||
|
sd-concepts-library/terraria-style
|
||||||
|
sd-concepts-library/tesla-bot
|
||||||
|
sd-concepts-library/test
|
||||||
|
sd-concepts-library/test-epson
|
||||||
|
sd-concepts-library/test2
|
||||||
|
sd-concepts-library/testing
|
||||||
|
sd-concepts-library/thalasin
|
||||||
|
sd-concepts-library/thegeneral
|
||||||
|
sd-concepts-library/thorneworks
|
||||||
|
sd-concepts-library/threestooges
|
||||||
|
sd-concepts-library/thunderdome-cover
|
||||||
|
sd-concepts-library/thunderdome-covers
|
||||||
|
sd-concepts-library/ti-junglepunk-v0
|
||||||
|
sd-concepts-library/tili-concept
|
||||||
|
sd-concepts-library/titan-robot
|
||||||
|
sd-concepts-library/tnj
|
||||||
|
sd-concepts-library/toho-pixel
|
||||||
|
sd-concepts-library/tomcat
|
||||||
|
sd-concepts-library/tonal1
|
||||||
|
sd-concepts-library/tony-diterlizzi-s-planescape-art
|
||||||
|
sd-concepts-library/towerplace
|
||||||
|
sd-concepts-library/toy
|
||||||
|
sd-concepts-library/toy-bonnie-plush
|
||||||
|
sd-concepts-library/toyota-sera
|
||||||
|
sd-concepts-library/transmutation-circles
|
||||||
|
sd-concepts-library/trash-polka-artstyle
|
||||||
|
sd-concepts-library/travis-bedel
|
||||||
|
sd-concepts-library/trigger-studio
|
||||||
|
sd-concepts-library/trust-support
|
||||||
|
sd-concepts-library/trypophobia
|
||||||
|
sd-concepts-library/ttte
|
||||||
|
sd-concepts-library/tubby
|
||||||
|
sd-concepts-library/tubby-cats
|
||||||
|
sd-concepts-library/tudisco
|
||||||
|
sd-concepts-library/turtlepics
|
||||||
|
sd-concepts-library/type
|
||||||
|
sd-concepts-library/ugly-sonic
|
||||||
|
sd-concepts-library/uliana-kudinova
|
||||||
|
sd-concepts-library/uma
|
||||||
|
sd-concepts-library/uma-clean-object
|
||||||
|
sd-concepts-library/uma-meme
|
||||||
|
sd-concepts-library/uma-meme-style
|
||||||
|
sd-concepts-library/uma-style-classic
|
||||||
|
sd-concepts-library/unfinished-building
|
||||||
|
sd-concepts-library/urivoldemort
|
||||||
|
sd-concepts-library/uzumaki
|
||||||
|
sd-concepts-library/valorantstyle
|
||||||
|
sd-concepts-library/vb-mox
|
||||||
|
sd-concepts-library/vcr-classique
|
||||||
|
sd-concepts-library/venice
|
||||||
|
sd-concepts-library/vespertine
|
||||||
|
sd-concepts-library/victor-narm
|
||||||
|
sd-concepts-library/vietstoneking
|
||||||
|
sd-concepts-library/vivien-reid
|
||||||
|
sd-concepts-library/vkuoo1
|
||||||
|
sd-concepts-library/vraska
|
||||||
|
sd-concepts-library/w3u
|
||||||
|
sd-concepts-library/walter-wick-photography
|
||||||
|
sd-concepts-library/warhammer-40k-drawing-style
|
||||||
|
sd-concepts-library/waterfallshadow
|
||||||
|
sd-concepts-library/wayne-reynolds-character
|
||||||
|
sd-concepts-library/wedding
|
||||||
|
sd-concepts-library/wedding-HandPainted
|
||||||
|
sd-concepts-library/werebloops
|
||||||
|
sd-concepts-library/wheatland
|
||||||
|
sd-concepts-library/wheatland-arknight
|
||||||
|
sd-concepts-library/wheelchair
|
||||||
|
sd-concepts-library/wildkat
|
||||||
|
sd-concepts-library/willy-hd
|
||||||
|
sd-concepts-library/wire-angels
|
||||||
|
sd-concepts-library/wish-artist-stile
|
||||||
|
sd-concepts-library/wlop-style
|
||||||
|
sd-concepts-library/wojak
|
||||||
|
sd-concepts-library/wojaks-now
|
||||||
|
sd-concepts-library/wojaks-now-now-now
|
||||||
|
sd-concepts-library/xatu
|
||||||
|
sd-concepts-library/xatu2
|
||||||
|
sd-concepts-library/xbh
|
||||||
|
sd-concepts-library/xi
|
||||||
|
sd-concepts-library/xidiversity
|
||||||
|
sd-concepts-library/xioboma
|
||||||
|
sd-concepts-library/xuna
|
||||||
|
sd-concepts-library/xyz
|
||||||
|
sd-concepts-library/yb-anime
|
||||||
|
sd-concepts-library/yerba-mate
|
||||||
|
sd-concepts-library/yesdelete
|
||||||
|
sd-concepts-library/yf21
|
||||||
|
sd-concepts-library/yilanov2
|
||||||
|
sd-concepts-library/yinit
|
||||||
|
sd-concepts-library/yoji-shinkawa-style
|
||||||
|
sd-concepts-library/yolandi-visser
|
||||||
|
sd-concepts-library/yoshi
|
||||||
|
sd-concepts-library/youpi2
|
||||||
|
sd-concepts-library/youtooz-candy
|
||||||
|
sd-concepts-library/yuji-himukai-style
|
||||||
|
sd-concepts-library/zaney
|
||||||
|
sd-concepts-library/zaneypixelz
|
||||||
|
sd-concepts-library/zdenek-art
|
||||||
|
sd-concepts-library/zero
|
||||||
|
sd-concepts-library/zero-bottle
|
||||||
|
sd-concepts-library/zero-suit-samus
|
||||||
|
sd-concepts-library/zillertal-can
|
||||||
|
sd-concepts-library/zizigooloo
|
||||||
|
sd-concepts-library/zk
|
||||||
|
sd-concepts-library/zoroark
|
@ -32,7 +32,7 @@ model:
|
|||||||
placeholder_strings: ["*"]
|
placeholder_strings: ["*"]
|
||||||
initializer_words: ['sculpture']
|
initializer_words: ['sculpture']
|
||||||
per_image_tokens: false
|
per_image_tokens: false
|
||||||
num_vectors_per_token: 1
|
num_vectors_per_token: 8
|
||||||
progressive_words: False
|
progressive_words: False
|
||||||
|
|
||||||
unet_config:
|
unet_config:
|
||||||
|
@ -32,7 +32,7 @@ model:
|
|||||||
placeholder_strings: ["*"]
|
placeholder_strings: ["*"]
|
||||||
initializer_words: ['sculpture']
|
initializer_words: ['sculpture']
|
||||||
per_image_tokens: false
|
per_image_tokens: false
|
||||||
num_vectors_per_token: 1
|
num_vectors_per_token: 8
|
||||||
progressive_words: False
|
progressive_words: False
|
||||||
|
|
||||||
unet_config:
|
unet_config:
|
||||||
|
@ -303,6 +303,8 @@ The WebGUI is only rapid development. Check back regularly for updates!
|
|||||||
| `--cors [CORS ...]` | Additional allowed origins, comma-separated |
|
| `--cors [CORS ...]` | Additional allowed origins, comma-separated |
|
||||||
| `--host HOST` | Web server: Host or IP to listen on. Set to 0.0.0.0 to accept traffic from other devices on your network. |
|
| `--host HOST` | Web server: Host or IP to listen on. Set to 0.0.0.0 to accept traffic from other devices on your network. |
|
||||||
| `--port PORT` | Web server: Port to listen on |
|
| `--port PORT` | Web server: Port to listen on |
|
||||||
|
| `--certfile CERTFILE` | Web server: Path to certificate file to use for SSL. Use together with --keyfile |
|
||||||
|
| `--keyfile KEYFILE` | Web server: Path to private key file to use for SSL. Use together with --certfile' |
|
||||||
| `--gui` | Start InvokeAI GUI - This is the "desktop mode" version of the web app. It uses Flask to create a desktop app experience of the webserver. |
|
| `--gui` | Start InvokeAI GUI - This is the "desktop mode" version of the web app. It uses Flask to create a desktop app experience of the webserver. |
|
||||||
|
|
||||||
### Web Specific Features
|
### Web Specific Features
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
name: invokeai
|
|
||||||
channels:
|
|
||||||
- pytorch
|
|
||||||
- conda-forge
|
|
||||||
- defaults
|
|
||||||
dependencies:
|
|
||||||
- python>=3.9
|
|
||||||
- pip=22.2.2
|
|
||||||
- numpy=1.23.3
|
|
||||||
- pip:
|
|
||||||
- --extra-index-url https://download.pytorch.org/whl/rocm5.2/
|
|
||||||
- albumentations==0.4.3
|
|
||||||
- dependency_injector==4.40.0
|
|
||||||
- diffusers==0.6.0
|
|
||||||
- einops==0.3.0
|
|
||||||
- eventlet
|
|
||||||
- flask==2.1.3
|
|
||||||
- flask_cors==3.0.10
|
|
||||||
- flask_socketio==5.3.0
|
|
||||||
- getpass_asterisk
|
|
||||||
- imageio-ffmpeg==0.4.2
|
|
||||||
- imageio==2.9.0
|
|
||||||
- kornia==0.6.0
|
|
||||||
- omegaconf==2.2.3
|
|
||||||
- opencv-python==4.5.5.64
|
|
||||||
- pillow==9.2.0
|
|
||||||
- pudb==2019.2
|
|
||||||
- pyreadline3
|
|
||||||
- pytorch-lightning==1.7.7
|
|
||||||
- send2trash==1.8.0
|
|
||||||
- streamlit==1.12.0
|
|
||||||
- taming-transformers-rom1504
|
|
||||||
- test-tube>=0.7.5
|
|
||||||
- torch
|
|
||||||
- torch-fidelity==0.3.0
|
|
||||||
- torchaudio
|
|
||||||
- torchmetrics==0.7.0
|
|
||||||
- torchvision
|
|
||||||
- transformers==4.21.3
|
|
||||||
- git+https://github.com/openai/CLIP.git@main#egg=clip
|
|
||||||
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k_diffusion
|
|
||||||
- git+https://github.com/invoke-ai/Real-ESRGAN.git#egg=realesrgan
|
|
||||||
- git+https://github.com/invoke-ai/GFPGAN.git#egg=gfpgan
|
|
||||||
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
|
||||||
- -e .
|
|
@ -42,4 +42,5 @@ dependencies:
|
|||||||
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k_diffusion
|
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k_diffusion
|
||||||
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
||||||
- git+https://github.com/invoke-ai/GFPGAN#egg=gfpgan
|
- git+https://github.com/invoke-ai/GFPGAN#egg=gfpgan
|
||||||
|
- git+https://github.com/invoke-ai/PyPatchMatch@0.1.1#egg=pypatchmatch
|
||||||
- -e .
|
- -e .
|
||||||
|
@ -44,4 +44,5 @@ dependencies:
|
|||||||
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
||||||
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
||||||
- git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
- git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
||||||
|
- git+https://github.com/invoke-ai/PyPatchMatch@0.1.1#egg=pypatchmatch
|
||||||
- -e .
|
- -e .
|
||||||
|
@ -43,4 +43,5 @@ dependencies:
|
|||||||
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
||||||
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
||||||
- git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
- git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
||||||
|
- git+https://github.com/invoke-ai/PyPatchMatch@0.1.1#egg=pypatchmatch
|
||||||
- -e .
|
- -e .
|
||||||
|
@ -59,6 +59,7 @@ dependencies:
|
|||||||
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
||||||
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
||||||
- git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
- git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
||||||
|
- git+https://github.com/invoke-ai/PyPatchMatch@0.1.1#egg=pypatchmatch
|
||||||
- -e .
|
- -e .
|
||||||
variables:
|
variables:
|
||||||
PYTORCH_ENABLE_MPS_FALLBACK: 1
|
PYTORCH_ENABLE_MPS_FALLBACK: 1
|
||||||
|
@ -44,4 +44,5 @@ dependencies:
|
|||||||
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k_diffusion
|
- git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k_diffusion
|
||||||
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
- git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
||||||
- git+https://github.com/invoke-ai/GFPGAN#egg=gfpgan
|
- git+https://github.com/invoke-ai/GFPGAN#egg=gfpgan
|
||||||
|
- git+https://github.com/invoke-ai/PyPatchMatch@0.1.1#egg=pypatchmatch
|
||||||
- -e .
|
- -e .
|
||||||
|
@ -35,4 +35,4 @@ picklescan
|
|||||||
git+https://github.com/openai/CLIP.git@main#egg=clip
|
git+https://github.com/openai/CLIP.git@main#egg=clip
|
||||||
git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
git+https://github.com/Birch-san/k-diffusion.git@mps#egg=k-diffusion
|
||||||
git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
git+https://github.com/invoke-ai/clipseg.git@relaxed-python-requirement#egg=clipseg
|
||||||
git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
git+https://github.com/invoke-ai/PyPatchMatch@0.1.1#egg=pypatchmatch
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
-r environments-and-requirements/requirements-base.txt
|
-r environments-and-requirements/requirements-base.txt
|
||||||
|
|
||||||
|
git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
||||||
|
|
||||||
# Get hardware-appropriate torch/torchvision
|
# Get hardware-appropriate torch/torchvision
|
||||||
--extra-index-url https://download.pytorch.org/whl/rocm5.1.1 --trusted-host https://download.pytorch.org
|
--extra-index-url https://download.pytorch.org/whl/rocm5.1.1 --trusted-host https://download.pytorch.org
|
||||||
torch
|
torch
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
--pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/nightly/cpu
|
--pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/nightly/cpu
|
||||||
|
|
||||||
|
git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
||||||
|
|
||||||
-r environments-and-requirements/requirements-base.txt
|
-r environments-and-requirements/requirements-base.txt
|
||||||
-e .
|
-e .
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
-r environments-and-requirements/requirements-base.txt
|
-r environments-and-requirements/requirements-base.txt
|
||||||
# -e .
|
git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
||||||
|
-e .
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
-r environments-and-requirements/requirements-base.txt
|
-r environments-and-requirements/requirements-base.txt
|
||||||
|
git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.2#egg=gfpgan
|
||||||
grpcio<1.51.0
|
grpcio<1.51.0
|
||||||
protobuf==3.19.6
|
protobuf==3.19.6
|
||||||
torch<1.13.0
|
torch<1.13.0
|
||||||
torchvision<0.14.0
|
torchvision<0.14.0
|
||||||
# -e .
|
-e .
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
-r environments-and-requirements/requirements-base.txt
|
-r environments-and-requirements/requirements-base.txt
|
||||||
|
git+https://github.com/invoke-ai/GFPGAN@basicsr-1.4.1#egg=gfpgan
|
||||||
# Get hardware-appropriate torch/torchvision
|
# Get hardware-appropriate torch/torchvision
|
||||||
--extra-index-url https://download.pytorch.org/whl/cu116 --trusted-host https://download.pytorch.org
|
--extra-index-url https://download.pytorch.org/whl/cu116 --trusted-host https://download.pytorch.org
|
||||||
basicsr==1.4.1
|
basicsr==1.4.1
|
||||||
torch==1.12.1
|
torch==1.12.1
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
- `python scripts/dream.py --web` serves both frontend and backend at
|
- `python scripts/dream.py --web` serves both frontend and backend at
|
||||||
http://localhost:9090
|
http://localhost:9090
|
||||||
|
|
||||||
## Environment
|
## Evironment
|
||||||
|
|
||||||
Install [node](https://nodejs.org/en/download/) (includes npm) and optionally
|
Install [node](https://nodejs.org/en/download/) (includes npm) and optionally
|
||||||
[yarn](https://yarnpkg.com/getting-started/install).
|
[yarn](https://yarnpkg.com/getting-started/install).
|
||||||
@ -15,7 +15,7 @@ packages.
|
|||||||
|
|
||||||
## Dev
|
## Dev
|
||||||
|
|
||||||
1. From `frontend/`, run `npm run dev` / `yarn dev` to start the dev server.
|
1. From `frontend/`, run `npm dev` / `yarn dev` to start the dev server.
|
||||||
2. Run `python scripts/dream.py --web`.
|
2. Run `python scripts/dream.py --web`.
|
||||||
3. Navigate to the dev server address e.g. `http://localhost:5173/`.
|
3. Navigate to the dev server address e.g. `http://localhost:5173/`.
|
||||||
|
|
||||||
|
623
frontend/dist/assets/index.2b7cd976.js
vendored
Normal file
1
frontend/dist/assets/index.40a72c80.css
vendored
501
frontend/dist/assets/index.a8ba2a6c.js
vendored
1
frontend/dist/assets/index.f999e69e.css
vendored
Normal file
4
frontend/dist/index.html
vendored
@ -6,8 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
||||||
<script type="module" crossorigin src="./assets/index.a8ba2a6c.js"></script>
|
<script type="module" crossorigin src="./assets/index.2b7cd976.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index.40a72c80.css">
|
<link rel="stylesheet" href="./assets/index.f999e69e.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
23
frontend/eslintconfig.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:react-hooks/recommended"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": ["@typescript-eslint", "eslint-plugin-react-hooks"],
|
||||||
|
"root": true,
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"node": {
|
||||||
|
"paths": ["src"],
|
||||||
|
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-filename-extension": [1, { "extensions": [".tsx", ".ts"] }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,13 @@
|
|||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build-dev": "tsc && vite build -m development",
|
"build-dev": "tsc && vite build -m development",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.10",
|
"@chakra-ui/icons": "^2.0.10",
|
||||||
"@chakra-ui/react": "^2.3.1",
|
"@chakra-ui/react": "^2.3.1",
|
||||||
|
"@emotion/cache": "^11.10.5",
|
||||||
"@emotion/react": "^11.10.4",
|
"@emotion/react": "^11.10.4",
|
||||||
"@emotion/styled": "^11.10.4",
|
"@emotion/styled": "^11.10.4",
|
||||||
"@radix-ui/react-context-menu": "^2.0.1",
|
"@radix-ui/react-context-menu": "^2.0.1",
|
||||||
@ -29,14 +31,18 @@
|
|||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.2",
|
"react-dropzone": "^14.2.2",
|
||||||
"react-hotkeys-hook": "^3.4.7",
|
"react-hotkeys-hook": "4.0.2",
|
||||||
"react-icons": "^4.4.0",
|
"react-icons": "^4.4.0",
|
||||||
"react-konva": "^18.2.3",
|
"react-konva": "^18.2.3",
|
||||||
|
"react-konva-utils": "^0.3.0",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
|
"react-zoom-pan-pinch": "^2.1.3",
|
||||||
|
"redux-deep-persist": "^1.0.6",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"socket.io": "^4.5.2",
|
"socket.io": "^4.5.2",
|
||||||
"socket.io-client": "^4.5.2",
|
"socket.io-client": "^4.5.2",
|
||||||
|
"use-image": "^1.1.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"yarn": "^1.22.19"
|
"yarn": "^1.22.19"
|
||||||
},
|
},
|
||||||
@ -51,10 +57,13 @@
|
|||||||
"eslint": "^8.23.0",
|
"eslint": "^8.23.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"patch-package": "^6.5.0",
|
||||||
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"sass": "^1.55.0",
|
"sass": "^1.55.0",
|
||||||
"tsc-watch": "^5.0.3",
|
"tsc-watch": "^5.0.3",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.6.4",
|
||||||
"vite": "^3.0.7",
|
"vite": "^3.0.7",
|
||||||
"vite-plugin-eslint": "^1.8.1"
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
|
"vite-tsconfig-paths": "^3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
frontend/patches/redux-deep-persist+1.0.6.patch
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
diff --git a/node_modules/redux-deep-persist/lib/types.d.ts b/node_modules/redux-deep-persist/lib/types.d.ts
|
||||||
|
index b67b8c2..7fc0fa1 100644
|
||||||
|
--- a/node_modules/redux-deep-persist/lib/types.d.ts
|
||||||
|
+++ b/node_modules/redux-deep-persist/lib/types.d.ts
|
||||||
|
@@ -35,6 +35,7 @@ export interface PersistConfig<S, RS = any, HSS = any, ESS = any> {
|
||||||
|
whitelist?: Array<string>;
|
||||||
|
transforms?: Array<Transform<HSS, ESS, S, RS>>;
|
||||||
|
throttle?: number;
|
||||||
|
+ debounce?: number;
|
||||||
|
migrate?: PersistMigrate;
|
||||||
|
stateReconciler?: false | StateReconciler<S>;
|
||||||
|
getStoredState?: (config: PersistConfig<S, RS, HSS, ESS>) => Promise<PersistedState>;
|
||||||
|
diff --git a/node_modules/redux-deep-persist/src/types.ts b/node_modules/redux-deep-persist/src/types.ts
|
||||||
|
index 398ac19..cbc5663 100644
|
||||||
|
--- a/node_modules/redux-deep-persist/src/types.ts
|
||||||
|
+++ b/node_modules/redux-deep-persist/src/types.ts
|
||||||
|
@@ -91,6 +91,7 @@ export interface PersistConfig<S, RS = any, HSS = any, ESS = any> {
|
||||||
|
whitelist?: Array<string>;
|
||||||
|
transforms?: Array<Transform<HSS, ESS, S, RS>>;
|
||||||
|
throttle?: number;
|
||||||
|
+ debounce?: number;
|
||||||
|
migrate?: PersistMigrate;
|
||||||
|
stateReconciler?: false | StateReconciler<S>;
|
||||||
|
/**
|
116
frontend/patches/redux-persist+6.0.0.patch
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
diff --git a/node_modules/redux-persist/es/createPersistoid.js b/node_modules/redux-persist/es/createPersistoid.js
|
||||||
|
index 8b43b9a..184faab 100644
|
||||||
|
--- a/node_modules/redux-persist/es/createPersistoid.js
|
||||||
|
+++ b/node_modules/redux-persist/es/createPersistoid.js
|
||||||
|
@@ -6,6 +6,7 @@ export default function createPersistoid(config) {
|
||||||
|
var whitelist = config.whitelist || null;
|
||||||
|
var transforms = config.transforms || [];
|
||||||
|
var throttle = config.throttle || 0;
|
||||||
|
+ var debounce = config.debounce || 0;
|
||||||
|
var storageKey = "".concat(config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX).concat(config.key);
|
||||||
|
var storage = config.storage;
|
||||||
|
var serialize;
|
||||||
|
@@ -28,30 +29,37 @@ export default function createPersistoid(config) {
|
||||||
|
var timeIterator = null;
|
||||||
|
var writePromise = null;
|
||||||
|
|
||||||
|
- var update = function update(state) {
|
||||||
|
- // add any changed keys to the queue
|
||||||
|
- Object.keys(state).forEach(function (key) {
|
||||||
|
- if (!passWhitelistBlacklist(key)) return; // is keyspace ignored? noop
|
||||||
|
+ // Timer for debounced `update()`
|
||||||
|
+ let timer = 0;
|
||||||
|
|
||||||
|
- if (lastState[key] === state[key]) return; // value unchanged? noop
|
||||||
|
+ function update(state) {
|
||||||
|
+ // Debounce the update
|
||||||
|
+ clearTimeout(timer);
|
||||||
|
+ timer = setTimeout(() => {
|
||||||
|
+ // add any changed keys to the queue
|
||||||
|
+ Object.keys(state).forEach(function (key) {
|
||||||
|
+ if (!passWhitelistBlacklist(key)) return; // is keyspace ignored? noop
|
||||||
|
|
||||||
|
- if (keysToProcess.indexOf(key) !== -1) return; // is key already queued? noop
|
||||||
|
+ if (lastState[key] === state[key]) return; // value unchanged? noop
|
||||||
|
|
||||||
|
- keysToProcess.push(key); // add key to queue
|
||||||
|
- }); //if any key is missing in the new state which was present in the lastState,
|
||||||
|
- //add it for processing too
|
||||||
|
+ if (keysToProcess.indexOf(key) !== -1) return; // is key already queued? noop
|
||||||
|
|
||||||
|
- Object.keys(lastState).forEach(function (key) {
|
||||||
|
- if (state[key] === undefined && passWhitelistBlacklist(key) && keysToProcess.indexOf(key) === -1 && lastState[key] !== undefined) {
|
||||||
|
- keysToProcess.push(key);
|
||||||
|
- }
|
||||||
|
- }); // start the time iterator if not running (read: throttle)
|
||||||
|
+ keysToProcess.push(key); // add key to queue
|
||||||
|
+ }); //if any key is missing in the new state which was present in the lastState,
|
||||||
|
+ //add it for processing too
|
||||||
|
|
||||||
|
- if (timeIterator === null) {
|
||||||
|
- timeIterator = setInterval(processNextKey, throttle);
|
||||||
|
- }
|
||||||
|
+ Object.keys(lastState).forEach(function (key) {
|
||||||
|
+ if (state[key] === undefined && passWhitelistBlacklist(key) && keysToProcess.indexOf(key) === -1 && lastState[key] !== undefined) {
|
||||||
|
+ keysToProcess.push(key);
|
||||||
|
+ }
|
||||||
|
+ }); // start the time iterator if not running (read: throttle)
|
||||||
|
+
|
||||||
|
+ if (timeIterator === null) {
|
||||||
|
+ timeIterator = setInterval(processNextKey, throttle);
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- lastState = state;
|
||||||
|
+ lastState = state;
|
||||||
|
+ }, debounce)
|
||||||
|
};
|
||||||
|
|
||||||
|
function processNextKey() {
|
||||||
|
diff --git a/node_modules/redux-persist/es/types.js.flow b/node_modules/redux-persist/es/types.js.flow
|
||||||
|
index c50d3cd..39d8be2 100644
|
||||||
|
--- a/node_modules/redux-persist/es/types.js.flow
|
||||||
|
+++ b/node_modules/redux-persist/es/types.js.flow
|
||||||
|
@@ -19,6 +19,7 @@ export type PersistConfig = {
|
||||||
|
whitelist?: Array<string>,
|
||||||
|
transforms?: Array<Transform>,
|
||||||
|
throttle?: number,
|
||||||
|
+ debounce?: number,
|
||||||
|
migrate?: (PersistedState, number) => Promise<PersistedState>,
|
||||||
|
stateReconciler?: false | Function,
|
||||||
|
getStoredState?: PersistConfig => Promise<PersistedState>, // used for migrations
|
||||||
|
diff --git a/node_modules/redux-persist/lib/types.js.flow b/node_modules/redux-persist/lib/types.js.flow
|
||||||
|
index c50d3cd..39d8be2 100644
|
||||||
|
--- a/node_modules/redux-persist/lib/types.js.flow
|
||||||
|
+++ b/node_modules/redux-persist/lib/types.js.flow
|
||||||
|
@@ -19,6 +19,7 @@ export type PersistConfig = {
|
||||||
|
whitelist?: Array<string>,
|
||||||
|
transforms?: Array<Transform>,
|
||||||
|
throttle?: number,
|
||||||
|
+ debounce?: number,
|
||||||
|
migrate?: (PersistedState, number) => Promise<PersistedState>,
|
||||||
|
stateReconciler?: false | Function,
|
||||||
|
getStoredState?: PersistConfig => Promise<PersistedState>, // used for migrations
|
||||||
|
diff --git a/node_modules/redux-persist/src/types.js b/node_modules/redux-persist/src/types.js
|
||||||
|
index c50d3cd..39d8be2 100644
|
||||||
|
--- a/node_modules/redux-persist/src/types.js
|
||||||
|
+++ b/node_modules/redux-persist/src/types.js
|
||||||
|
@@ -19,6 +19,7 @@ export type PersistConfig = {
|
||||||
|
whitelist?: Array<string>,
|
||||||
|
transforms?: Array<Transform>,
|
||||||
|
throttle?: number,
|
||||||
|
+ debounce?: number,
|
||||||
|
migrate?: (PersistedState, number) => Promise<PersistedState>,
|
||||||
|
stateReconciler?: false | Function,
|
||||||
|
getStoredState?: PersistConfig => Promise<PersistedState>, // used for migrations
|
||||||
|
diff --git a/node_modules/redux-persist/types/types.d.ts b/node_modules/redux-persist/types/types.d.ts
|
||||||
|
index b3733bc..2a1696c 100644
|
||||||
|
--- a/node_modules/redux-persist/types/types.d.ts
|
||||||
|
+++ b/node_modules/redux-persist/types/types.d.ts
|
||||||
|
@@ -35,6 +35,7 @@ declare module "redux-persist/es/types" {
|
||||||
|
whitelist?: Array<string>;
|
||||||
|
transforms?: Array<Transform<HSS, ESS, S, RS>>;
|
||||||
|
throttle?: number;
|
||||||
|
+ debounce?: number;
|
||||||
|
migrate?: PersistMigrate;
|
||||||
|
stateReconciler?: false | StateReconciler<S>;
|
||||||
|
/**
|
@ -1,5 +1,9 @@
|
|||||||
@use '../styles/Mixins/' as *;
|
@use '../styles/Mixins/' as *;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--svg-color);
|
||||||
|
}
|
||||||
|
|
||||||
.App {
|
.App {
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
@ -1,89 +1,19 @@
|
|||||||
import { useEffect } from 'react';
|
import ProgressBar from 'features/system/components/ProgressBar';
|
||||||
import ProgressBar from '../features/system/ProgressBar';
|
import SiteHeader from 'features/system/components/SiteHeader';
|
||||||
import SiteHeader from '../features/system/SiteHeader';
|
import Console from 'features/system/components/Console';
|
||||||
import Console from '../features/system/Console';
|
|
||||||
import { useAppDispatch } from './store';
|
|
||||||
import { requestSystemConfig } from './socketio/actions';
|
|
||||||
import { keepGUIAlive } from './utils';
|
import { keepGUIAlive } from './utils';
|
||||||
import InvokeTabs from '../features/tabs/InvokeTabs';
|
import InvokeTabs from 'features/tabs/components/InvokeTabs';
|
||||||
import ImageUploader from '../common/components/ImageUploader';
|
import ImageUploader from 'common/components/ImageUploader';
|
||||||
import { RootState, useAppSelector } from '../app/store';
|
|
||||||
|
|
||||||
import FloatingGalleryButton from '../features/tabs/FloatingGalleryButton';
|
import useToastWatcher from 'features/system/hooks/useToastWatcher';
|
||||||
import FloatingOptionsPanelButtons from '../features/tabs/FloatingOptionsPanelButtons';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import FloatingOptionsPanelButtons from 'features/tabs/components/FloatingOptionsPanelButtons';
|
||||||
import { GalleryState } from '../features/gallery/gallerySlice';
|
import FloatingGalleryButton from 'features/tabs/components/FloatingGalleryButton';
|
||||||
import { OptionsState } from '../features/options/optionsSlice';
|
|
||||||
import { activeTabNameSelector } from '../features/options/optionsSelectors';
|
|
||||||
import { SystemState } from '../features/system/systemSlice';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { Model } from './invokeai';
|
|
||||||
|
|
||||||
keepGUIAlive();
|
keepGUIAlive();
|
||||||
|
|
||||||
const appSelector = createSelector(
|
|
||||||
[
|
|
||||||
(state: RootState) => state.gallery,
|
|
||||||
(state: RootState) => state.options,
|
|
||||||
(state: RootState) => state.system,
|
|
||||||
activeTabNameSelector,
|
|
||||||
],
|
|
||||||
(
|
|
||||||
gallery: GalleryState,
|
|
||||||
options: OptionsState,
|
|
||||||
system: SystemState,
|
|
||||||
activeTabName
|
|
||||||
) => {
|
|
||||||
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
|
||||||
gallery;
|
|
||||||
const {
|
|
||||||
shouldShowOptionsPanel,
|
|
||||||
shouldHoldOptionsPanelOpen,
|
|
||||||
shouldPinOptionsPanel,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const modelStatusText = _.reduce(
|
|
||||||
system.model_list,
|
|
||||||
(acc: string, cur: Model, key: string) => {
|
|
||||||
if (cur.status === 'active') acc = key;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldShowGalleryButton = !(
|
|
||||||
shouldShowGallery ||
|
|
||||||
(shouldHoldGalleryOpen && !shouldPinGallery)
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldShowOptionsPanelButton =
|
|
||||||
!(
|
|
||||||
shouldShowOptionsPanel ||
|
|
||||||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
|
||||||
) && ['txt2img', 'img2img', 'inpainting'].includes(activeTabName);
|
|
||||||
|
|
||||||
return {
|
|
||||||
modelStatusText,
|
|
||||||
shouldShowGalleryButton,
|
|
||||||
shouldShowOptionsPanelButton,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: _.isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const dispatch = useAppDispatch();
|
useToastWatcher();
|
||||||
|
|
||||||
const { shouldShowGalleryButton, shouldShowOptionsPanelButton } =
|
|
||||||
useAppSelector(appSelector);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(requestSystemConfig());
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
@ -96,9 +26,9 @@ const App = () => {
|
|||||||
<div className="app-console">
|
<div className="app-console">
|
||||||
<Console />
|
<Console />
|
||||||
</div>
|
</div>
|
||||||
{shouldShowGalleryButton && <FloatingGalleryButton />}
|
|
||||||
{shouldShowOptionsPanelButton && <FloatingOptionsPanelButtons />}
|
|
||||||
</ImageUploader>
|
</ImageUploader>
|
||||||
|
<FloatingOptionsPanelButtons />
|
||||||
|
<FloatingGalleryButton />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// TODO: use Enums?
|
// TODO: use Enums?
|
||||||
|
|
||||||
import { InProgressImageType } from '../features/system/systemSlice';
|
import { InProgressImageType } from 'features/system/store/systemSlice';
|
||||||
|
|
||||||
// Valid samplers
|
// Valid samplers
|
||||||
export const SAMPLERS: Array<string> = [
|
export const SAMPLERS: Array<string> = [
|
||||||
|
@ -13,10 +13,13 @@ export enum Feature {
|
|||||||
UPSCALE,
|
UPSCALE,
|
||||||
FACE_CORRECTION,
|
FACE_CORRECTION,
|
||||||
IMAGE_TO_IMAGE,
|
IMAGE_TO_IMAGE,
|
||||||
|
BOUNDING_BOX,
|
||||||
|
SEAM_CORRECTION,
|
||||||
|
INFILL_AND_SCALING,
|
||||||
}
|
}
|
||||||
/** For each tooltip in the UI, the below feature definitions & props will pull relevant information into the tooltip.
|
/** For each tooltip in the UI, the below feature definitions & props will pull relevant information into the tooltip.
|
||||||
*
|
*
|
||||||
* To-do: href & GuideImages are placeholders, and are not currently utilized, but will be updated (along with the tooltip UI) as feature and UI development and we get a better idea on where things "forever homes" will be .
|
* To-do: href & GuideImages are placeholders, and are not currently utilized, but will be updated (along with the tooltip UI) as feature and UI develop and we get a better idea on where things "forever homes" will be .
|
||||||
*/
|
*/
|
||||||
export const FEATURES: Record<Feature, FeatureHelpInfo> = {
|
export const FEATURES: Record<Feature, FeatureHelpInfo> = {
|
||||||
[Feature.PROMPT]: {
|
[Feature.PROMPT]: {
|
||||||
@ -55,7 +58,22 @@ export const FEATURES: Record<Feature, FeatureHelpInfo> = {
|
|||||||
guideImage: 'asset/path.gif',
|
guideImage: 'asset/path.gif',
|
||||||
},
|
},
|
||||||
[Feature.IMAGE_TO_IMAGE]: {
|
[Feature.IMAGE_TO_IMAGE]: {
|
||||||
text: 'ImageToImage allows the upload of an initial image, which InvokeAI will use to guide the generation process, along with a prompt. A lower value for this setting will more closely resemble the original image. Values between 0-1 are accepted, and a range of .25-.75 is recommended ',
|
text: 'Image to Image allows the upload of an initial image, which InvokeAI will use to guide the generation process, along with a prompt. A lower value for this setting will more closely resemble the original image. Values between 0-1 are accepted, and a range of .25-.75 is recommended ',
|
||||||
|
href: 'link/to/docs/feature3.html',
|
||||||
|
guideImage: 'asset/path.gif',
|
||||||
|
},
|
||||||
|
[Feature.BOUNDING_BOX]: {
|
||||||
|
text: 'The bounding box is analogous to the Width and Height settings for Text to Image or Image to Image. Only the area in the box will be processed.',
|
||||||
|
href: 'link/to/docs/feature3.html',
|
||||||
|
guideImage: 'asset/path.gif',
|
||||||
|
},
|
||||||
|
[Feature.SEAM_CORRECTION]: {
|
||||||
|
text: 'Control the handling of visible seams which may occur when a generated image is pasted back onto the canvas.',
|
||||||
|
href: 'link/to/docs/feature3.html',
|
||||||
|
guideImage: 'asset/path.gif',
|
||||||
|
},
|
||||||
|
[Feature.INFILL_AND_SCALING]: {
|
||||||
|
text: 'Manage infill methods (used on masked or erased areas of the canvas) and scaling (useful for small bounding box sizes).',
|
||||||
href: 'link/to/docs/feature3.html',
|
href: 'link/to/docs/feature3.html',
|
||||||
guideImage: 'asset/path.gif',
|
guideImage: 'asset/path.gif',
|
||||||
},
|
},
|
||||||
|
43
frontend/src/app/invokeai.d.ts
vendored
@ -12,7 +12,9 @@
|
|||||||
* 'gfpgan'.
|
* 'gfpgan'.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Category as GalleryCategory } from '../features/gallery/gallerySlice';
|
import { Category as GalleryCategory } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { InvokeTabName } from 'features/tabs/components/InvokeTabs';
|
||||||
|
import { IRect } from 'konva/lib/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO:
|
* TODO:
|
||||||
@ -103,7 +105,7 @@ export declare type PostProcessedImageMetadata =
|
|||||||
| FacetoolMetadata;
|
| FacetoolMetadata;
|
||||||
|
|
||||||
// Metadata includes the system config and image metadata.
|
// Metadata includes the system config and image metadata.
|
||||||
export declare type Metadata = SystemConfig & {
|
export declare type Metadata = SystemGenerationMetadata & {
|
||||||
image: GeneratedImageMetadata | PostProcessedImageMetadata;
|
image: GeneratedImageMetadata | PostProcessedImageMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,12 +113,14 @@ export declare type Metadata = SystemConfig & {
|
|||||||
export declare type Image = {
|
export declare type Image = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
thumbnail: string;
|
||||||
mtime: number;
|
mtime: number;
|
||||||
metadata?: Metadata;
|
metadata?: Metadata;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
category: GalleryCategory;
|
category: GalleryCategory;
|
||||||
isBase64: boolean;
|
isBase64?: boolean;
|
||||||
|
dreamPrompt?: 'string';
|
||||||
};
|
};
|
||||||
|
|
||||||
// GalleryImages is an array of Image.
|
// GalleryImages is an array of Image.
|
||||||
@ -140,13 +144,18 @@ export declare type SystemStatus = {
|
|||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export declare type SystemConfig = {
|
export declare type SystemGenerationMetadata = {
|
||||||
model: string;
|
model: string;
|
||||||
model_id: string;
|
model_weights?: string;
|
||||||
|
model_id?: string;
|
||||||
model_hash: string;
|
model_hash: string;
|
||||||
app_id: string;
|
app_id: string;
|
||||||
app_version: string;
|
app_version: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export declare type SystemConfig = SystemGenerationMetadata & {
|
||||||
model_list: ModelList;
|
model_list: ModelList;
|
||||||
|
infill_methods: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export declare type ModelStatus = 'active' | 'cached' | 'not loaded';
|
export declare type ModelStatus = 'active' | 'cached' | 'not loaded';
|
||||||
@ -171,10 +180,19 @@ export declare type SystemStatusResponse = SystemStatus;
|
|||||||
|
|
||||||
export declare type SystemConfigResponse = SystemConfig;
|
export declare type SystemConfigResponse = SystemConfig;
|
||||||
|
|
||||||
export declare type ImageResultResponse = Omit<Image, 'uuid'>;
|
export declare type ImageResultResponse = Omit<Image, 'uuid'> & {
|
||||||
|
boundingBox?: IRect;
|
||||||
|
generationMode: InvokeTabName;
|
||||||
|
};
|
||||||
|
|
||||||
export declare type ImageUploadResponse = Omit<Image, 'uuid' | 'metadata'> & {
|
export declare type ImageUploadResponse = {
|
||||||
destination: 'img2img' | 'inpainting';
|
// image: Omit<Image, 'uuid' | 'metadata' | 'category'>;
|
||||||
|
url: string;
|
||||||
|
mtime: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
thumbnail: string;
|
||||||
|
// bbox: [number, number, number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
export declare type ErrorResponse = {
|
export declare type ErrorResponse = {
|
||||||
@ -198,9 +216,12 @@ export declare type ImageUrlResponse = {
|
|||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export declare type ImageUploadDestination = 'img2img' | 'inpainting';
|
|
||||||
|
|
||||||
export declare type UploadImagePayload = {
|
export declare type UploadImagePayload = {
|
||||||
file: File;
|
file: File;
|
||||||
destination?: ImageUploadDestination;
|
destination?: ImageUploadDestination;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export declare type UploadOutpaintingMergeImagePayload = {
|
||||||
|
dataURL: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
@ -1,39 +1,35 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { RootState } from '../store';
|
import { RootState } from 'app/store';
|
||||||
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
|
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||||
import { OptionsState } from '../../features/options/optionsSlice';
|
import { OptionsState } from 'features/options/store/optionsSlice';
|
||||||
|
import { SystemState } from 'features/system/store/systemSlice';
|
||||||
import { SystemState } from '../../features/system/systemSlice';
|
import { validateSeedWeights } from 'common/util/seedWeightPairs';
|
||||||
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
|
import { initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { validateSeedWeights } from '../../common/util/seedWeightPairs';
|
|
||||||
|
|
||||||
export const readinessSelector = createSelector(
|
export const readinessSelector = createSelector(
|
||||||
[
|
[
|
||||||
(state: RootState) => state.options,
|
(state: RootState) => state.options,
|
||||||
(state: RootState) => state.system,
|
(state: RootState) => state.system,
|
||||||
(state: RootState) => state.inpainting,
|
initialCanvasImageSelector,
|
||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
],
|
],
|
||||||
(
|
(
|
||||||
options: OptionsState,
|
options: OptionsState,
|
||||||
system: SystemState,
|
system: SystemState,
|
||||||
inpainting: InpaintingState,
|
initialCanvasImage,
|
||||||
activeTabName
|
activeTabName
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
prompt,
|
prompt,
|
||||||
shouldGenerateVariations,
|
shouldGenerateVariations,
|
||||||
seedWeights,
|
seedWeights,
|
||||||
// maskPath,
|
|
||||||
initialImage,
|
initialImage,
|
||||||
seed,
|
seed,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const { isProcessing, isConnected } = system;
|
const { isProcessing, isConnected } = system;
|
||||||
|
|
||||||
const { imageToInpaint } = inpainting;
|
|
||||||
|
|
||||||
let isReady = true;
|
let isReady = true;
|
||||||
const reasonsWhyNotReady: string[] = [];
|
const reasonsWhyNotReady: string[] = [];
|
||||||
|
|
||||||
@ -48,20 +44,6 @@ export const readinessSelector = createSelector(
|
|||||||
reasonsWhyNotReady.push('No initial image selected');
|
reasonsWhyNotReady.push('No initial image selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'inpainting' && !imageToInpaint) {
|
|
||||||
isReady = false;
|
|
||||||
reasonsWhyNotReady.push('No inpainting image selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
// // 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
|
// TODO: job queue
|
||||||
// Cannot generate if already processing an image
|
// Cannot generate if already processing an image
|
||||||
if (isProcessing) {
|
if (isProcessing) {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { GalleryCategory } from '../../features/gallery/gallerySlice';
|
import { GalleryCategory } from 'features/gallery/store/gallerySlice';
|
||||||
import { InvokeTabName } from '../../features/tabs/InvokeTabs';
|
import { InvokeTabName } from 'features/tabs/components/InvokeTabs';
|
||||||
import * as InvokeAI from '../invokeai';
|
import * as InvokeAI from 'app/invokeai';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We can't use redux-toolkit's createSlice() to make these actions,
|
* We can't use redux-toolkit's createSlice() to make these actions,
|
||||||
@ -26,8 +25,6 @@ export const requestNewImages = createAction<GalleryCategory>(
|
|||||||
export const cancelProcessing = createAction<undefined>(
|
export const cancelProcessing = createAction<undefined>(
|
||||||
'socketio/cancelProcessing'
|
'socketio/cancelProcessing'
|
||||||
);
|
);
|
||||||
export const uploadImage = createAction<InvokeAI.UploadImagePayload>('socketio/uploadImage');
|
|
||||||
export const uploadMaskImage = createAction<File>('socketio/uploadMaskImage');
|
|
||||||
|
|
||||||
export const requestSystemConfig = createAction<undefined>(
|
export const requestSystemConfig = createAction<undefined>(
|
||||||
'socketio/requestSystemConfig'
|
'socketio/requestSystemConfig'
|
||||||
@ -36,3 +33,11 @@ export const requestSystemConfig = createAction<undefined>(
|
|||||||
export const requestModelChange = createAction<string>(
|
export const requestModelChange = createAction<string>(
|
||||||
'socketio/requestModelChange'
|
'socketio/requestModelChange'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const saveStagingAreaImageToGallery = createAction<string>(
|
||||||
|
'socketio/saveStagingAreaImageToGallery'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const emptyTempFolder = createAction<undefined>(
|
||||||
|
'socketio/requestEmptyTempFolder'
|
||||||
|
);
|
||||||
|
@ -4,23 +4,22 @@ import { Socket } from 'socket.io-client';
|
|||||||
import {
|
import {
|
||||||
frontendToBackendParameters,
|
frontendToBackendParameters,
|
||||||
FrontendToBackendParametersConfig,
|
FrontendToBackendParametersConfig,
|
||||||
} from '../../common/util/parameterTranslation';
|
} from 'common/util/parameterTranslation';
|
||||||
import {
|
import {
|
||||||
GalleryCategory,
|
GalleryCategory,
|
||||||
GalleryState,
|
GalleryState,
|
||||||
removeImage,
|
removeImage,
|
||||||
} from '../../features/gallery/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { OptionsState } from '../../features/options/optionsSlice';
|
import { OptionsState } from 'features/options/store/optionsSlice';
|
||||||
import {
|
import {
|
||||||
addLogEntry,
|
addLogEntry,
|
||||||
errorOccurred,
|
generationRequested,
|
||||||
modelChangeRequested,
|
modelChangeRequested,
|
||||||
setIsProcessing,
|
setIsProcessing,
|
||||||
} from '../../features/system/systemSlice';
|
} from 'features/system/store/systemSlice';
|
||||||
import { inpaintingImageElementRef } from '../../features/tabs/Inpainting/InpaintingCanvas';
|
import { InvokeTabName } from 'features/tabs/components/InvokeTabs';
|
||||||
import { InvokeTabName } from '../../features/tabs/InvokeTabs';
|
import * as InvokeAI from 'app/invokeai';
|
||||||
import * as InvokeAI from '../invokeai';
|
import { RootState } from 'app/store';
|
||||||
import { RootState } from '../store';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object containing all functions which use `socketio.emit()`.
|
* Returns an object containing all functions which use `socketio.emit()`.
|
||||||
@ -42,7 +41,7 @@ const makeSocketIOEmitters = (
|
|||||||
const {
|
const {
|
||||||
options: optionsState,
|
options: optionsState,
|
||||||
system: systemState,
|
system: systemState,
|
||||||
inpainting: inpaintingState,
|
canvas: canvasState,
|
||||||
gallery: galleryState,
|
gallery: galleryState,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
@ -50,32 +49,13 @@ const makeSocketIOEmitters = (
|
|||||||
{
|
{
|
||||||
generationMode,
|
generationMode,
|
||||||
optionsState,
|
optionsState,
|
||||||
inpaintingState,
|
canvasState,
|
||||||
systemState,
|
systemState,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (generationMode === 'inpainting') {
|
dispatch(generationRequested());
|
||||||
if (
|
|
||||||
!inpaintingImageElementRef.current ||
|
|
||||||
!inpaintingState.imageToInpaint?.url
|
|
||||||
) {
|
|
||||||
dispatch(
|
|
||||||
addLogEntry({
|
|
||||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
|
||||||
message: 'Inpainting image not loaded, cannot generate image.',
|
|
||||||
level: 'error',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
dispatch(errorOccurred());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
frontendToBackendParametersConfig.imageToProcessUrl =
|
if (!['txt2img', 'img2img'].includes(generationMode)) {
|
||||||
inpaintingState.imageToInpaint.url;
|
|
||||||
|
|
||||||
frontendToBackendParametersConfig.maskImageElement =
|
|
||||||
inpaintingImageElementRef.current;
|
|
||||||
} else if (!['txt2img', 'img2img'].includes(generationMode)) {
|
|
||||||
if (!galleryState.currentImage?.url) return;
|
if (!galleryState.currentImage?.url) return;
|
||||||
|
|
||||||
frontendToBackendParametersConfig.imageToProcessUrl =
|
frontendToBackendParametersConfig.imageToProcessUrl =
|
||||||
@ -96,7 +76,12 @@ const makeSocketIOEmitters = (
|
|||||||
// TODO: handle maintaining masks for reproducibility in future
|
// TODO: handle maintaining masks for reproducibility in future
|
||||||
if (generationParameters.init_mask) {
|
if (generationParameters.init_mask) {
|
||||||
generationParameters.init_mask = generationParameters.init_mask
|
generationParameters.init_mask = generationParameters.init_mask
|
||||||
.substr(0, 20)
|
.substr(0, 64)
|
||||||
|
.concat('...');
|
||||||
|
}
|
||||||
|
if (generationParameters.init_img) {
|
||||||
|
generationParameters.init_img = generationParameters.init_img
|
||||||
|
.substr(0, 64)
|
||||||
.concat('...');
|
.concat('...');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,9 +147,9 @@ const makeSocketIOEmitters = (
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
emitDeleteImage: (imageToDelete: InvokeAI.Image) => {
|
emitDeleteImage: (imageToDelete: InvokeAI.Image) => {
|
||||||
const { url, uuid, category } = imageToDelete;
|
const { url, uuid, category, thumbnail } = imageToDelete;
|
||||||
dispatch(removeImage(imageToDelete));
|
dispatch(removeImage(imageToDelete));
|
||||||
socketio.emit('deleteImage', url, uuid, category);
|
socketio.emit('deleteImage', url, thumbnail, uuid, category);
|
||||||
},
|
},
|
||||||
emitRequestImages: (category: GalleryCategory) => {
|
emitRequestImages: (category: GalleryCategory) => {
|
||||||
const gallery: GalleryState = getState().gallery;
|
const gallery: GalleryState = getState().gallery;
|
||||||
@ -179,13 +164,6 @@ const makeSocketIOEmitters = (
|
|||||||
emitCancelProcessing: () => {
|
emitCancelProcessing: () => {
|
||||||
socketio.emit('cancel');
|
socketio.emit('cancel');
|
||||||
},
|
},
|
||||||
emitUploadImage: (payload: InvokeAI.UploadImagePayload) => {
|
|
||||||
const { file, destination } = payload;
|
|
||||||
socketio.emit('uploadImage', file, file.name, destination);
|
|
||||||
},
|
|
||||||
emitUploadMaskImage: (file: File) => {
|
|
||||||
socketio.emit('uploadMaskImage', file, file.name);
|
|
||||||
},
|
|
||||||
emitRequestSystemConfig: () => {
|
emitRequestSystemConfig: () => {
|
||||||
socketio.emit('requestSystemConfig');
|
socketio.emit('requestSystemConfig');
|
||||||
},
|
},
|
||||||
@ -193,6 +171,12 @@ const makeSocketIOEmitters = (
|
|||||||
dispatch(modelChangeRequested());
|
dispatch(modelChangeRequested());
|
||||||
socketio.emit('requestModelChange', modelName);
|
socketio.emit('requestModelChange', modelName);
|
||||||
},
|
},
|
||||||
|
emitSaveStagingAreaImageToGallery: (url: string) => {
|
||||||
|
socketio.emit('requestSaveStagingAreaImageToGallery', url);
|
||||||
|
},
|
||||||
|
emitRequestEmptyTempFolder: () => {
|
||||||
|
socketio.emit('requestEmptyTempFolder');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { AnyAction, MiddlewareAPI, Dispatch } from '@reduxjs/toolkit';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import dateFormat from 'dateformat';
|
import dateFormat from 'dateformat';
|
||||||
|
|
||||||
import * as InvokeAI from '../invokeai';
|
import * as InvokeAI from 'app/invokeai';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addLogEntry,
|
addLogEntry,
|
||||||
@ -15,7 +15,8 @@ import {
|
|||||||
errorOccurred,
|
errorOccurred,
|
||||||
setModelList,
|
setModelList,
|
||||||
setIsCancelable,
|
setIsCancelable,
|
||||||
} from '../../features/system/systemSlice';
|
addToast,
|
||||||
|
} from 'features/system/store/systemSlice';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addGalleryImages,
|
addGalleryImages,
|
||||||
@ -23,21 +24,22 @@ import {
|
|||||||
clearIntermediateImage,
|
clearIntermediateImage,
|
||||||
GalleryState,
|
GalleryState,
|
||||||
removeImage,
|
removeImage,
|
||||||
setCurrentImage,
|
|
||||||
setIntermediateImage,
|
setIntermediateImage,
|
||||||
} from '../../features/gallery/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearInitialImage,
|
clearInitialImage,
|
||||||
|
setInfillMethod,
|
||||||
setInitialImage,
|
setInitialImage,
|
||||||
setMaskPath,
|
setMaskPath,
|
||||||
} from '../../features/options/optionsSlice';
|
} from 'features/options/store/optionsSlice';
|
||||||
import { requestImages, requestNewImages } from './actions';
|
|
||||||
import {
|
import {
|
||||||
clearImageToInpaint,
|
requestImages,
|
||||||
setImageToInpaint,
|
requestNewImages,
|
||||||
} from '../../features/tabs/Inpainting/inpaintingSlice';
|
requestSystemConfig,
|
||||||
import { tabMap } from '../../features/tabs/InvokeTabs';
|
} from './actions';
|
||||||
|
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { tabMap } from 'features/tabs/components/InvokeTabs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object containing listener callbacks for socketio events.
|
* Returns an object containing listener callbacks for socketio events.
|
||||||
@ -56,6 +58,7 @@ const makeSocketIOListeners = (
|
|||||||
try {
|
try {
|
||||||
dispatch(setIsConnected(true));
|
dispatch(setIsConnected(true));
|
||||||
dispatch(setCurrentStatus('Connected'));
|
dispatch(setCurrentStatus('Connected'));
|
||||||
|
dispatch(requestSystemConfig());
|
||||||
const gallery: GalleryState = getState().gallery;
|
const gallery: GalleryState = getState().gallery;
|
||||||
|
|
||||||
if (gallery.categories.user.latest_mtime) {
|
if (gallery.categories.user.latest_mtime) {
|
||||||
@ -97,19 +100,42 @@ const makeSocketIOListeners = (
|
|||||||
*/
|
*/
|
||||||
onGenerationResult: (data: InvokeAI.ImageResultResponse) => {
|
onGenerationResult: (data: InvokeAI.ImageResultResponse) => {
|
||||||
try {
|
try {
|
||||||
const { shouldLoopback, activeTab } = getState().options;
|
const state = getState();
|
||||||
|
const { shouldLoopback, activeTab } = state.options;
|
||||||
|
const { boundingBox: _, generationMode, ...rest } = data;
|
||||||
|
|
||||||
const newImage = {
|
const newImage = {
|
||||||
uuid: uuidv4(),
|
uuid: uuidv4(),
|
||||||
...data,
|
...rest,
|
||||||
category: 'result',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(
|
if (['txt2img', 'img2img'].includes(generationMode)) {
|
||||||
addImage({
|
dispatch(
|
||||||
category: 'result',
|
addImage({
|
||||||
image: newImage,
|
category: 'result',
|
||||||
})
|
image: { ...newImage, category: 'result' },
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generationMode === 'unifiedCanvas' && data.boundingBox) {
|
||||||
|
const { boundingBox } = data;
|
||||||
|
dispatch(
|
||||||
|
addImageToStagingArea({
|
||||||
|
image: { ...newImage, category: 'temp' },
|
||||||
|
boundingBox,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (state.canvas.shouldAutoSave) {
|
||||||
|
dispatch(
|
||||||
|
addImage({
|
||||||
|
image: { ...newImage, category: 'result' },
|
||||||
|
category: 'result',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldLoopback) {
|
if (shouldLoopback) {
|
||||||
const activeTabName = tabMap[activeTab];
|
const activeTabName = tabMap[activeTab];
|
||||||
@ -118,13 +144,11 @@ const makeSocketIOListeners = (
|
|||||||
dispatch(setInitialImage(newImage));
|
dispatch(setInitialImage(newImage));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'inpainting': {
|
|
||||||
dispatch(setImageToInpaint(newImage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch(clearIntermediateImage());
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addLogEntry({
|
addLogEntry({
|
||||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
||||||
@ -144,6 +168,7 @@ const makeSocketIOListeners = (
|
|||||||
setIntermediateImage({
|
setIntermediateImage({
|
||||||
uuid: uuidv4(),
|
uuid: uuidv4(),
|
||||||
...data,
|
...data,
|
||||||
|
category: 'result',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (!data.isBase64) {
|
if (!data.isBase64) {
|
||||||
@ -299,16 +324,11 @@ const makeSocketIOListeners = (
|
|||||||
|
|
||||||
// remove references to image in options
|
// remove references to image in options
|
||||||
const { initialImage, maskPath } = getState().options;
|
const { initialImage, maskPath } = getState().options;
|
||||||
const { imageToInpaint } = getState().inpainting;
|
|
||||||
|
|
||||||
if (initialImage?.url === url || initialImage === url) {
|
if (initialImage?.url === url || initialImage === url) {
|
||||||
dispatch(clearInitialImage());
|
dispatch(clearInitialImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageToInpaint?.url === url) {
|
|
||||||
dispatch(clearImageToInpaint());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maskPath === url) {
|
if (maskPath === url) {
|
||||||
dispatch(setMaskPath(''));
|
dispatch(setMaskPath(''));
|
||||||
}
|
}
|
||||||
@ -320,56 +340,11 @@ const makeSocketIOListeners = (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onImageUploaded: (data: InvokeAI.ImageUploadResponse) => {
|
|
||||||
const { destination, ...rest } = data;
|
|
||||||
const image = {
|
|
||||||
uuid: uuidv4(),
|
|
||||||
...rest,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
dispatch(addImage({ image, category: 'user' }));
|
|
||||||
|
|
||||||
switch (destination) {
|
|
||||||
case 'img2img': {
|
|
||||||
dispatch(setInitialImage(image));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'inpainting': {
|
|
||||||
dispatch(setImageToInpaint(image));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
dispatch(setCurrentImage(image));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
addLogEntry({
|
|
||||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
|
||||||
message: `Image uploaded: ${data.url}`,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Callback to run when we receive a 'maskImageUploaded' event.
|
|
||||||
*/
|
|
||||||
onMaskImageUploaded: (data: InvokeAI.ImageUrlResponse) => {
|
|
||||||
const { url } = data;
|
|
||||||
dispatch(setMaskPath(url));
|
|
||||||
dispatch(
|
|
||||||
addLogEntry({
|
|
||||||
timestamp: dateFormat(new Date(), 'isoDateTime'),
|
|
||||||
message: `Mask image uploaded: ${url}`,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onSystemConfig: (data: InvokeAI.SystemConfig) => {
|
onSystemConfig: (data: InvokeAI.SystemConfig) => {
|
||||||
dispatch(setSystemConfig(data));
|
dispatch(setSystemConfig(data));
|
||||||
|
if (!data.infill_methods.includes('patchmatch')) {
|
||||||
|
dispatch(setInfillMethod(data.infill_methods[0]));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onModelChanged: (data: InvokeAI.ModelChangeResponse) => {
|
onModelChanged: (data: InvokeAI.ModelChangeResponse) => {
|
||||||
const { model_name, model_list } = data;
|
const { model_name, model_list } = data;
|
||||||
@ -399,6 +374,16 @@ const makeSocketIOListeners = (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
onTempFolderEmptied: () => {
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: 'Temp Folder Emptied',
|
||||||
|
status: 'success',
|
||||||
|
duration: 2500,
|
||||||
|
isClosable: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { io } from 'socket.io-client';
|
|||||||
import makeSocketIOListeners from './listeners';
|
import makeSocketIOListeners from './listeners';
|
||||||
import makeSocketIOEmitters from './emitters';
|
import makeSocketIOEmitters from './emitters';
|
||||||
|
|
||||||
import * as InvokeAI from '../invokeai';
|
import * as InvokeAI from 'app/invokeai';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a socketio middleware to handle communication with server.
|
* Creates a socketio middleware to handle communication with server.
|
||||||
@ -43,11 +43,10 @@ export const socketioMiddleware = () => {
|
|||||||
onGalleryImages,
|
onGalleryImages,
|
||||||
onProcessingCanceled,
|
onProcessingCanceled,
|
||||||
onImageDeleted,
|
onImageDeleted,
|
||||||
onImageUploaded,
|
|
||||||
onMaskImageUploaded,
|
|
||||||
onSystemConfig,
|
onSystemConfig,
|
||||||
onModelChanged,
|
onModelChanged,
|
||||||
onModelChangeFailed,
|
onModelChangeFailed,
|
||||||
|
onTempFolderEmptied,
|
||||||
} = makeSocketIOListeners(store);
|
} = makeSocketIOListeners(store);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -58,10 +57,10 @@ export const socketioMiddleware = () => {
|
|||||||
emitRequestImages,
|
emitRequestImages,
|
||||||
emitRequestNewImages,
|
emitRequestNewImages,
|
||||||
emitCancelProcessing,
|
emitCancelProcessing,
|
||||||
emitUploadImage,
|
|
||||||
emitUploadMaskImage,
|
|
||||||
emitRequestSystemConfig,
|
emitRequestSystemConfig,
|
||||||
emitRequestModelChange,
|
emitRequestModelChange,
|
||||||
|
emitSaveStagingAreaImageToGallery,
|
||||||
|
emitRequestEmptyTempFolder,
|
||||||
} = makeSocketIOEmitters(store, socketio);
|
} = makeSocketIOEmitters(store, socketio);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,17 +103,6 @@ export const socketioMiddleware = () => {
|
|||||||
onImageDeleted(data);
|
onImageDeleted(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socketio.on(
|
|
||||||
'imageUploaded',
|
|
||||||
(data: InvokeAI.ImageUploadResponse) => {
|
|
||||||
onImageUploaded(data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
socketio.on('maskImageUploaded', (data: InvokeAI.ImageUrlResponse) => {
|
|
||||||
onMaskImageUploaded(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => {
|
socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => {
|
||||||
onSystemConfig(data);
|
onSystemConfig(data);
|
||||||
});
|
});
|
||||||
@ -127,6 +115,10 @@ export const socketioMiddleware = () => {
|
|||||||
onModelChangeFailed(data);
|
onModelChangeFailed(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socketio.on('tempFolderEmptied', () => {
|
||||||
|
onTempFolderEmptied();
|
||||||
|
});
|
||||||
|
|
||||||
areListenersSet = true;
|
areListenersSet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,16 +161,6 @@ export const socketioMiddleware = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'socketio/uploadImage': {
|
|
||||||
emitUploadImage(action.payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'socketio/uploadMaskImage': {
|
|
||||||
emitUploadMaskImage(action.payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'socketio/requestSystemConfig': {
|
case 'socketio/requestSystemConfig': {
|
||||||
emitRequestSystemConfig();
|
emitRequestSystemConfig();
|
||||||
break;
|
break;
|
||||||
@ -188,6 +170,16 @@ export const socketioMiddleware = () => {
|
|||||||
emitRequestModelChange(action.payload);
|
emitRequestModelChange(action.payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'socketio/saveStagingAreaImageToGallery': {
|
||||||
|
emitSaveStagingAreaImageToGallery(action.payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'socketio/requestEmptyTempFolder': {
|
||||||
|
emitRequestEmptyTempFolder();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next(action);
|
next(action);
|
||||||
|
@ -5,16 +5,14 @@ import type { TypedUseSelectorHook } from 'react-redux';
|
|||||||
import { persistReducer } from 'redux-persist';
|
import { persistReducer } from 'redux-persist';
|
||||||
import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
|
import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
|
||||||
|
|
||||||
import optionsReducer, { OptionsState } from '../features/options/optionsSlice';
|
import { getPersistConfig } from 'redux-deep-persist';
|
||||||
import galleryReducer, { GalleryState } from '../features/gallery/gallerySlice';
|
|
||||||
import inpaintingReducer, {
|
import optionsReducer from 'features/options/store/optionsSlice';
|
||||||
InpaintingState,
|
import galleryReducer from 'features/gallery/store/gallerySlice';
|
||||||
} from '../features/tabs/Inpainting/inpaintingSlice';
|
import systemReducer from 'features/system/store/systemSlice';
|
||||||
|
import canvasReducer from 'features/canvas/store/canvasSlice';
|
||||||
|
|
||||||
import systemReducer, { SystemState } from '../features/system/systemSlice';
|
|
||||||
import { socketioMiddleware } from './socketio/middleware';
|
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.
|
* redux-persist provides an easy and reliable way to persist state across reloads.
|
||||||
@ -28,87 +26,79 @@ import { PersistPartial } from 'redux-persist/es/persistReducer';
|
|||||||
* These can be blacklisted in redux-persist.
|
* These can be blacklisted in redux-persist.
|
||||||
*
|
*
|
||||||
* The necesssary nested persistors with blacklists are configured below.
|
* The necesssary nested persistors with blacklists are configured below.
|
||||||
*
|
|
||||||
* TODO: Do we blacklist initialImagePath? If the image is deleted from disk we get an
|
|
||||||
* ugly 404. But if we blacklist it, then this is a valuable parameter that is lost
|
|
||||||
* on reload. Need to figure out a good way to handle this.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const rootPersistConfig = {
|
const canvasBlacklist = [
|
||||||
key: 'root',
|
'cursorPosition',
|
||||||
storage,
|
'isCanvasInitialized',
|
||||||
stateReconciler: autoMergeLevel2,
|
'doesCanvasNeedScaling',
|
||||||
blacklist: ['gallery', 'system', 'inpainting'],
|
].map((blacklistItem) => `canvas.${blacklistItem}`);
|
||||||
};
|
|
||||||
|
|
||||||
const systemPersistConfig = {
|
const systemBlacklist = [
|
||||||
key: 'system',
|
'currentIteration',
|
||||||
storage,
|
'currentStatus',
|
||||||
stateReconciler: autoMergeLevel2,
|
'currentStep',
|
||||||
blacklist: [
|
'isCancelable',
|
||||||
'isCancelable',
|
'isConnected',
|
||||||
'isConnected',
|
'isESRGANAvailable',
|
||||||
'isProcessing',
|
'isGFPGANAvailable',
|
||||||
'currentStep',
|
'isProcessing',
|
||||||
'socketId',
|
'socketId',
|
||||||
'isESRGANAvailable',
|
'totalIterations',
|
||||||
'isGFPGANAvailable',
|
'totalSteps',
|
||||||
'currentStep',
|
].map((blacklistItem) => `system.${blacklistItem}`);
|
||||||
'totalSteps',
|
|
||||||
'currentIteration',
|
|
||||||
'totalIterations',
|
|
||||||
'currentStatus',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const galleryPersistConfig = {
|
const galleryBlacklist = [
|
||||||
key: 'gallery',
|
'categories',
|
||||||
storage,
|
'currentCategory',
|
||||||
stateReconciler: autoMergeLevel2,
|
'currentImage',
|
||||||
whitelist: [
|
'currentImageUuid',
|
||||||
'galleryWidth',
|
'shouldAutoSwitchToNewImages',
|
||||||
'shouldPinGallery',
|
'shouldHoldGalleryOpen',
|
||||||
'shouldShowGallery',
|
'intermediateImage',
|
||||||
'galleryScrollPosition',
|
].map((blacklistItem) => `gallery.${blacklistItem}`);
|
||||||
'galleryImageMinimumWidth',
|
|
||||||
'galleryImageObjectFit',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const inpaintingPersistConfig = {
|
const rootReducer = combineReducers({
|
||||||
key: 'inpainting',
|
|
||||||
storage,
|
|
||||||
stateReconciler: autoMergeLevel2,
|
|
||||||
blacklist: ['pastLines', 'futuresLines', 'cursorPosition'],
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducers = combineReducers({
|
|
||||||
options: optionsReducer,
|
options: optionsReducer,
|
||||||
gallery: persistReducer<GalleryState>(galleryPersistConfig, galleryReducer),
|
gallery: galleryReducer,
|
||||||
system: persistReducer<SystemState>(systemPersistConfig, systemReducer),
|
system: systemReducer,
|
||||||
inpainting: persistReducer<InpaintingState>(
|
canvas: canvasReducer,
|
||||||
inpaintingPersistConfig,
|
|
||||||
inpaintingReducer
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const persistedReducer = persistReducer<{
|
const rootPersistConfig = getPersistConfig({
|
||||||
options: OptionsState;
|
key: 'root',
|
||||||
gallery: GalleryState & PersistPartial;
|
storage,
|
||||||
system: SystemState & PersistPartial;
|
rootReducer,
|
||||||
inpainting: InpaintingState & PersistPartial;
|
blacklist: [...canvasBlacklist, ...systemBlacklist, ...galleryBlacklist],
|
||||||
}>(rootPersistConfig, reducers);
|
debounce: 300,
|
||||||
|
});
|
||||||
|
|
||||||
|
const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
|
||||||
|
|
||||||
// Continue with store setup
|
// Continue with store setup
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: persistedReducer,
|
reducer: persistedReducer,
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
// redux-persist sometimes needs to temporarily put a function in redux state, need to disable this check
|
immutableCheck: false,
|
||||||
serializableCheck: false,
|
serializableCheck: false,
|
||||||
}).concat(socketioMiddleware()),
|
}).concat(socketioMiddleware()),
|
||||||
|
devTools: {
|
||||||
|
// Uncommenting these very rapidly called actions makes the redux dev tools output much more readable
|
||||||
|
actionsDenylist: [
|
||||||
|
'canvas/setCursorPosition',
|
||||||
|
'canvas/setStageCoordinates',
|
||||||
|
'canvas/setStageScale',
|
||||||
|
'canvas/setIsDrawing',
|
||||||
|
// 'canvas/setBoundingBoxCoordinates',
|
||||||
|
// 'canvas/setBoundingBoxDimensions',
|
||||||
|
'canvas/setIsDrawing',
|
||||||
|
'canvas/addPointToCurrentLine',
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type AppGetState = typeof store.getState;
|
||||||
export type RootState = ReturnType<typeof store.getState>;
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
||||||
|
BIN
frontend/src/assets/images/mask.afdesign
Normal file
77
frontend/src/assets/images/mask.svg
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 60 60" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
<g transform="matrix(1,0,0,1,0,5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,10)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,15)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,20)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,25)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,30)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,35)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,40)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,45)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,50)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,55)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,60)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-10)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-15)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-20)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-25)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-30)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-35)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-40)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-45)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-50)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-55)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,0,-60)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
@ -1,7 +1,7 @@
|
|||||||
import { Box, forwardRef, Icon } from '@chakra-ui/react';
|
import { Box, forwardRef, Icon } from '@chakra-ui/react';
|
||||||
import { IconType } from 'react-icons';
|
import { IconType } from 'react-icons';
|
||||||
import { MdHelp } from 'react-icons/md';
|
import { MdHelp } from 'react-icons/md';
|
||||||
import { Feature } from '../../app/features';
|
import { Feature } from 'app/features';
|
||||||
import GuidePopover from './GuidePopover';
|
import GuidePopover from './GuidePopover';
|
||||||
|
|
||||||
type GuideIconProps = {
|
type GuideIconProps = {
|
||||||
@ -13,7 +13,7 @@ const GuideIcon = forwardRef(
|
|||||||
({ feature, icon = MdHelp }: GuideIconProps, ref) => (
|
({ feature, icon = MdHelp }: GuideIconProps, ref) => (
|
||||||
<GuidePopover feature={feature}>
|
<GuidePopover feature={feature}>
|
||||||
<Box ref={ref}>
|
<Box ref={ref}>
|
||||||
<Icon as={icon} />
|
<Icon marginBottom={'-.15rem'} as={icon} />
|
||||||
</Box>
|
</Box>
|
||||||
</GuidePopover>
|
</GuidePopover>
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
.guide-popover-arrow {
|
.guide-popover-arrow {
|
||||||
background-color: var(--tab-panel-bg) !important;
|
background-color: var(--tab-panel-bg);
|
||||||
box-shadow: none !important;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-popover-content {
|
.guide-popover-content {
|
||||||
background-color: var(--background-color-secondary) !important;
|
background-color: var(--background-color-secondary);
|
||||||
border: none !important;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-popover-guide-content {
|
.guide-popover-guide-content {
|
||||||
|
@ -5,12 +5,12 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
Box,
|
Box,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { SystemState } from '../../features/system/systemSlice';
|
import { SystemState } from 'features/system/store/systemSlice';
|
||||||
import { useAppSelector } from '../../app/store';
|
import { useAppSelector } from 'app/store';
|
||||||
import { RootState } from '../../app/store';
|
import { RootState } from 'app/store';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import { Feature, FEATURES } from '../../app/features';
|
import { Feature, FEATURES } from 'app/features';
|
||||||
|
|
||||||
type GuideProps = {
|
type GuideProps = {
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
|
86
frontend/src/common/components/IAIAlertDialog.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
Button,
|
||||||
|
forwardRef,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { cloneElement, ReactElement, ReactNode, useRef } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
acceptButtonText?: string;
|
||||||
|
acceptCallback: () => void;
|
||||||
|
cancelButtonText?: string;
|
||||||
|
cancelCallback?: () => void;
|
||||||
|
children: ReactNode;
|
||||||
|
title: string;
|
||||||
|
triggerComponent: ReactElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IAIAlertDialog = forwardRef((props: Props, ref) => {
|
||||||
|
const {
|
||||||
|
acceptButtonText = 'Accept',
|
||||||
|
acceptCallback,
|
||||||
|
cancelButtonText = 'Cancel',
|
||||||
|
cancelCallback,
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
triggerComponent,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
|
const handleAccept = () => {
|
||||||
|
acceptCallback();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
cancelCallback && cancelCallback();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{cloneElement(triggerComponent, {
|
||||||
|
onClick: onOpen,
|
||||||
|
ref: ref,
|
||||||
|
})}
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
isOpen={isOpen}
|
||||||
|
leastDestructiveRef={cancelRef}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<AlertDialogOverlay>
|
||||||
|
<AlertDialogContent className="modal">
|
||||||
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
|
{title}
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogBody>{children}</AlertDialogBody>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<Button
|
||||||
|
ref={cancelRef}
|
||||||
|
onClick={handleCancel}
|
||||||
|
className="modal-close-btn"
|
||||||
|
>
|
||||||
|
{cancelButtonText}
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="red" onClick={handleAccept} ml={3}>
|
||||||
|
{acceptButtonText}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
export default IAIAlertDialog;
|
@ -1,3 +1,8 @@
|
|||||||
.invokeai__button {
|
.invokeai__button {
|
||||||
justify-content: space-between;
|
background-color: var(--btn-base-color);
|
||||||
|
place-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--btn-base-color-hover);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
svg {
|
svg {
|
||||||
width: 0.6rem;
|
width: 0.6rem;
|
||||||
height: 0.6rem;
|
height: 0.6rem;
|
||||||
stroke-width: 3px !important;
|
stroke-width: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-checked] {
|
&[data-checked] {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
@use '../../styles/Mixins/' as *;
|
@use '../../styles/Mixins/' as *;
|
||||||
|
|
||||||
.invokeai__icon-button {
|
.invokeai__icon-button {
|
||||||
background-color: var(--btn-grey);
|
background: var(--btn-base-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--btn-grey-hover);
|
background-color: var(--btn-base-color-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-selected='true'] {
|
&[data-selected='true'] {
|
||||||
@ -20,16 +20,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[data-variant='link'] {
|
&[data-variant='link'] {
|
||||||
background: none !important;
|
background: none;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: none !important;
|
background: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-selected='true'] {
|
// Check Box Style
|
||||||
border-color: var(--accent-color);
|
&[data-as-checkbox='true'] {
|
||||||
|
background-color: var(--btn-base-color);
|
||||||
|
border: 3px solid var(--btn-base-color);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--accent-color-hover);
|
background-color: var(--btn-base-color);
|
||||||
|
border-color: var(--btn-checkbox-border-hover);
|
||||||
|
svg {
|
||||||
|
fill: var(--text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-selected='true'] {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
svg {
|
||||||
|
fill: var(--accent-color-hover);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
svg {
|
||||||
|
fill: var(--accent-color-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,28 +61,12 @@
|
|||||||
animation-duration: 1s;
|
animation-duration: 1s;
|
||||||
animation-timing-function: ease-in-out;
|
animation-timing-function: ease-in-out;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
animation: none;
|
animation: none;
|
||||||
background-color: var(--accent-color-hover);
|
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 {
|
@keyframes pulseColor {
|
||||||
|
@ -25,13 +25,23 @@ const IAIIconButton = forwardRef((props: IAIIconButtonProps, forwardedRef) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} hasArrow {...tooltipProps}>
|
<Tooltip
|
||||||
|
label={tooltip}
|
||||||
|
hasArrow
|
||||||
|
{...tooltipProps}
|
||||||
|
{...(tooltipProps?.placement
|
||||||
|
? { placement: tooltipProps.placement }
|
||||||
|
: { placement: 'top' })}
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
className={`invokeai__icon-button ${styleClass}`}
|
className={
|
||||||
|
styleClass
|
||||||
|
? `invokeai__icon-button ${styleClass}`
|
||||||
|
: `invokeai__icon-button`
|
||||||
|
}
|
||||||
data-as-checkbox={asCheckbox}
|
data-as-checkbox={asCheckbox}
|
||||||
data-selected={isChecked !== undefined ? isChecked : undefined}
|
data-selected={isChecked !== undefined ? isChecked : undefined}
|
||||||
style={props.onClick ? { cursor: 'pointer' } : {}}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
.invokeai__number-input-form-control {
|
.invokeai__number-input-form-control {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: max-content auto;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
column-gap: 1rem;
|
||||||
|
|
||||||
.invokeai__number-input-form-label {
|
.invokeai__number-input-form-label {
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary);
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
flex-grow: 2;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-right: 1rem;
|
|
||||||
|
|
||||||
&[data-focus] + .invokeai__number-input-root {
|
&[data-focus] + .invokeai__number-input-root {
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -33,7 +31,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: var(--background-color-secondary);
|
background-color: var(--background-color-secondary);
|
||||||
border: 2px solid var(--border-color);
|
border: 2px solid var(--border-color);
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invokeai__number-input-field {
|
.invokeai__number-input-field {
|
||||||
@ -41,10 +39,8 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 0;
|
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding-left: 0.5rem;
|
padding: 0 0.5rem;
|
||||||
padding-right: 0.5rem;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -21,6 +21,7 @@ const numberStringRegex = /^-?(0\.)?\.?$/;
|
|||||||
interface Props extends Omit<NumberInputProps, 'onChange'> {
|
interface Props extends Omit<NumberInputProps, 'onChange'> {
|
||||||
styleClass?: string;
|
styleClass?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
labelFontSize?: string | number;
|
||||||
width?: string | number;
|
width?: string | number;
|
||||||
showStepper?: boolean;
|
showStepper?: boolean;
|
||||||
value: number;
|
value: number;
|
||||||
@ -43,6 +44,7 @@ interface Props extends Omit<NumberInputProps, 'onChange'> {
|
|||||||
const IAINumberInput = (props: Props) => {
|
const IAINumberInput = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
|
labelFontSize = '1rem',
|
||||||
styleClass,
|
styleClass,
|
||||||
isDisabled = false,
|
isDisabled = false,
|
||||||
showStepper = true,
|
showStepper = true,
|
||||||
@ -127,6 +129,7 @@ const IAINumberInput = (props: Props) => {
|
|||||||
<FormLabel
|
<FormLabel
|
||||||
className="invokeai__number-input-form-label"
|
className="invokeai__number-input-form-label"
|
||||||
style={{ display: label ? 'block' : 'none' }}
|
style={{ display: label ? 'block' : 'none' }}
|
||||||
|
fontSize={labelFontSize}
|
||||||
{...formLabelProps}
|
{...formLabelProps}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
.invokeai__popover-content {
|
.invokeai__popover-content {
|
||||||
min-width: unset;
|
min-width: unset;
|
||||||
width: unset !important;
|
width: unset;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 0.5rem !important;
|
border-radius: 0.5rem;
|
||||||
background-color: var(--background-color) !important;
|
background-color: var(--background-color);
|
||||||
border: 2px solid var(--border-color) !important;
|
border: 2px solid var(--border-color);
|
||||||
|
|
||||||
.invokeai__popover-arrow {
|
.invokeai__popover-arrow {
|
||||||
background-color: var(--background-color) !important;
|
background-color: var(--background-color) !important;
|
||||||
|
@ -29,7 +29,7 @@ const IAIPopover = (props: IAIPopoverProps) => {
|
|||||||
<Popover {...rest}>
|
<Popover {...rest}>
|
||||||
<PopoverTrigger>{triggerComponent}</PopoverTrigger>
|
<PopoverTrigger>{triggerComponent}</PopoverTrigger>
|
||||||
<PopoverContent className={`invokeai__popover-content ${styleClass}`}>
|
<PopoverContent className={`invokeai__popover-content ${styleClass}`}>
|
||||||
{hasArrow && <PopoverArrow className={'invokeai__popover-arrow'} />}
|
{hasArrow && <PopoverArrow className="invokeai__popover-arrow" />}
|
||||||
{children}
|
{children}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: max-content;
|
|
||||||
|
|
||||||
.invokeai__select-label {
|
.invokeai__select-label {
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary);
|
||||||
@ -15,6 +14,7 @@
|
|||||||
border: 2px solid var(--border-color);
|
border: 2px solid var(--border-color);
|
||||||
background-color: var(--background-color-secondary);
|
background-color: var(--background-color-secondary);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
|
|
||||||
@ -27,5 +27,6 @@
|
|||||||
|
|
||||||
.invokeai__select-option {
|
.invokeai__select-option {
|
||||||
background-color: var(--background-color-secondary);
|
background-color: var(--background-color-secondary);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
import { FormControl, FormLabel, Select, SelectProps } from '@chakra-ui/react';
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Select,
|
||||||
|
SelectProps,
|
||||||
|
Tooltip,
|
||||||
|
TooltipProps,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { MouseEvent } from 'react';
|
import { MouseEvent } from 'react';
|
||||||
|
|
||||||
type IAISelectProps = SelectProps & {
|
type IAISelectProps = SelectProps & {
|
||||||
label: string;
|
label?: string;
|
||||||
styleClass?: string;
|
styleClass?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
tooltipProps?: Omit<TooltipProps, 'children'>;
|
||||||
validValues:
|
validValues:
|
||||||
| Array<number | string>
|
| Array<number | string>
|
||||||
| Array<{ key: string; value: string | number }>;
|
| Array<{ key: string; value: string | number }>;
|
||||||
@ -16,6 +25,8 @@ const IAISelect = (props: IAISelectProps) => {
|
|||||||
label,
|
label,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
validValues,
|
validValues,
|
||||||
|
tooltip,
|
||||||
|
tooltipProps,
|
||||||
size = 'sm',
|
size = 'sm',
|
||||||
fontSize = 'md',
|
fontSize = 'md',
|
||||||
styleClass,
|
styleClass,
|
||||||
@ -32,37 +43,41 @@ const IAISelect = (props: IAISelectProps) => {
|
|||||||
e.nativeEvent.cancelBubble = true;
|
e.nativeEvent.cancelBubble = true;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormLabel
|
{label && (
|
||||||
className="invokeai__select-label"
|
<FormLabel
|
||||||
fontSize={fontSize}
|
className="invokeai__select-label"
|
||||||
marginBottom={1}
|
fontSize={fontSize}
|
||||||
flexGrow={2}
|
marginBottom={1}
|
||||||
whiteSpace="nowrap"
|
flexGrow={2}
|
||||||
>
|
whiteSpace="nowrap"
|
||||||
{label}
|
>
|
||||||
</FormLabel>
|
{label}
|
||||||
<Select
|
</FormLabel>
|
||||||
className="invokeai__select-picker"
|
)}
|
||||||
fontSize={fontSize}
|
<Tooltip label={tooltip} {...tooltipProps}>
|
||||||
size={size}
|
<Select
|
||||||
{...rest}
|
className="invokeai__select-picker"
|
||||||
>
|
fontSize={fontSize}
|
||||||
{validValues.map((opt) => {
|
size={size}
|
||||||
return typeof opt === 'string' || typeof opt === 'number' ? (
|
{...rest}
|
||||||
<option key={opt} value={opt} className="invokeai__select-option">
|
>
|
||||||
{opt}
|
{validValues.map((opt) => {
|
||||||
</option>
|
return typeof opt === 'string' || typeof opt === 'number' ? (
|
||||||
) : (
|
<option key={opt} value={opt} className="invokeai__select-option">
|
||||||
<option
|
{opt}
|
||||||
key={opt.value}
|
</option>
|
||||||
value={opt.value}
|
) : (
|
||||||
className="invokeai__select-option"
|
<option
|
||||||
>
|
key={opt.value}
|
||||||
{opt.key}
|
value={opt.value}
|
||||||
</option>
|
className="invokeai__select-option"
|
||||||
);
|
>
|
||||||
})}
|
{opt.key}
|
||||||
</Select>
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Tooltip>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,40 +1,62 @@
|
|||||||
@use '../../styles/Mixins/' as *;
|
.invokeai__slider-component {
|
||||||
|
|
||||||
.invokeai__slider-form-control {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: max-content;
|
|
||||||
padding-right: 0.25rem;
|
|
||||||
|
|
||||||
.invokeai__slider-inner-container {
|
.invokeai__slider-component-label {
|
||||||
display: flex;
|
min-width: max-content;
|
||||||
column-gap: 0.5rem;
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.invokeai__slider-form-label {
|
.invokeai__slider_track {
|
||||||
color: var(--text-color-secondary);
|
background-color: var(--tab-color);
|
||||||
margin: 0;
|
}
|
||||||
margin-right: 0.5rem;
|
|
||||||
margin-bottom: 0.1rem;
|
.invokeai__slider_track-filled {
|
||||||
|
background-color: var(--slider-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invokeai__slider-thumb {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invokeai__slider-mark {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--slider-color);
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invokeai__slider-number-input {
|
||||||
|
border: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 2rem;
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 2px solid var(--input-border-color);
|
||||||
|
box-shadow: 0 0 10px 0 var(--input-box-shadow-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.invokeai__slider-root {
|
&:disabled {
|
||||||
.invokeai__slider-filled-track {
|
opacity: 0.2;
|
||||||
background-color: var(--accent-color-hover);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.invokeai__slider-track {
|
.invokeai__slider-number-stepper {
|
||||||
background-color: var(--text-color-secondary);
|
border: none;
|
||||||
height: 5px;
|
}
|
||||||
border-radius: 9999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invokeai__slider-thumb {
|
&[data-markers='true'] {
|
||||||
}
|
.invokeai__slider_container {
|
||||||
|
margin-top: -1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.invokeai__slider-thumb-tooltip {
|
|
||||||
}
|
|
||||||
|
@ -1,87 +1,246 @@
|
|||||||
import {
|
import {
|
||||||
Slider,
|
|
||||||
SliderTrack,
|
|
||||||
SliderFilledTrack,
|
|
||||||
SliderThumb,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
FormLabel,
|
|
||||||
Tooltip,
|
|
||||||
SliderProps,
|
|
||||||
FormControlProps,
|
FormControlProps,
|
||||||
|
FormLabel,
|
||||||
FormLabelProps,
|
FormLabelProps,
|
||||||
SliderTrackProps,
|
HStack,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputFieldProps,
|
||||||
|
NumberInputProps,
|
||||||
|
NumberInputStepper,
|
||||||
|
NumberInputStepperProps,
|
||||||
|
Slider,
|
||||||
|
SliderFilledTrack,
|
||||||
|
SliderMark,
|
||||||
|
SliderMarkProps,
|
||||||
|
SliderThumb,
|
||||||
SliderThumbProps,
|
SliderThumbProps,
|
||||||
|
SliderTrack,
|
||||||
|
SliderTrackProps,
|
||||||
|
Tooltip,
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
SliderInnerTrackProps,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import React, { FocusEvent, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { BiReset } from 'react-icons/bi';
|
||||||
|
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
type IAISliderProps = SliderProps & {
|
export type IAIFullSliderProps = {
|
||||||
label?: string;
|
label: string;
|
||||||
|
value: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
onChange: (v: number) => void;
|
||||||
|
withSliderMarks?: boolean;
|
||||||
|
sliderMarkLeftOffset?: number;
|
||||||
|
sliderMarkRightOffset?: number;
|
||||||
|
withInput?: boolean;
|
||||||
|
isInteger?: boolean;
|
||||||
|
inputWidth?: string | number;
|
||||||
|
inputReadOnly?: boolean;
|
||||||
|
withReset?: boolean;
|
||||||
|
handleReset?: () => void;
|
||||||
|
isResetDisabled?: boolean;
|
||||||
|
isSliderDisabled?: boolean;
|
||||||
|
isInputDisabled?: boolean;
|
||||||
|
tooltipSuffix?: string;
|
||||||
|
hideTooltip?: boolean;
|
||||||
styleClass?: string;
|
styleClass?: string;
|
||||||
formControlProps?: FormControlProps;
|
sliderFormControlProps?: FormControlProps;
|
||||||
formLabelProps?: FormLabelProps;
|
sliderFormLabelProps?: FormLabelProps;
|
||||||
|
sliderMarkProps?: Omit<SliderMarkProps, 'value'>;
|
||||||
sliderTrackProps?: SliderTrackProps;
|
sliderTrackProps?: SliderTrackProps;
|
||||||
sliderInnerTrackProps?: SliderInnerTrackProps;
|
|
||||||
sliderThumbProps?: SliderThumbProps;
|
sliderThumbProps?: SliderThumbProps;
|
||||||
sliderThumbTooltipProps?: Omit<TooltipProps, 'children'>;
|
sliderNumberInputProps?: NumberInputProps;
|
||||||
|
sliderNumberInputFieldProps?: NumberInputFieldProps;
|
||||||
|
sliderNumberInputStepperProps?: NumberInputStepperProps;
|
||||||
|
sliderTooltipProps?: Omit<TooltipProps, 'children'>;
|
||||||
|
sliderIAIIconButtonProps?: IAIIconButtonProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
const IAISlider = (props: IAISliderProps) => {
|
export default function IAISlider(props: IAIFullSliderProps) {
|
||||||
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
|
value,
|
||||||
|
min = 1,
|
||||||
|
max = 100,
|
||||||
|
step = 1,
|
||||||
|
onChange,
|
||||||
|
tooltipSuffix = '',
|
||||||
|
withSliderMarks = false,
|
||||||
|
sliderMarkLeftOffset = 0,
|
||||||
|
sliderMarkRightOffset = -7,
|
||||||
|
withInput = false,
|
||||||
|
isInteger = false,
|
||||||
|
inputWidth = '5rem',
|
||||||
|
inputReadOnly = true,
|
||||||
|
withReset = false,
|
||||||
|
hideTooltip = false,
|
||||||
|
handleReset,
|
||||||
|
isResetDisabled,
|
||||||
|
isSliderDisabled,
|
||||||
|
isInputDisabled,
|
||||||
styleClass,
|
styleClass,
|
||||||
formControlProps,
|
sliderFormControlProps,
|
||||||
formLabelProps,
|
sliderFormLabelProps,
|
||||||
|
sliderMarkProps,
|
||||||
sliderTrackProps,
|
sliderTrackProps,
|
||||||
sliderInnerTrackProps,
|
|
||||||
sliderThumbProps,
|
sliderThumbProps,
|
||||||
sliderThumbTooltipProps,
|
sliderNumberInputProps,
|
||||||
|
sliderNumberInputFieldProps,
|
||||||
|
sliderNumberInputStepperProps,
|
||||||
|
sliderTooltipProps,
|
||||||
|
sliderIAIIconButtonProps,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const [localInputValue, setLocalInputValue] = useState<string>(String(value));
|
||||||
|
|
||||||
|
const numberInputMax = useMemo(
|
||||||
|
() => (sliderNumberInputProps?.max ? sliderNumberInputProps.max : max),
|
||||||
|
[max, sliderNumberInputProps?.max]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (String(value) !== localInputValue && localInputValue !== '') {
|
||||||
|
setLocalInputValue(String(value));
|
||||||
|
}
|
||||||
|
}, [value, localInputValue, setLocalInputValue]);
|
||||||
|
|
||||||
|
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||||
|
const clamped = _.clamp(
|
||||||
|
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
||||||
|
min,
|
||||||
|
numberInputMax
|
||||||
|
);
|
||||||
|
setLocalInputValue(String(clamped));
|
||||||
|
onChange(clamped);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (v: any) => {
|
||||||
|
setLocalInputValue(v);
|
||||||
|
onChange(Number(v));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetDisable = () => {
|
||||||
|
if (!handleReset) return;
|
||||||
|
handleReset();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
className={`invokeai__slider-form-control ${styleClass}`}
|
className={
|
||||||
{...formControlProps}
|
styleClass
|
||||||
|
? `invokeai__slider-component ${styleClass}`
|
||||||
|
: `invokeai__slider-component`
|
||||||
|
}
|
||||||
|
data-markers={withSliderMarks}
|
||||||
|
{...sliderFormControlProps}
|
||||||
>
|
>
|
||||||
<div className="invokeai__slider-inner-container">
|
<FormLabel
|
||||||
<FormLabel
|
className="invokeai__slider-component-label"
|
||||||
className={`invokeai__slider-form-label`}
|
{...sliderFormLabelProps}
|
||||||
whiteSpace="nowrap"
|
>
|
||||||
{...formLabelProps}
|
{label}
|
||||||
>
|
</FormLabel>
|
||||||
{label}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
|
<HStack w={'100%'} gap={2}>
|
||||||
<Slider
|
<Slider
|
||||||
className={`invokeai__slider-root`}
|
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
|
value={value}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onMouseEnter={() => setShowTooltip(true)}
|
||||||
|
onMouseLeave={() => setShowTooltip(false)}
|
||||||
|
focusThumbOnChange={false}
|
||||||
|
isDisabled={isSliderDisabled}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<SliderTrack
|
{withSliderMarks && (
|
||||||
className={`invokeai__slider-track`}
|
<>
|
||||||
{...sliderTrackProps}
|
<SliderMark
|
||||||
>
|
value={min}
|
||||||
<SliderFilledTrack
|
className="invokeai__slider-mark invokeai__slider-mark-start"
|
||||||
className={`invokeai__slider-filled-track`}
|
ml={sliderMarkLeftOffset}
|
||||||
{...sliderInnerTrackProps}
|
{...sliderMarkProps}
|
||||||
/>
|
>
|
||||||
|
{min}
|
||||||
|
</SliderMark>
|
||||||
|
<SliderMark
|
||||||
|
value={max}
|
||||||
|
className="invokeai__slider-mark invokeai__slider-mark-end"
|
||||||
|
ml={sliderMarkRightOffset}
|
||||||
|
{...sliderMarkProps}
|
||||||
|
>
|
||||||
|
{max}
|
||||||
|
</SliderMark>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SliderTrack className="invokeai__slider_track" {...sliderTrackProps}>
|
||||||
|
<SliderFilledTrack className="invokeai__slider_track-filled" />
|
||||||
</SliderTrack>
|
</SliderTrack>
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className={`invokeai__slider-thumb-tooltip`}
|
|
||||||
placement="top"
|
|
||||||
hasArrow
|
hasArrow
|
||||||
{...sliderThumbTooltipProps}
|
className="invokeai__slider-component-tooltip"
|
||||||
|
placement="top"
|
||||||
|
isOpen={showTooltip}
|
||||||
|
label={`${value}${tooltipSuffix}`}
|
||||||
|
hidden={hideTooltip}
|
||||||
|
{...sliderTooltipProps}
|
||||||
>
|
>
|
||||||
<SliderThumb
|
<SliderThumb
|
||||||
className={`invokeai__slider-thumb`}
|
className="invokeai__slider-thumb"
|
||||||
{...sliderThumbProps}
|
{...sliderThumbProps}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Slider>
|
</Slider>
|
||||||
</div>
|
|
||||||
|
{withInput && (
|
||||||
|
<NumberInput
|
||||||
|
min={min}
|
||||||
|
max={numberInputMax}
|
||||||
|
step={step}
|
||||||
|
value={localInputValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
|
className="invokeai__slider-number-field"
|
||||||
|
isDisabled={isInputDisabled}
|
||||||
|
{...sliderNumberInputProps}
|
||||||
|
>
|
||||||
|
<NumberInputField
|
||||||
|
className="invokeai__slider-number-input"
|
||||||
|
width={inputWidth}
|
||||||
|
readOnly={inputReadOnly}
|
||||||
|
{...sliderNumberInputFieldProps}
|
||||||
|
/>
|
||||||
|
<NumberInputStepper {...sliderNumberInputStepperProps}>
|
||||||
|
<NumberIncrementStepper className="invokeai__slider-number-stepper" />
|
||||||
|
<NumberDecrementStepper className="invokeai__slider-number-stepper" />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{withReset && (
|
||||||
|
<IAIIconButton
|
||||||
|
size={'sm'}
|
||||||
|
aria-label={'Reset'}
|
||||||
|
tooltip={'Reset'}
|
||||||
|
icon={<BiReset />}
|
||||||
|
onClick={handleResetDisable}
|
||||||
|
isDisabled={isResetDisabled}
|
||||||
|
{...sliderIAIIconButtonProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default IAISlider;
|
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.image-uploader-button-outer {
|
.image-uploader-button-outer {
|
||||||
min-width: 20rem;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -42,10 +41,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
color: var(--tab-list-text-inactive);
|
color: var(--tab-list-text-inactive);
|
||||||
background-color: var(--btn-grey);
|
background-color: var(--background-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--btn-grey-hover);
|
background-color: var(--background-color-light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,10 +65,10 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 4rem !important;
|
width: 4rem;
|
||||||
height: 4rem !important;
|
height: 4rem;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.2rem !important;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { useCallback, ReactNode, useState, useEffect } from 'react';
|
import {
|
||||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
useCallback,
|
||||||
|
ReactNode,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
KeyboardEvent,
|
||||||
|
} from 'react';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||||
import { FileRejection, useDropzone } from 'react-dropzone';
|
import { FileRejection, useDropzone } from 'react-dropzone';
|
||||||
import { useToast } from '@chakra-ui/react';
|
import { useToast } from '@chakra-ui/react';
|
||||||
import { uploadImage } from '../../app/socketio/actions';
|
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
|
||||||
import { ImageUploadDestination, UploadImagePayload } from '../../app/invokeai';
|
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||||
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
import { tabDict } from 'features/tabs/components/InvokeTabs';
|
||||||
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
|
|
||||||
import { tabDict } from '../../features/tabs/InvokeTabs';
|
|
||||||
import ImageUploadOverlay from './ImageUploadOverlay';
|
import ImageUploadOverlay from './ImageUploadOverlay';
|
||||||
|
import { uploadImage } from 'features/gallery/store/thunks/uploadImage';
|
||||||
|
import useImageUploader from 'common/hooks/useImageUploader';
|
||||||
|
|
||||||
type ImageUploaderProps = {
|
type ImageUploaderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -19,6 +25,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const toast = useToast({});
|
const toast = useToast({});
|
||||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||||
|
const { setOpenUploader } = useImageUploader();
|
||||||
|
|
||||||
const fileRejectionCallback = useCallback(
|
const fileRejectionCallback = useCallback(
|
||||||
(rejection: FileRejection) => {
|
(rejection: FileRejection) => {
|
||||||
@ -38,15 +45,10 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fileAcceptedCallback = useCallback(
|
const fileAcceptedCallback = useCallback(
|
||||||
(file: File) => {
|
async (file: File) => {
|
||||||
setIsHandlingUpload(true);
|
dispatch(uploadImage({ imageFile: file }));
|
||||||
const payload: UploadImagePayload = { file };
|
|
||||||
if (['img2img', 'inpainting'].includes(activeTabName)) {
|
|
||||||
payload.destination = activeTabName as ImageUploadDestination;
|
|
||||||
}
|
|
||||||
dispatch(uploadImage(payload));
|
|
||||||
},
|
},
|
||||||
[dispatch, activeTabName]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
@ -77,6 +79,8 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
maxFiles: 1,
|
maxFiles: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setOpenUploader(open);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pasteImageListener = (e: ClipboardEvent) => {
|
const pasteImageListener = (e: ClipboardEvent) => {
|
||||||
const dataTransferItemList = e.clipboardData?.items;
|
const dataTransferItemList = e.clipboardData?.items;
|
||||||
@ -118,12 +122,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: UploadImagePayload = { file };
|
dispatch(uploadImage({ imageFile: file }));
|
||||||
if (['img2img', 'inpainting'].includes(activeTabName)) {
|
|
||||||
payload.destination = activeTabName as ImageUploadDestination;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(uploadImage(payload));
|
|
||||||
};
|
};
|
||||||
document.addEventListener('paste', pasteImageListener);
|
document.addEventListener('paste', pasteImageListener);
|
||||||
return () => {
|
return () => {
|
||||||
@ -131,13 +130,21 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
};
|
};
|
||||||
}, [dispatch, toast, activeTabName]);
|
}, [dispatch, toast, activeTabName]);
|
||||||
|
|
||||||
const overlaySecondaryText = ['img2img', 'inpainting'].includes(activeTabName)
|
const overlaySecondaryText = ['img2img', 'unifiedCanvas'].includes(
|
||||||
|
activeTabName
|
||||||
|
)
|
||||||
? ` to ${tabDict[activeTabName as keyof typeof tabDict].tooltip}`
|
? ` to ${tabDict[activeTabName as keyof typeof tabDict].tooltip}`
|
||||||
: ``;
|
: ``;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageUploaderTriggerContext.Provider value={open}>
|
<ImageUploaderTriggerContext.Provider value={open}>
|
||||||
<div {...getRootProps({ style: {} })}>
|
<div
|
||||||
|
{...getRootProps({ style: {} })}
|
||||||
|
onKeyDown={(e: KeyboardEvent) => {
|
||||||
|
// Bail out if user hits spacebar - do not open the uploader
|
||||||
|
if (e.key === ' ') return;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
{children}
|
{children}
|
||||||
{isDragActive && isHandlingUpload && (
|
{isDragActive && isHandlingUpload && (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Heading } from '@chakra-ui/react';
|
import { Heading } from '@chakra-ui/react';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { FaUpload } from 'react-icons/fa';
|
import { FaUpload } from 'react-icons/fa';
|
||||||
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
|
||||||
|
|
||||||
type ImageUploaderButtonProps = {
|
type ImageUploaderButtonProps = {
|
||||||
styleClass?: string;
|
styleClass?: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { FaUpload } from 'react-icons/fa';
|
import { FaUpload } from 'react-icons/fa';
|
||||||
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
|
||||||
import IAIIconButton from './IAIIconButton';
|
import IAIIconButton from './IAIIconButton';
|
||||||
|
|
||||||
const ImageUploaderIconButton = () => {
|
const ImageUploaderIconButton = () => {
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import Img2ImgPlaceHolder from '../../../assets/images/image2img.png';
|
|
||||||
|
|
||||||
export const ImageToImageWIP = () => {
|
|
||||||
return (
|
|
||||||
<div className="work-in-progress txt2img-work-in-progress">
|
|
||||||
<img src={Img2ImgPlaceHolder} alt="img2img_placeholder" />
|
|
||||||
<h1>Image To Image</h1>
|
|
||||||
<p>
|
|
||||||
Image to Image is already available in the WebUI. You can access it from
|
|
||||||
the Text to Image - Advanced Options menu. A dedicated UI for Image To
|
|
||||||
Image will be released soon.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function InpaintingWIP() {
|
|
||||||
return (
|
|
||||||
<div className="work-in-progress inpainting-work-in-progress">
|
|
||||||
<h1>Inpainting</h1>
|
|
||||||
<p>
|
|
||||||
Inpainting is available as a part of the Invoke AI Command Line
|
|
||||||
Interface. A dedicated WebUI interface will be released in the near
|
|
||||||
future.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function OutpaintingWIP() {
|
|
||||||
return (
|
|
||||||
<div className="work-in-progress outpainting-work-in-progress">
|
|
||||||
<h1>Outpainting</h1>
|
|
||||||
<p>
|
|
||||||
Outpainting is available as a part of the Invoke AI Command Line
|
|
||||||
Interface. A dedicated WebUI interface will be released in the near
|
|
||||||
future.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -9,7 +9,7 @@ export const PostProcessingWIP = () => {
|
|||||||
Upscaling and Face Restoration are already available in the WebUI. You
|
Upscaling and Face Restoration are already available in the WebUI. You
|
||||||
can access them from the Advanced Options menu of the Text To Image and
|
can access them from the Advanced Options menu of the Text To Image and
|
||||||
Image To Image tabs. You can also process images directly, using the
|
Image To Image tabs. You can also process images directly, using the
|
||||||
image action buttons above the main image display.
|
image action buttons above the current image display or in the viewer.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
A dedicated UI will be released soon to facilitate more advanced post
|
A dedicated UI will be released soon to facilitate more advanced post
|
||||||
|
16
frontend/src/common/components/WorkInProgress/Training.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function TrainingWIP() {
|
||||||
|
return (
|
||||||
|
<div className="work-in-progress nodes-work-in-progress">
|
||||||
|
<h1>Training</h1>
|
||||||
|
<p>
|
||||||
|
A dedicated workflow for training your own embeddings and checkpoints
|
||||||
|
using Textual Inversion and Dreambooth from the web interface. <br />
|
||||||
|
<br />
|
||||||
|
InvokeAI already supports training custom embeddings using Textual
|
||||||
|
Inversion using the main script.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,25 +1,37 @@
|
|||||||
import { RefObject, useEffect } from 'react';
|
import { RefObject, useEffect, useRef } from 'react';
|
||||||
|
import { Rect } from 'react-konva';
|
||||||
|
|
||||||
const useClickOutsideWatcher = (
|
const watchers: {
|
||||||
ref: RefObject<HTMLElement>,
|
ref: RefObject<HTMLElement>;
|
||||||
callback: () => void,
|
enable: boolean;
|
||||||
req = true
|
callback: () => void;
|
||||||
) => {
|
}[] = [];
|
||||||
|
|
||||||
|
const useClickOutsideWatcher = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(e: MouseEvent) {
|
function handleClickOutside(e: MouseEvent) {
|
||||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
watchers.forEach(({ ref, enable, callback }) => {
|
||||||
callback();
|
if (enable && ref.current && !ref.current.contains(e.target as Node)) {
|
||||||
}
|
console.log('callback');
|
||||||
}
|
callback();
|
||||||
if (req) {
|
}
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
});
|
||||||
}
|
}
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
return () => {
|
return () => {
|
||||||
if (req) {
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [ref, req, callback]);
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
addWatcher: (watcher: {
|
||||||
|
ref: RefObject<HTMLElement>;
|
||||||
|
callback: () => void;
|
||||||
|
enable: boolean;
|
||||||
|
}) => {
|
||||||
|
watchers.push(watcher);
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useClickOutsideWatcher;
|
export default useClickOutsideWatcher;
|
||||||
|
14
frontend/src/common/hooks/useImageUploader.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
let openFunction: () => void;
|
||||||
|
|
||||||
|
const useImageUploader = () => {
|
||||||
|
return {
|
||||||
|
setOpenUploader: (open?: () => void) => {
|
||||||
|
if (open) {
|
||||||
|
openFunction = open;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openUploader: openFunction,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useImageUploader;
|
16
frontend/src/common/icons/TrainingIcon.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { createIcon } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const TrainingIcon = createIcon({
|
||||||
|
displayName: 'TrainingIcon',
|
||||||
|
viewBox: '0 0 3544 3544',
|
||||||
|
path: (
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M0,768.593L0,2774.71C0,2930.6 78.519,3068.3 198.135,3150.37C273.059,3202.68 364.177,3233.38 462.407,3233.38C462.407,3233.38 3080.9,3233.38 3080.9,3233.38C3179.13,3233.38 3270.25,3202.68 3345.17,3150.37C3464.79,3068.3 3543.31,2930.6 3543.31,2774.71L3543.31,768.593C3543.31,517.323 3339.31,313.324 3088.04,313.324L455.269,313.324C203.999,313.324 0,517.323 0,768.593ZM3427.88,775.73L3427.88,2770.97C3427.88,2962.47 3272.4,3117.95 3080.9,3117.95L462.407,3117.95C270.906,3117.95 115.431,2962.47 115.431,2770.97C115.431,2770.97 115.431,775.73 115.431,775.73C115.431,584.229 270.906,428.755 462.407,428.755C462.407,428.755 3080.9,428.755 3080.9,428.755C3272.4,428.755 3427.88,584.229 3427.88,775.73ZM796.24,1322.76L796.24,1250.45C796.24,1199.03 836.16,1157.27 885.331,1157.27C885.331,1157.27 946.847,1157.27 946.847,1157.27C996.017,1157.27 1035.94,1199.03 1035.94,1250.45L1035.94,1644.81L2507.37,1644.81L2507.37,1250.45C2507.37,1199.03 2547.29,1157.27 2596.46,1157.27C2596.46,1157.27 2657.98,1157.27 2657.98,1157.27C2707.15,1157.27 2747.07,1199.03 2747.07,1250.45L2747.07,1322.76C2756.66,1319.22 2767.02,1317.29 2777.83,1317.29C2777.83,1317.29 2839.34,1317.29 2839.34,1317.29C2888.51,1317.29 2928.43,1357.21 2928.43,1406.38L2928.43,1527.32C2933.51,1526.26 2938.77,1525.71 2944.16,1525.71L2995.3,1525.71C3036.18,1525.71 3069.37,1557.59 3069.37,1596.86C3069.37,1596.86 3069.37,1946.44 3069.37,1946.44C3069.37,1985.72 3036.18,2017.6 2995.3,2017.6C2995.3,2017.6 2944.16,2017.6 2944.16,2017.6C2938.77,2017.6 2933.51,2017.04 2928.43,2015.99L2928.43,2136.92C2928.43,2186.09 2888.51,2226.01 2839.34,2226.01L2777.83,2226.01C2767.02,2226.01 2756.66,2224.08 2747.07,2220.55L2747.07,2292.85C2747.07,2344.28 2707.15,2386.03 2657.98,2386.03C2657.98,2386.03 2596.46,2386.03 2596.46,2386.03C2547.29,2386.03 2507.37,2344.28 2507.37,2292.85L2507.37,1898.5L1035.94,1898.5L1035.94,2292.85C1035.94,2344.28 996.017,2386.03 946.847,2386.03C946.847,2386.03 885.331,2386.03 885.331,2386.03C836.16,2386.03 796.24,2344.28 796.24,2292.85L796.24,2220.55C786.651,2224.08 776.29,2226.01 765.482,2226.01L703.967,2226.01C654.796,2226.01 614.876,2186.09 614.876,2136.92L614.876,2015.99C609.801,2017.04 604.539,2017.6 599.144,2017.6C599.144,2017.6 548.003,2017.6 548.003,2017.6C507.125,2017.6 473.937,1985.72 473.937,1946.44C473.937,1946.44 473.937,1596.86 473.937,1596.86C473.937,1557.59 507.125,1525.71 548.003,1525.71L599.144,1525.71C604.539,1525.71 609.801,1526.26 614.876,1527.32L614.876,1406.38C614.876,1357.21 654.796,1317.29 703.967,1317.29C703.967,1317.29 765.482,1317.29 765.482,1317.29C776.29,1317.29 786.651,1319.22 796.24,1322.76ZM977.604,1250.45C977.604,1232.7 963.822,1218.29 946.847,1218.29L885.331,1218.29C868.355,1218.29 854.573,1232.7 854.573,1250.45L854.573,2292.85C854.573,2310.61 868.355,2325.02 885.331,2325.02L946.847,2325.02C963.822,2325.02 977.604,2310.61 977.604,2292.85L977.604,1250.45ZM2565.7,1250.45C2565.7,1232.7 2579.49,1218.29 2596.46,1218.29L2657.98,1218.29C2674.95,1218.29 2688.73,1232.7 2688.73,1250.45L2688.73,2292.85C2688.73,2310.61 2674.95,2325.02 2657.98,2325.02L2596.46,2325.02C2579.49,2325.02 2565.7,2310.61 2565.7,2292.85L2565.7,1250.45ZM673.209,1406.38L673.209,2136.92C673.209,2153.9 686.991,2167.68 703.967,2167.68L765.482,2167.68C782.458,2167.68 796.24,2153.9 796.24,2136.92L796.24,1406.38C796.24,1389.41 782.458,1375.63 765.482,1375.63L703.967,1375.63C686.991,1375.63 673.209,1389.41 673.209,1406.38ZM2870.1,1406.38L2870.1,2136.92C2870.1,2153.9 2856.32,2167.68 2839.34,2167.68L2777.83,2167.68C2760.85,2167.68 2747.07,2153.9 2747.07,2136.92L2747.07,1406.38C2747.07,1389.41 2760.85,1375.63 2777.83,1375.63L2839.34,1375.63C2856.32,1375.63 2870.1,1389.41 2870.1,1406.38ZM614.876,1577.5C610.535,1574.24 605.074,1572.3 599.144,1572.3L548.003,1572.3C533.89,1572.3 522.433,1583.3 522.433,1596.86L522.433,1946.44C522.433,1960 533.89,1971.01 548.003,1971.01L599.144,1971.01C605.074,1971.01 610.535,1969.07 614.876,1965.81L614.876,1577.5ZM2928.43,1965.81L2928.43,1577.5C2932.77,1574.24 2938.23,1572.3 2944.16,1572.3L2995.3,1572.3C3009.42,1572.3 3020.87,1583.3 3020.87,1596.86L3020.87,1946.44C3020.87,1960 3009.42,1971.01 2995.3,1971.01L2944.16,1971.01C2938.23,1971.01 2932.77,1969.07 2928.43,1965.81ZM2507.37,1703.14L1035.94,1703.14L1035.94,1840.16L2507.37,1840.16L2507.37,1898.38L2507.37,1659.46L2507.37,1703.14Z"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TrainingIcon;
|
BIN
frontend/src/common/icons/UnifiedCanvas.afdesign
Normal file
16
frontend/src/common/icons/UnifiedCanvasIcon.tsx
Normal file
BIN
frontend/src/common/icons/design_files/Training.afdesign
Normal file
5
frontend/src/common/icons/design_files/Training.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 3544 3544" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<path d="M0,768.593L0,2774.71C0,2930.6 78.519,3068.3 198.135,3150.37C273.059,3202.68 364.177,3233.38 462.407,3233.38C462.407,3233.38 3080.9,3233.38 3080.9,3233.38C3179.13,3233.38 3270.25,3202.68 3345.17,3150.37C3464.79,3068.3 3543.31,2930.6 3543.31,2774.71L3543.31,768.593C3543.31,517.323 3339.31,313.324 3088.04,313.324L455.269,313.324C203.999,313.324 0,517.323 0,768.593ZM3427.88,775.73L3427.88,2770.97C3427.88,2962.47 3272.4,3117.95 3080.9,3117.95L462.407,3117.95C270.906,3117.95 115.431,2962.47 115.431,2770.97C115.431,2770.97 115.431,775.73 115.431,775.73C115.431,584.229 270.906,428.755 462.407,428.755C462.407,428.755 3080.9,428.755 3080.9,428.755C3272.4,428.755 3427.88,584.229 3427.88,775.73ZM796.24,1322.76L796.24,1250.45C796.24,1199.03 836.16,1157.27 885.331,1157.27C885.331,1157.27 946.847,1157.27 946.847,1157.27C996.017,1157.27 1035.94,1199.03 1035.94,1250.45L1035.94,1644.81L2507.37,1644.81L2507.37,1250.45C2507.37,1199.03 2547.29,1157.27 2596.46,1157.27C2596.46,1157.27 2657.98,1157.27 2657.98,1157.27C2707.15,1157.27 2747.07,1199.03 2747.07,1250.45L2747.07,1322.76C2756.66,1319.22 2767.02,1317.29 2777.83,1317.29C2777.83,1317.29 2839.34,1317.29 2839.34,1317.29C2888.51,1317.29 2928.43,1357.21 2928.43,1406.38L2928.43,1527.32C2933.51,1526.26 2938.77,1525.71 2944.16,1525.71L2995.3,1525.71C3036.18,1525.71 3069.37,1557.59 3069.37,1596.86C3069.37,1596.86 3069.37,1946.44 3069.37,1946.44C3069.37,1985.72 3036.18,2017.6 2995.3,2017.6C2995.3,2017.6 2944.16,2017.6 2944.16,2017.6C2938.77,2017.6 2933.51,2017.04 2928.43,2015.99L2928.43,2136.92C2928.43,2186.09 2888.51,2226.01 2839.34,2226.01L2777.83,2226.01C2767.02,2226.01 2756.66,2224.08 2747.07,2220.55L2747.07,2292.85C2747.07,2344.28 2707.15,2386.03 2657.98,2386.03C2657.98,2386.03 2596.46,2386.03 2596.46,2386.03C2547.29,2386.03 2507.37,2344.28 2507.37,2292.85L2507.37,1898.5L1035.94,1898.5L1035.94,2292.85C1035.94,2344.28 996.017,2386.03 946.847,2386.03C946.847,2386.03 885.331,2386.03 885.331,2386.03C836.16,2386.03 796.24,2344.28 796.24,2292.85L796.24,2220.55C786.651,2224.08 776.29,2226.01 765.482,2226.01L703.967,2226.01C654.796,2226.01 614.876,2186.09 614.876,2136.92L614.876,2015.99C609.801,2017.04 604.539,2017.6 599.144,2017.6C599.144,2017.6 548.003,2017.6 548.003,2017.6C507.125,2017.6 473.937,1985.72 473.937,1946.44C473.937,1946.44 473.937,1596.86 473.937,1596.86C473.937,1557.59 507.125,1525.71 548.003,1525.71L599.144,1525.71C604.539,1525.71 609.801,1526.26 614.876,1527.32L614.876,1406.38C614.876,1357.21 654.796,1317.29 703.967,1317.29C703.967,1317.29 765.482,1317.29 765.482,1317.29C776.29,1317.29 786.651,1319.22 796.24,1322.76ZM977.604,1250.45C977.604,1232.7 963.822,1218.29 946.847,1218.29L885.331,1218.29C868.355,1218.29 854.573,1232.7 854.573,1250.45L854.573,2292.85C854.573,2310.61 868.355,2325.02 885.331,2325.02L946.847,2325.02C963.822,2325.02 977.604,2310.61 977.604,2292.85L977.604,1250.45ZM2565.7,1250.45C2565.7,1232.7 2579.49,1218.29 2596.46,1218.29L2657.98,1218.29C2674.95,1218.29 2688.73,1232.7 2688.73,1250.45L2688.73,2292.85C2688.73,2310.61 2674.95,2325.02 2657.98,2325.02L2596.46,2325.02C2579.49,2325.02 2565.7,2310.61 2565.7,2292.85L2565.7,1250.45ZM673.209,1406.38L673.209,2136.92C673.209,2153.9 686.991,2167.68 703.967,2167.68L765.482,2167.68C782.458,2167.68 796.24,2153.9 796.24,2136.92L796.24,1406.38C796.24,1389.41 782.458,1375.63 765.482,1375.63L703.967,1375.63C686.991,1375.63 673.209,1389.41 673.209,1406.38ZM2870.1,1406.38L2870.1,2136.92C2870.1,2153.9 2856.32,2167.68 2839.34,2167.68L2777.83,2167.68C2760.85,2167.68 2747.07,2153.9 2747.07,2136.92L2747.07,1406.38C2747.07,1389.41 2760.85,1375.63 2777.83,1375.63L2839.34,1375.63C2856.32,1375.63 2870.1,1389.41 2870.1,1406.38ZM614.876,1577.5C610.535,1574.24 605.074,1572.3 599.144,1572.3L548.003,1572.3C533.89,1572.3 522.433,1583.3 522.433,1596.86L522.433,1946.44C522.433,1960 533.89,1971.01 548.003,1971.01L599.144,1971.01C605.074,1971.01 610.535,1969.07 614.876,1965.81L614.876,1577.5ZM2928.43,1965.81L2928.43,1577.5C2932.77,1574.24 2938.23,1572.3 2944.16,1572.3L2995.3,1572.3C3009.42,1572.3 3020.87,1583.3 3020.87,1596.86L3020.87,1946.44C3020.87,1960 3009.42,1971.01 2995.3,1971.01L2944.16,1971.01C2938.23,1971.01 2932.77,1969.07 2928.43,1965.81ZM2507.37,1703.14L1035.94,1703.14L1035.94,1840.16L2507.37,1840.16L2507.37,1898.38L2507.37,1659.46L2507.37,1703.14Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
BIN
frontend/src/common/icons/design_files/UnifiedCanvas.afdesign
Normal file
7
frontend/src/common/icons/design_files/UnifiedCanvas.svg
Normal file
After Width: | Height: | Size: 9.8 KiB |
21
frontend/src/common/util/openBase64ImageInTab.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
type Base64AndCaption = {
|
||||||
|
base64: string;
|
||||||
|
caption: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openBase64ImageInTab = (images: Base64AndCaption[]) => {
|
||||||
|
const w = window.open('');
|
||||||
|
if (!w) return;
|
||||||
|
|
||||||
|
images.forEach((i) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.src = i.base64;
|
||||||
|
|
||||||
|
w.document.write(i.caption);
|
||||||
|
w.document.write('</br>');
|
||||||
|
w.document.write(image.outerHTML);
|
||||||
|
w.document.write('</br></br>');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default openBase64ImageInTab;
|
@ -1,20 +1,24 @@
|
|||||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from '../../app/constants';
|
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
|
||||||
import { OptionsState } from '../../features/options/optionsSlice';
|
import { OptionsState } from 'features/options/store/optionsSlice';
|
||||||
import { SystemState } from '../../features/system/systemSlice';
|
import { SystemState } from 'features/system/store/systemSlice';
|
||||||
|
|
||||||
import { stringToSeedWeightsArray } from './seedWeightPairs';
|
import { stringToSeedWeightsArray } from './seedWeightPairs';
|
||||||
import randomInt from './randomInt';
|
import randomInt from './randomInt';
|
||||||
import { InvokeTabName } from '../../features/tabs/InvokeTabs';
|
import { InvokeTabName } from 'features/tabs/components/InvokeTabs';
|
||||||
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
|
import {
|
||||||
import generateMask from '../../features/tabs/Inpainting/util/generateMask';
|
CanvasState,
|
||||||
|
isCanvasMaskLine,
|
||||||
|
} from 'features/canvas/store/canvasTypes';
|
||||||
|
import generateMask from 'features/canvas/util/generateMask';
|
||||||
|
import openBase64ImageInTab from './openBase64ImageInTab';
|
||||||
|
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
||||||
|
|
||||||
export type FrontendToBackendParametersConfig = {
|
export type FrontendToBackendParametersConfig = {
|
||||||
generationMode: InvokeTabName;
|
generationMode: InvokeTabName;
|
||||||
optionsState: OptionsState;
|
optionsState: OptionsState;
|
||||||
inpaintingState: InpaintingState;
|
canvasState: CanvasState;
|
||||||
systemState: SystemState;
|
systemState: SystemState;
|
||||||
imageToProcessUrl?: string;
|
imageToProcessUrl?: string;
|
||||||
maskImageElement?: HTMLImageElement;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,46 +28,56 @@ export type FrontendToBackendParametersConfig = {
|
|||||||
export const frontendToBackendParameters = (
|
export const frontendToBackendParameters = (
|
||||||
config: FrontendToBackendParametersConfig
|
config: FrontendToBackendParametersConfig
|
||||||
): { [key: string]: any } => {
|
): { [key: string]: any } => {
|
||||||
|
const canvasBaseLayer = getCanvasBaseLayer();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
generationMode,
|
generationMode,
|
||||||
optionsState,
|
optionsState,
|
||||||
inpaintingState,
|
canvasState,
|
||||||
systemState,
|
systemState,
|
||||||
imageToProcessUrl,
|
imageToProcessUrl,
|
||||||
maskImageElement,
|
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
prompt,
|
|
||||||
iterations,
|
|
||||||
steps,
|
|
||||||
cfgScale,
|
cfgScale,
|
||||||
threshold,
|
codeformerFidelity,
|
||||||
perlin,
|
facetoolStrength,
|
||||||
|
facetoolType,
|
||||||
height,
|
height,
|
||||||
width,
|
|
||||||
sampler,
|
|
||||||
seed,
|
|
||||||
seamless,
|
|
||||||
hiresFix,
|
hiresFix,
|
||||||
img2imgStrength,
|
img2imgStrength,
|
||||||
|
infillMethod,
|
||||||
initialImage,
|
initialImage,
|
||||||
|
iterations,
|
||||||
|
perlin,
|
||||||
|
prompt,
|
||||||
|
sampler,
|
||||||
|
seamBlur,
|
||||||
|
seamless,
|
||||||
|
seamSize,
|
||||||
|
seamSteps,
|
||||||
|
seamStrength,
|
||||||
|
seed,
|
||||||
|
seedWeights,
|
||||||
shouldFitToWidthHeight,
|
shouldFitToWidthHeight,
|
||||||
shouldGenerateVariations,
|
shouldGenerateVariations,
|
||||||
variationAmount,
|
shouldRandomizeSeed,
|
||||||
seedWeights,
|
|
||||||
shouldRunESRGAN,
|
shouldRunESRGAN,
|
||||||
|
shouldRunFacetool,
|
||||||
|
steps,
|
||||||
|
threshold,
|
||||||
|
tileSize,
|
||||||
upscalingLevel,
|
upscalingLevel,
|
||||||
upscalingStrength,
|
upscalingStrength,
|
||||||
shouldRunFacetool,
|
variationAmount,
|
||||||
facetoolStrength,
|
width,
|
||||||
codeformerFidelity,
|
|
||||||
facetoolType,
|
|
||||||
shouldRandomizeSeed,
|
|
||||||
} = optionsState;
|
} = optionsState;
|
||||||
|
|
||||||
const { shouldDisplayInProgressType, saveIntermediatesInterval } =
|
const {
|
||||||
systemState;
|
shouldDisplayInProgressType,
|
||||||
|
saveIntermediatesInterval,
|
||||||
|
enableImageDebugging,
|
||||||
|
} = systemState;
|
||||||
|
|
||||||
const generationParameters: { [k: string]: any } = {
|
const generationParameters: { [k: string]: any } = {
|
||||||
prompt,
|
prompt,
|
||||||
@ -80,6 +94,8 @@ export const frontendToBackendParameters = (
|
|||||||
progress_images: shouldDisplayInProgressType === 'full-res',
|
progress_images: shouldDisplayInProgressType === 'full-res',
|
||||||
progress_latents: shouldDisplayInProgressType === 'latents',
|
progress_latents: shouldDisplayInProgressType === 'latents',
|
||||||
save_intermediates: saveIntermediatesInterval,
|
save_intermediates: saveIntermediatesInterval,
|
||||||
|
generation_mode: generationMode,
|
||||||
|
init_mask: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
generationParameters.seed = shouldRandomizeSeed
|
generationParameters.seed = shouldRandomizeSeed
|
||||||
@ -101,35 +117,38 @@ export const frontendToBackendParameters = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// inpainting exclusive parameters
|
// inpainting exclusive parameters
|
||||||
if (generationMode === 'inpainting' && maskImageElement) {
|
if (generationMode === 'unifiedCanvas' && canvasBaseLayer) {
|
||||||
const {
|
const {
|
||||||
lines,
|
layerState: { objects },
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinates,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
inpaintReplace,
|
inpaintReplace,
|
||||||
shouldUseInpaintReplace,
|
shouldUseInpaintReplace,
|
||||||
} = inpaintingState;
|
stageScale,
|
||||||
|
isMaskEnabled,
|
||||||
|
shouldPreserveMaskedArea,
|
||||||
|
boundingBoxScaleMethod: boundingBoxScale,
|
||||||
|
scaledBoundingBoxDimensions,
|
||||||
|
} = canvasState;
|
||||||
|
|
||||||
const boundingBox = {
|
const boundingBox = {
|
||||||
...boundingBoxCoordinate,
|
...boundingBoxCoordinates,
|
||||||
...boundingBoxDimensions,
|
...boundingBoxDimensions,
|
||||||
};
|
};
|
||||||
|
|
||||||
generationParameters.init_img = imageToProcessUrl;
|
const maskDataURL = generateMask(
|
||||||
generationParameters.strength = img2imgStrength;
|
isMaskEnabled ? objects.filter(isCanvasMaskLine) : [],
|
||||||
generationParameters.fit = false;
|
|
||||||
|
|
||||||
const { maskDataURL, isMaskEmpty } = generateMask(
|
|
||||||
maskImageElement,
|
|
||||||
lines,
|
|
||||||
boundingBox
|
boundingBox
|
||||||
);
|
);
|
||||||
|
|
||||||
generationParameters.is_mask_empty = isMaskEmpty;
|
generationParameters.init_mask = maskDataURL;
|
||||||
|
|
||||||
generationParameters.init_mask = maskDataURL.split(
|
generationParameters.fit = false;
|
||||||
'data:image/png;base64,'
|
|
||||||
)[1];
|
generationParameters.init_img = imageToProcessUrl;
|
||||||
|
generationParameters.strength = img2imgStrength;
|
||||||
|
|
||||||
|
generationParameters.invert_mask = shouldPreserveMaskedArea;
|
||||||
|
|
||||||
if (shouldUseInpaintReplace) {
|
if (shouldUseInpaintReplace) {
|
||||||
generationParameters.inpaint_replace = inpaintReplace;
|
generationParameters.inpaint_replace = inpaintReplace;
|
||||||
@ -137,8 +156,47 @@ export const frontendToBackendParameters = (
|
|||||||
|
|
||||||
generationParameters.bounding_box = boundingBox;
|
generationParameters.bounding_box = boundingBox;
|
||||||
|
|
||||||
// TODO: The server metadata generation needs to be changed to fix this.
|
const tempScale = canvasBaseLayer.scale();
|
||||||
|
|
||||||
|
canvasBaseLayer.scale({
|
||||||
|
x: 1 / stageScale,
|
||||||
|
y: 1 / stageScale,
|
||||||
|
});
|
||||||
|
|
||||||
|
const absPos = canvasBaseLayer.getAbsolutePosition();
|
||||||
|
|
||||||
|
const imageDataURL = canvasBaseLayer.toDataURL({
|
||||||
|
x: boundingBox.x + absPos.x,
|
||||||
|
y: boundingBox.y + absPos.y,
|
||||||
|
width: boundingBox.width,
|
||||||
|
height: boundingBox.height,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (enableImageDebugging) {
|
||||||
|
openBase64ImageInTab([
|
||||||
|
{ base64: maskDataURL, caption: 'mask sent as init_mask' },
|
||||||
|
{ base64: imageDataURL, caption: 'image sent as init_img' },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasBaseLayer.scale(tempScale);
|
||||||
|
|
||||||
|
generationParameters.init_img = imageDataURL;
|
||||||
|
|
||||||
generationParameters.progress_images = false;
|
generationParameters.progress_images = false;
|
||||||
|
|
||||||
|
if (boundingBoxScale !== 'none') {
|
||||||
|
generationParameters.inpaint_width = scaledBoundingBoxDimensions.width;
|
||||||
|
generationParameters.inpaint_height = scaledBoundingBoxDimensions.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
generationParameters.seam_size = seamSize;
|
||||||
|
generationParameters.seam_blur = seamBlur;
|
||||||
|
generationParameters.seam_strength = seamStrength;
|
||||||
|
generationParameters.seam_steps = seamSteps;
|
||||||
|
generationParameters.tile_size = tileSize;
|
||||||
|
generationParameters.infill_method = infillMethod;
|
||||||
|
generationParameters.force_outpaint = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldGenerateVariations) {
|
if (shouldGenerateVariations) {
|
||||||
@ -171,6 +229,10 @@ export const frontendToBackendParameters = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enableImageDebugging) {
|
||||||
|
generationParameters.enable_image_debugging = enableImageDebugging;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generationParameters,
|
generationParameters,
|
||||||
esrganParameters,
|
esrganParameters,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as InvokeAI from '../../app/invokeai';
|
import * as InvokeAI from 'app/invokeai';
|
||||||
|
|
||||||
const promptToString = (prompt: InvokeAI.Prompt): string => {
|
const promptToString = (prompt: InvokeAI.Prompt): string => {
|
||||||
if (prompt.length === 1) {
|
if (prompt.length === 1) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as InvokeAI from '../../app/invokeai';
|
import * as InvokeAI from 'app/invokeai';
|
||||||
|
|
||||||
export const stringToSeedWeights = (
|
export const stringToSeedWeights = (
|
||||||
string: string
|
string: string
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { useAppDispatch } from 'app/store';
|
||||||
|
import IAIAlertDialog from 'common/components/IAIAlertDialog';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { FaTrash } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const ClearCanvasHistoryButtonModal = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIAlertDialog
|
||||||
|
title={'Clear Canvas History'}
|
||||||
|
acceptCallback={() => dispatch(clearCanvasHistory())}
|
||||||
|
acceptButtonText={'Clear History'}
|
||||||
|
triggerComponent={
|
||||||
|
<IAIButton size={'sm'} leftIcon={<FaTrash />}>
|
||||||
|
Clear Canvas History
|
||||||
|
</IAIButton>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Clearing the canvas history leaves your current canvas intact, but
|
||||||
|
irreversibly clears the undo and redo history.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p>Are you sure you want to clear the canvas history?</p>
|
||||||
|
</IAIAlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ClearCanvasHistoryButtonModal;
|
205
frontend/src/features/canvas/components/IAICanvas.tsx
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import Konva from 'konva';
|
||||||
|
import { Layer, Stage } from 'react-konva';
|
||||||
|
import { useAppSelector } from 'app/store';
|
||||||
|
import {
|
||||||
|
canvasSelector,
|
||||||
|
isStagingSelector,
|
||||||
|
} from 'features/canvas/store/canvasSelectors';
|
||||||
|
import IAICanvasMaskLines from './IAICanvasMaskLines';
|
||||||
|
import IAICanvasToolPreview from './IAICanvasToolPreview';
|
||||||
|
import { Vector2d } from 'konva/lib/types';
|
||||||
|
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
|
||||||
|
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import IAICanvasMaskCompositer from './IAICanvasMaskCompositer';
|
||||||
|
import useCanvasWheel from '../hooks/useCanvasZoom';
|
||||||
|
import useCanvasMouseDown from '../hooks/useCanvasMouseDown';
|
||||||
|
import useCanvasMouseUp from '../hooks/useCanvasMouseUp';
|
||||||
|
import useCanvasMouseMove from '../hooks/useCanvasMouseMove';
|
||||||
|
import useCanvasMouseEnter from '../hooks/useCanvasMouseEnter';
|
||||||
|
import useCanvasMouseOut from '../hooks/useCanvasMouseOut';
|
||||||
|
import useCanvasDragMove from '../hooks/useCanvasDragMove';
|
||||||
|
import IAICanvasObjectRenderer from './IAICanvasObjectRenderer';
|
||||||
|
import IAICanvasGrid from './IAICanvasGrid';
|
||||||
|
import IAICanvasIntermediateImage from './IAICanvasIntermediateImage';
|
||||||
|
import IAICanvasStatusText from './IAICanvasStatusText';
|
||||||
|
import IAICanvasStagingArea from './IAICanvasStagingArea';
|
||||||
|
import IAICanvasStagingAreaToolbar from './IAICanvasStagingAreaToolbar';
|
||||||
|
import {
|
||||||
|
setCanvasBaseLayer,
|
||||||
|
setCanvasStage,
|
||||||
|
} from '../util/konvaInstanceProvider';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[canvasSelector, isStagingSelector],
|
||||||
|
(canvas, isStaging) => {
|
||||||
|
const {
|
||||||
|
isMaskEnabled,
|
||||||
|
stageScale,
|
||||||
|
shouldShowBoundingBox,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isMouseOverBoundingBox,
|
||||||
|
isMovingBoundingBox,
|
||||||
|
stageDimensions,
|
||||||
|
stageCoordinates,
|
||||||
|
tool,
|
||||||
|
isMovingStage,
|
||||||
|
shouldShowIntermediates,
|
||||||
|
shouldShowGrid,
|
||||||
|
} = canvas;
|
||||||
|
|
||||||
|
let stageCursor: string | undefined = '';
|
||||||
|
|
||||||
|
if (tool === 'move' || isStaging) {
|
||||||
|
if (isMovingStage) {
|
||||||
|
stageCursor = 'grabbing';
|
||||||
|
} else {
|
||||||
|
stageCursor = 'grab';
|
||||||
|
}
|
||||||
|
} else if (isTransformingBoundingBox) {
|
||||||
|
stageCursor = undefined;
|
||||||
|
} else if (isMouseOverBoundingBox) {
|
||||||
|
stageCursor = 'move';
|
||||||
|
} else {
|
||||||
|
stageCursor = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMaskEnabled,
|
||||||
|
isModifyingBoundingBox: isTransformingBoundingBox || isMovingBoundingBox,
|
||||||
|
shouldShowBoundingBox,
|
||||||
|
shouldShowGrid,
|
||||||
|
stageCoordinates,
|
||||||
|
stageCursor,
|
||||||
|
stageDimensions,
|
||||||
|
stageScale,
|
||||||
|
tool,
|
||||||
|
isStaging,
|
||||||
|
shouldShowIntermediates,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const IAICanvas = () => {
|
||||||
|
const {
|
||||||
|
isMaskEnabled,
|
||||||
|
isModifyingBoundingBox,
|
||||||
|
shouldShowBoundingBox,
|
||||||
|
shouldShowGrid,
|
||||||
|
stageCoordinates,
|
||||||
|
stageCursor,
|
||||||
|
stageDimensions,
|
||||||
|
stageScale,
|
||||||
|
tool,
|
||||||
|
isStaging,
|
||||||
|
shouldShowIntermediates,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
useCanvasHotkeys();
|
||||||
|
|
||||||
|
const stageRef = useRef<Konva.Stage | null>(null);
|
||||||
|
const canvasBaseLayerRef = useRef<Konva.Layer | null>(null);
|
||||||
|
|
||||||
|
const canvasStageRefCallback = useCallback((el: Konva.Stage) => {
|
||||||
|
setCanvasStage(el as Konva.Stage);
|
||||||
|
stageRef.current = el;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const canvasBaseLayerRefCallback = useCallback((el: Konva.Layer) => {
|
||||||
|
setCanvasBaseLayer(el as Konva.Layer);
|
||||||
|
canvasBaseLayerRef.current = el;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const lastCursorPositionRef = useRef<Vector2d>({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
// Use refs for values that do not affect rendering, other values in redux
|
||||||
|
const didMouseMoveRef = useRef<boolean>(false);
|
||||||
|
|
||||||
|
const handleWheel = useCanvasWheel(stageRef);
|
||||||
|
const handleMouseDown = useCanvasMouseDown(stageRef);
|
||||||
|
const handleMouseUp = useCanvasMouseUp(stageRef, didMouseMoveRef);
|
||||||
|
const handleMouseMove = useCanvasMouseMove(
|
||||||
|
stageRef,
|
||||||
|
didMouseMoveRef,
|
||||||
|
lastCursorPositionRef
|
||||||
|
);
|
||||||
|
const handleMouseEnter = useCanvasMouseEnter(stageRef);
|
||||||
|
const handleMouseOut = useCanvasMouseOut();
|
||||||
|
const { handleDragStart, handleDragMove, handleDragEnd } =
|
||||||
|
useCanvasDragMove();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="inpainting-canvas-container">
|
||||||
|
<div className="inpainting-canvas-wrapper">
|
||||||
|
<Stage
|
||||||
|
tabIndex={-1}
|
||||||
|
ref={canvasStageRefCallback}
|
||||||
|
className={'inpainting-canvas-stage'}
|
||||||
|
style={{
|
||||||
|
...(stageCursor ? { cursor: stageCursor } : {}),
|
||||||
|
}}
|
||||||
|
x={stageCoordinates.x}
|
||||||
|
y={stageCoordinates.y}
|
||||||
|
width={stageDimensions.width}
|
||||||
|
height={stageDimensions.height}
|
||||||
|
scale={{ x: stageScale, y: stageScale }}
|
||||||
|
onTouchStart={handleMouseDown}
|
||||||
|
onTouchMove={handleMouseMove}
|
||||||
|
onTouchEnd={handleMouseUp}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseOut}
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
onDragMove={handleDragMove}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
listening={(tool === 'move' || isStaging) && !isModifyingBoundingBox}
|
||||||
|
draggable={(tool === 'move' || isStaging) && !isModifyingBoundingBox}
|
||||||
|
>
|
||||||
|
<Layer id={'grid'} visible={shouldShowGrid}>
|
||||||
|
<IAICanvasGrid />
|
||||||
|
</Layer>
|
||||||
|
|
||||||
|
<Layer
|
||||||
|
id={'base'}
|
||||||
|
ref={canvasBaseLayerRefCallback}
|
||||||
|
listening={false}
|
||||||
|
imageSmoothingEnabled={false}
|
||||||
|
>
|
||||||
|
<IAICanvasObjectRenderer />
|
||||||
|
</Layer>
|
||||||
|
<Layer id={'mask'} visible={isMaskEnabled} listening={false}>
|
||||||
|
<IAICanvasMaskLines visible={true} listening={false} />
|
||||||
|
<IAICanvasMaskCompositer listening={false} />
|
||||||
|
</Layer>
|
||||||
|
<Layer id="preview" imageSmoothingEnabled={false}>
|
||||||
|
{!isStaging && (
|
||||||
|
<IAICanvasToolPreview
|
||||||
|
visible={tool !== 'move'}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<IAICanvasStagingArea visible={isStaging} />
|
||||||
|
{shouldShowIntermediates && <IAICanvasIntermediateImage />}
|
||||||
|
<IAICanvasBoundingBox
|
||||||
|
visible={shouldShowBoundingBox && !isStaging}
|
||||||
|
/>
|
||||||
|
</Layer>
|
||||||
|
</Stage>
|
||||||
|
<IAICanvasStatusText />
|
||||||
|
<IAICanvasStagingAreaToolbar />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvas;
|
115
frontend/src/features/canvas/components/IAICanvasGrid.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/
|
||||||
|
|
||||||
|
import { useColorMode } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { ReactNode, useCallback, useLayoutEffect, useState } from 'react';
|
||||||
|
import { Group, Line as KonvaLine } from 'react-konva';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[canvasSelector],
|
||||||
|
(canvas) => {
|
||||||
|
const { stageScale, stageCoordinates, stageDimensions } = canvas;
|
||||||
|
return { stageScale, stageCoordinates, stageDimensions };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const gridLinesColor = {
|
||||||
|
dark: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
green: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
light: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const IAICanvasGrid = () => {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
const { stageScale, stageCoordinates, stageDimensions } =
|
||||||
|
useAppSelector(selector);
|
||||||
|
const [gridLines, setGridLines] = useState<ReactNode[]>([]);
|
||||||
|
|
||||||
|
const unscale = useCallback(
|
||||||
|
(value: number) => {
|
||||||
|
return value / stageScale;
|
||||||
|
},
|
||||||
|
[stageScale]
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const gridLineColor = gridLinesColor[colorMode];
|
||||||
|
|
||||||
|
const { width, height } = stageDimensions;
|
||||||
|
const { x, y } = stageCoordinates;
|
||||||
|
|
||||||
|
const stageRect = {
|
||||||
|
x1: 0,
|
||||||
|
y1: 0,
|
||||||
|
x2: width,
|
||||||
|
y2: height,
|
||||||
|
offset: {
|
||||||
|
x: unscale(x),
|
||||||
|
y: unscale(y),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridOffset = {
|
||||||
|
x: Math.ceil(unscale(x) / 64) * 64,
|
||||||
|
y: Math.ceil(unscale(y) / 64) * 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridRect = {
|
||||||
|
x1: -gridOffset.x,
|
||||||
|
y1: -gridOffset.y,
|
||||||
|
x2: unscale(width) - gridOffset.x + 64,
|
||||||
|
y2: unscale(height) - gridOffset.y + 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridFullRect = {
|
||||||
|
x1: Math.min(stageRect.x1, gridRect.x1),
|
||||||
|
y1: Math.min(stageRect.y1, gridRect.y1),
|
||||||
|
x2: Math.max(stageRect.x2, gridRect.x2),
|
||||||
|
y2: Math.max(stageRect.y2, gridRect.y2),
|
||||||
|
};
|
||||||
|
|
||||||
|
const fullRect = gridFullRect;
|
||||||
|
|
||||||
|
const // find the x & y size of the grid
|
||||||
|
xSize = fullRect.x2 - fullRect.x1,
|
||||||
|
ySize = fullRect.y2 - fullRect.y1,
|
||||||
|
// compute the number of steps required on each axis.
|
||||||
|
xSteps = Math.round(xSize / 64) + 1,
|
||||||
|
ySteps = Math.round(ySize / 64) + 1;
|
||||||
|
|
||||||
|
const xLines = _.range(0, xSteps).map((i) => (
|
||||||
|
<KonvaLine
|
||||||
|
key={`x_${i}`}
|
||||||
|
x={fullRect.x1 + i * 64}
|
||||||
|
y={fullRect.y1}
|
||||||
|
points={[0, 0, 0, ySize]}
|
||||||
|
stroke={gridLineColor}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
const yLines = _.range(0, ySteps).map((i) => (
|
||||||
|
<KonvaLine
|
||||||
|
key={`y_${i}`}
|
||||||
|
x={fullRect.x1}
|
||||||
|
y={fullRect.y1 + i * 64}
|
||||||
|
points={[0, 0, xSize, 0]}
|
||||||
|
stroke={gridLineColor}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
setGridLines(xLines.concat(yLines));
|
||||||
|
}, [stageScale, stageCoordinates, stageDimensions, colorMode, unscale]);
|
||||||
|
|
||||||
|
return <Group>{gridLines}</Group>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasGrid;
|
15
frontend/src/features/canvas/components/IAICanvasImage.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Image } from 'react-konva';
|
||||||
|
import useImage from 'use-image';
|
||||||
|
|
||||||
|
type IAICanvasImageProps = {
|
||||||
|
url: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
const IAICanvasImage = (props: IAICanvasImageProps) => {
|
||||||
|
const { url, x, y } = props;
|
||||||
|
const [image] = useImage(url);
|
||||||
|
return <Image x={x} y={y} image={image} listening={false} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasImage;
|
@ -0,0 +1,59 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { RootState, useAppSelector } from 'app/store';
|
||||||
|
import { GalleryState } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { ImageConfig } from 'konva/lib/shapes/Image';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Image as KonvaImage } from 'react-konva';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[(state: RootState) => state.gallery],
|
||||||
|
(gallery: GalleryState) => {
|
||||||
|
return gallery.intermediateImage ? gallery.intermediateImage : null;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type Props = Omit<ImageConfig, 'image'>;
|
||||||
|
|
||||||
|
const IAICanvasIntermediateImage = (props: Props) => {
|
||||||
|
const { ...rest } = props;
|
||||||
|
const intermediateImage = useAppSelector(selector);
|
||||||
|
|
||||||
|
const [loadedImageElement, setLoadedImageElement] =
|
||||||
|
useState<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!intermediateImage) return;
|
||||||
|
const tempImage = new Image();
|
||||||
|
|
||||||
|
tempImage.onload = () => {
|
||||||
|
setLoadedImageElement(tempImage);
|
||||||
|
};
|
||||||
|
tempImage.src = intermediateImage.url;
|
||||||
|
}, [intermediateImage]);
|
||||||
|
|
||||||
|
if (!intermediateImage?.boundingBox) return null;
|
||||||
|
|
||||||
|
const {
|
||||||
|
boundingBox: { x, y, width, height },
|
||||||
|
} = intermediateImage;
|
||||||
|
|
||||||
|
return loadedImageElement ? (
|
||||||
|
<KonvaImage
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
image={loadedImageElement}
|
||||||
|
listening={false}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasIntermediateImage;
|
@ -0,0 +1,175 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store';
|
||||||
|
import { RectConfig } from 'konva/lib/shapes/Rect';
|
||||||
|
import { Rect } from 'react-konva';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
|
||||||
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import Konva from 'konva';
|
||||||
|
import { isNumber } from 'lodash';
|
||||||
|
|
||||||
|
export const canvasMaskCompositerSelector = createSelector(
|
||||||
|
canvasSelector,
|
||||||
|
(canvas) => {
|
||||||
|
const { maskColor, stageCoordinates, stageDimensions, stageScale } = canvas;
|
||||||
|
|
||||||
|
return {
|
||||||
|
stageCoordinates,
|
||||||
|
stageDimensions,
|
||||||
|
stageScale,
|
||||||
|
maskColorString: rgbaColorToString(maskColor),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type IAICanvasMaskCompositerProps = RectConfig;
|
||||||
|
|
||||||
|
const getColoredSVG = (color: string) => {
|
||||||
|
return `data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="60px" height="60px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,0)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,2.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,7.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,10)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,12.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,15)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,17.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,20)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,22.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,25)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,27.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,30)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-2.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-7.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-10)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-12.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-15)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-17.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-20)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-22.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-25)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-27.5)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,-30)">
|
||||||
|
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
|
||||||
|
</g>
|
||||||
|
</svg>`.replaceAll('black', color);
|
||||||
|
};
|
||||||
|
|
||||||
|
const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
||||||
|
const { ...rest } = props;
|
||||||
|
|
||||||
|
const { maskColorString, stageCoordinates, stageDimensions, stageScale } =
|
||||||
|
useAppSelector(canvasMaskCompositerSelector);
|
||||||
|
|
||||||
|
const [fillPatternImage, setFillPatternImage] =
|
||||||
|
useState<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
|
const [offset, setOffset] = useState<number>(0);
|
||||||
|
|
||||||
|
const rectRef = useRef<Konva.Rect>(null);
|
||||||
|
const incrementOffset = useCallback(() => {
|
||||||
|
setOffset(offset + 1);
|
||||||
|
setTimeout(incrementOffset, 500);
|
||||||
|
}, [offset]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fillPatternImage) return;
|
||||||
|
const image = new Image();
|
||||||
|
|
||||||
|
image.onload = () => {
|
||||||
|
setFillPatternImage(image);
|
||||||
|
};
|
||||||
|
image.src = getColoredSVG(maskColorString);
|
||||||
|
}, [fillPatternImage, maskColorString]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!fillPatternImage) return;
|
||||||
|
fillPatternImage.src = getColoredSVG(maskColorString);
|
||||||
|
}, [fillPatternImage, maskColorString]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => setOffset((i) => (i + 1) % 5), 50);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!fillPatternImage ||
|
||||||
|
!isNumber(stageCoordinates.x) ||
|
||||||
|
!isNumber(stageCoordinates.y) ||
|
||||||
|
!isNumber(stageScale) ||
|
||||||
|
!isNumber(stageDimensions.width) ||
|
||||||
|
!isNumber(stageDimensions.height)
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Rect
|
||||||
|
ref={rectRef}
|
||||||
|
offsetX={stageCoordinates.x / stageScale}
|
||||||
|
offsetY={stageCoordinates.y / stageScale}
|
||||||
|
height={stageDimensions.height / stageScale}
|
||||||
|
width={stageDimensions.width / stageScale}
|
||||||
|
fillPatternImage={fillPatternImage}
|
||||||
|
fillPatternOffsetY={!isNumber(offset) ? 0 : offset}
|
||||||
|
fillPatternRepeat={'repeat'}
|
||||||
|
fillPatternScale={{ x: 1 / stageScale, y: 1 / stageScale }}
|
||||||
|
listening={true}
|
||||||
|
globalCompositeOperation={'source-in'}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasMaskCompositer;
|
@ -0,0 +1,54 @@
|
|||||||
|
import { GroupConfig } from 'konva/lib/Group';
|
||||||
|
import { Group, Line } from 'react-konva';
|
||||||
|
import { useAppSelector } from 'app/store';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
import { isCanvasMaskLine } from '../store/canvasTypes';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export const canvasLinesSelector = createSelector(
|
||||||
|
[canvasSelector],
|
||||||
|
(canvas) => {
|
||||||
|
return { objects: canvas.layerState.objects };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type InpaintingCanvasLinesProps = GroupConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the lines which comprise the mask.
|
||||||
|
*
|
||||||
|
* Uses globalCompositeOperation to handle the brush and eraser tools.
|
||||||
|
*/
|
||||||
|
const IAICanvasLines = (props: InpaintingCanvasLinesProps) => {
|
||||||
|
const { ...rest } = props;
|
||||||
|
const { objects } = useAppSelector(canvasLinesSelector);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group listening={false} {...rest}>
|
||||||
|
{objects.filter(isCanvasMaskLine).map((line, i) => (
|
||||||
|
<Line
|
||||||
|
key={i}
|
||||||
|
points={line.points}
|
||||||
|
stroke={'rgb(0,0,0)'} // The lines can be any color, just need alpha > 0
|
||||||
|
strokeWidth={line.strokeWidth * 2}
|
||||||
|
tension={0}
|
||||||
|
lineCap="round"
|
||||||
|
lineJoin="round"
|
||||||
|
shadowForStrokeEnabled={false}
|
||||||
|
listening={false}
|
||||||
|
globalCompositeOperation={
|
||||||
|
line.tool === 'brush' ? 'source-over' : 'destination-out'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasLines;
|
@ -0,0 +1,62 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { Group, Line } from 'react-konva';
|
||||||
|
import { isCanvasBaseImage, isCanvasBaseLine } from '../store/canvasTypes';
|
||||||
|
import IAICanvasImage from './IAICanvasImage';
|
||||||
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[canvasSelector],
|
||||||
|
(canvas) => {
|
||||||
|
const {
|
||||||
|
layerState: { objects },
|
||||||
|
} = canvas;
|
||||||
|
return {
|
||||||
|
objects,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const IAICanvasObjectRenderer = () => {
|
||||||
|
const { objects } = useAppSelector(selector);
|
||||||
|
|
||||||
|
if (!objects) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group name="outpainting-objects" listening={false}>
|
||||||
|
{objects.map((obj, i) => {
|
||||||
|
if (isCanvasBaseImage(obj)) {
|
||||||
|
return (
|
||||||
|
<IAICanvasImage key={i} x={obj.x} y={obj.y} url={obj.image.url} />
|
||||||
|
);
|
||||||
|
} else if (isCanvasBaseLine(obj)) {
|
||||||
|
return (
|
||||||
|
<Line
|
||||||
|
key={i}
|
||||||
|
points={obj.points}
|
||||||
|
stroke={obj.color ? rgbaColorToString(obj.color) : 'rgb(0,0,0)'} // The lines can be any color, just need alpha > 0
|
||||||
|
strokeWidth={obj.strokeWidth * 2}
|
||||||
|
tension={0}
|
||||||
|
lineCap="round"
|
||||||
|
lineJoin="round"
|
||||||
|
shadowForStrokeEnabled={false}
|
||||||
|
listening={false}
|
||||||
|
globalCompositeOperation={
|
||||||
|
obj.tool === 'brush' ? 'source-over' : 'destination-out'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasObjectRenderer;
|
79
frontend/src/features/canvas/components/IAICanvasResizer.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { Spinner } from '@chakra-ui/react';
|
||||||
|
import { useLayoutEffect, useRef } from 'react';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||||
|
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||||
|
import {
|
||||||
|
resizeAndScaleCanvas,
|
||||||
|
resizeCanvas,
|
||||||
|
setCanvasContainerDimensions,
|
||||||
|
setDoesCanvasNeedScaling,
|
||||||
|
} from 'features/canvas/store/canvasSlice';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
canvasSelector,
|
||||||
|
initialCanvasImageSelector,
|
||||||
|
} from 'features/canvas/store/canvasSelectors';
|
||||||
|
|
||||||
|
const canvasResizerSelector = createSelector(
|
||||||
|
canvasSelector,
|
||||||
|
initialCanvasImageSelector,
|
||||||
|
activeTabNameSelector,
|
||||||
|
(canvas, initialCanvasImage, activeTabName) => {
|
||||||
|
const { doesCanvasNeedScaling, isCanvasInitialized } = canvas;
|
||||||
|
return {
|
||||||
|
doesCanvasNeedScaling,
|
||||||
|
activeTabName,
|
||||||
|
initialCanvasImage,
|
||||||
|
isCanvasInitialized,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const IAICanvasResizer = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const {
|
||||||
|
doesCanvasNeedScaling,
|
||||||
|
activeTabName,
|
||||||
|
initialCanvasImage,
|
||||||
|
isCanvasInitialized,
|
||||||
|
} = useAppSelector(canvasResizerSelector);
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
|
||||||
|
const { clientWidth, clientHeight } = ref.current;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setCanvasContainerDimensions({
|
||||||
|
width: clientWidth,
|
||||||
|
height: clientHeight,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isCanvasInitialized) {
|
||||||
|
dispatch(resizeAndScaleCanvas());
|
||||||
|
} else {
|
||||||
|
dispatch(resizeCanvas());
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(setDoesCanvasNeedScaling(false));
|
||||||
|
}, 0);
|
||||||
|
}, [
|
||||||
|
dispatch,
|
||||||
|
initialCanvasImage,
|
||||||
|
doesCanvasNeedScaling,
|
||||||
|
activeTabName,
|
||||||
|
isCanvasInitialized,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="inpainting-canvas-area">
|
||||||
|
<Spinner thickness="2px" speed="1s" size="xl" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasResizer;
|
@ -0,0 +1,84 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store';
|
||||||
|
import { GroupConfig } from 'konva/lib/Group';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { Group, Rect } from 'react-konva';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
import IAICanvasImage from './IAICanvasImage';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[canvasSelector],
|
||||||
|
(canvas) => {
|
||||||
|
const {
|
||||||
|
layerState: {
|
||||||
|
stagingArea: { images, selectedImageIndex },
|
||||||
|
},
|
||||||
|
shouldShowStagingImage,
|
||||||
|
shouldShowStagingOutline,
|
||||||
|
} = canvas;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentStagingAreaImage:
|
||||||
|
images.length > 0 ? images[selectedImageIndex] : undefined,
|
||||||
|
isOnFirstImage: selectedImageIndex === 0,
|
||||||
|
isOnLastImage: selectedImageIndex === images.length - 1,
|
||||||
|
shouldShowStagingImage,
|
||||||
|
shouldShowStagingOutline,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type Props = GroupConfig;
|
||||||
|
|
||||||
|
const IAICanvasStagingArea = (props: Props) => {
|
||||||
|
const { ...rest } = props;
|
||||||
|
const {
|
||||||
|
currentStagingAreaImage,
|
||||||
|
shouldShowStagingImage,
|
||||||
|
shouldShowStagingOutline,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
|
if (!currentStagingAreaImage) return null;
|
||||||
|
|
||||||
|
const {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
image: { width, height, url },
|
||||||
|
} = currentStagingAreaImage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group {...rest}>
|
||||||
|
{shouldShowStagingImage && <IAICanvasImage url={url} x={x} y={y} />}
|
||||||
|
{shouldShowStagingOutline && (
|
||||||
|
<Group>
|
||||||
|
<Rect
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
strokeWidth={1}
|
||||||
|
stroke={'black'}
|
||||||
|
strokeScaleEnabled={false}
|
||||||
|
/>
|
||||||
|
<Rect
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
dash={[4, 4]}
|
||||||
|
strokeWidth={1}
|
||||||
|
stroke={'white'}
|
||||||
|
strokeScaleEnabled={false}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasStagingArea;
|
@ -0,0 +1,180 @@
|
|||||||
|
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
FaArrowLeft,
|
||||||
|
FaArrowRight,
|
||||||
|
FaCheck,
|
||||||
|
FaEye,
|
||||||
|
FaEyeSlash,
|
||||||
|
FaSave,
|
||||||
|
FaTrash,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
import {
|
||||||
|
commitStagingAreaImage,
|
||||||
|
discardStagedImages,
|
||||||
|
nextStagingAreaImage,
|
||||||
|
prevStagingAreaImage,
|
||||||
|
setShouldShowStagingImage,
|
||||||
|
setShouldShowStagingOutline,
|
||||||
|
} from 'features/canvas/store/canvasSlice';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[canvasSelector],
|
||||||
|
(canvas) => {
|
||||||
|
const {
|
||||||
|
layerState: {
|
||||||
|
stagingArea: { images, selectedImageIndex },
|
||||||
|
},
|
||||||
|
shouldShowStagingOutline,
|
||||||
|
shouldShowStagingImage,
|
||||||
|
} = canvas;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentStagingAreaImage:
|
||||||
|
images.length > 0 ? images[selectedImageIndex] : undefined,
|
||||||
|
isOnFirstImage: selectedImageIndex === 0,
|
||||||
|
isOnLastImage: selectedImageIndex === images.length - 1,
|
||||||
|
shouldShowStagingImage,
|
||||||
|
shouldShowStagingOutline,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const IAICanvasStagingAreaToolbar = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const {
|
||||||
|
isOnFirstImage,
|
||||||
|
isOnLastImage,
|
||||||
|
currentStagingAreaImage,
|
||||||
|
shouldShowStagingImage,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
|
const handleMouseOver = useCallback(() => {
|
||||||
|
dispatch(setShouldShowStagingOutline(false));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleMouseOut = useCallback(() => {
|
||||||
|
dispatch(setShouldShowStagingOutline(true));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['left'],
|
||||||
|
() => {
|
||||||
|
handlePrevImage();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['right'],
|
||||||
|
() => {
|
||||||
|
handleNextImage();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['enter'],
|
||||||
|
() => {
|
||||||
|
handleAccept();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePrevImage = () => dispatch(prevStagingAreaImage());
|
||||||
|
const handleNextImage = () => dispatch(nextStagingAreaImage());
|
||||||
|
const handleAccept = () => dispatch(commitStagingAreaImage());
|
||||||
|
|
||||||
|
if (!currentStagingAreaImage) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
pos={'absolute'}
|
||||||
|
bottom={'1rem'}
|
||||||
|
w={'100%'}
|
||||||
|
align={'center'}
|
||||||
|
justify={'center'}
|
||||||
|
filter="drop-shadow(0 0.5rem 1rem rgba(0,0,0))"
|
||||||
|
onMouseOver={handleMouseOver}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
|
>
|
||||||
|
<ButtonGroup isAttached>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Previous (Left)"
|
||||||
|
aria-label="Previous (Left)"
|
||||||
|
icon={<FaArrowLeft />}
|
||||||
|
onClick={handlePrevImage}
|
||||||
|
data-selected={true}
|
||||||
|
isDisabled={isOnFirstImage}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Next (Right)"
|
||||||
|
aria-label="Next (Right)"
|
||||||
|
icon={<FaArrowRight />}
|
||||||
|
onClick={handleNextImage}
|
||||||
|
data-selected={true}
|
||||||
|
isDisabled={isOnLastImage}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Accept (Enter)"
|
||||||
|
aria-label="Accept (Enter)"
|
||||||
|
icon={<FaCheck />}
|
||||||
|
onClick={handleAccept}
|
||||||
|
data-selected={true}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Show/Hide"
|
||||||
|
aria-label="Show/Hide"
|
||||||
|
data-alert={!shouldShowStagingImage}
|
||||||
|
icon={shouldShowStagingImage ? <FaEye /> : <FaEyeSlash />}
|
||||||
|
onClick={() =>
|
||||||
|
dispatch(setShouldShowStagingImage(!shouldShowStagingImage))
|
||||||
|
}
|
||||||
|
data-selected={true}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Save to Gallery"
|
||||||
|
aria-label="Save to Gallery"
|
||||||
|
icon={<FaSave />}
|
||||||
|
onClick={() =>
|
||||||
|
dispatch(
|
||||||
|
saveStagingAreaImageToGallery(currentStagingAreaImage.image.url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
data-selected={true}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Discard All"
|
||||||
|
aria-label="Discard All"
|
||||||
|
icon={<FaTrash />}
|
||||||
|
onClick={() => dispatch(discardStagedImages())}
|
||||||
|
data-selected={true}
|
||||||
|
style={{ backgroundColor: 'var(--btn-delete-image)' }}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasStagingAreaToolbar;
|