Compare commits

..

196 Commits

Author SHA1 Message Date
Ryan Dick
9b763b9e4c Fix issue with seamless context managers when seamless is not configured. 2024-01-05 10:31:58 -05:00
Sergey Borisov
7f3be627c2 Add more seamless configuration options. 2024-01-05 09:57:28 -05:00
psychedelicious
cb7e56a9a3 chore(ui): lint 2024-01-05 08:34:46 -05:00
psychedelicious
1a710a4c12 fix(ui): restore prev colors for workflow editor
Brand colors are now prefixed with "invoke".
2024-01-05 08:34:46 -05:00
psychedelicious
d8d266d3be fix(ui): fix field title spacing
Closes #5405
2024-01-05 08:34:46 -05:00
psychedelicious
4716632c23 fix(ui): tsc 2024-01-06 00:03:07 +11:00
psychedelicious
3c4150d153 fix(ui): update most other selectors
Just a few stragglers left. Good enough for now.
2024-01-06 00:03:07 +11:00
psychedelicious
b71b14d582 fix(ui): update workflow selectors 2024-01-06 00:03:07 +11:00
psychedelicious
73481d4aec feat(ui): clean up canvas selectors
Do not memoize unless absolutely necessary. Minor perf improvement
2024-01-06 00:03:07 +11:00
psychedelicious
2c049a3b94 feat(ui): clean up a few selectors that do not need to be memoized 2024-01-06 00:03:07 +11:00
psychedelicious
367de44a8b fix(ui): tidy remaining selectors
These were just using overly verbose syntax - like explicitly typing `state: RootState`, which is unnecessary.
2024-01-06 00:03:07 +11:00
psychedelicious
f5f378d04b fix(ui): revert back to lrumemoize 2024-01-06 00:03:07 +11:00
psychedelicious
823edbfdef fix(ui): fix more state => state selectors 2024-01-06 00:03:07 +11:00
psychedelicious
29bbb27289 fix(ui): re-add reselect patch
Accidentally removed it last commit.
2024-01-06 00:03:07 +11:00
psychedelicious
a23502f7ff fix(ui): do not use state => state as an input selector
This is a no-no, whoops!
2024-01-06 00:03:07 +11:00
psychedelicious
ce64dbefce chore(ui): lint 2024-01-06 00:03:07 +11:00
psychedelicious
b47afdc3b5 feat(ui): patch reselect to use lruMemoize only
Pending resolution of https://github.com/reduxjs/reselect/issues/635, we can patch `reselect` to use `lruMemoize` exclusively.

Pin RTK and react-redux versions too just to be safe.

This reduces the major GC events that were causing lag/stutters in the app, particularly in canvas and workflow editor.
2024-01-06 00:03:07 +11:00
psychedelicious
cde9c3090f fix(ui): use useAppSelector instead of useSelector 2024-01-06 00:03:07 +11:00
psychedelicious
6924b04d7c feat(ui): use lruMemoize for all entity adapter selectors 2024-01-06 00:03:07 +11:00
blessedcoolant
83fbd4bdf2 ui: slightly reposition floating bars. 2024-01-05 23:59:08 +11:00
Lincoln Stein
6460dcc7e0 use torch.bfloat16 on cuda systems 2024-01-04 23:25:52 -05:00
Millun Atluri
59aa009c93 {release} v3.6.0rc3 (#5408)
## What type of PR is this? (check all applicable)

Release v3.6.0rc3


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [] Yes
- [X] No


## Description
Next release candidate

## Related Tickets & Documents
N/A
<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings
[Uploading InvokeAI-installer-v3.6.0rc3.zip…](Installer zip)

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Merge Plan
This PR can be merged when approved
<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
Release on PyPI & Github
2024-01-05 10:39:20 +11:00
Millun Atluri
59d2a012cd {release} v3.6.0rc3 2024-01-05 09:40:21 +11:00
Kent Keirsey
7e3b620830 Update README.md 2024-01-04 15:56:44 -05:00
psychedelicious
e16b55816f fix(ui): clarify comparison in usePanel 2024-01-05 07:09:37 +11:00
psychedelicious
895cb8637e fix(ui): fix panel resize bug
A bug that caused panels to be collapsed on a fresh indexedDb in was fixed in dd32c632cd, but this re-introduced a different bug that caused the panels to expand on window resize, if they were already collapsed.

Revert the previous change and instead add one imperative resize outside the observer, so that on startup, we set both panels to their minimum sizes.
2024-01-05 07:09:37 +11:00
Riccardo Giovanetti
fe5bceb1ed translationBot(ui): update translation (Italian)
Currently translated at 97.3% (1363 of 1400 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-01-05 07:04:04 +11:00
Mary Hipp
5d475a40f5 lint 2024-01-04 12:51:36 -05:00
Mary Hipp
bca7ea1674 option to override logo component 2024-01-04 12:51:36 -05:00
Mary Hipp
f27bb402fb render one or the other 2024-01-04 11:11:10 -05:00
Mary Hipp Rogers
dd32c632cd fix default panel width (#5403)
* fix default panel width

* lint

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
2024-01-04 11:04:21 -05:00
Mary Hipp Rogers
9e2e740033 custom components for nav, gallery header, and app info (#5400)
* replace custom header with custom nav component to go below settings

* add option for custom gallery header

* add option for custom app info text on logo hover

* add data-testid for tabs

* remove descriptions

* lint

* lint

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
2024-01-04 15:30:27 +00:00
psychedelicious
d6362ce0bd fix(ui): fix invalid nesting of button in button 2024-01-04 09:36:59 -05:00
psychedelicious
2347a00a70 fix(ui): do not show loading state on floating invoke button if disabled 2024-01-04 09:36:59 -05:00
psychedelicious
0b7dc721cf chore(ui): lint 2024-01-04 09:36:59 -05:00
psychedelicious
ac04a834ef feat(ui): tidy hotkeysmodal state 2024-01-04 09:36:59 -05:00
psychedelicious
bbca053b48 feat(ui): style settings modal 2024-01-04 09:36:59 -05:00
psychedelicious
fcf2006502 feat(ui): increase brightnesst of accordion title 2024-01-04 09:36:59 -05:00
psychedelicious
ac0d0019bd chore(ui): lint 2024-01-04 09:36:59 -05:00
psychedelicious
2d922a0a65 feat(ui): restore floating options and gallery buttons 2024-01-04 09:36:59 -05:00
psychedelicious
8db14911d7 feat(ui): give tooltips padding from screen edge
We can pass a popperjs modifier to the tooltip to give it this padding.
2024-01-04 09:36:59 -05:00
psychedelicious
01bab58b20 fix(ui): do not resize panel when window resizes if panel is collapsed 2024-01-04 09:36:59 -05:00
psychedelicious
7a57bc99cf feat(ui): statusindicator changes
We are now using the lefthand vertical strip for the settings menu button. This is a good place for the status indicator.

Really, we only need to display something *if there is a problem*. If the app is processing, the progress bar indicates that.

For the case where the panels are collapsed, I'll add the floating buttons back in some form, and we'll indicate via those if the app is processing something.
2024-01-04 09:36:59 -05:00
psychedelicious
d3b6d86e74 feat(ui): tweak badge styles 2024-01-04 09:36:59 -05:00
psychedelicious
360b6cb286 fix(ui): fix logo version tooltip 2024-01-04 09:36:59 -05:00
psychedelicious
8f9e9e639e fix(ui): fix hotkey key & untranslated string 2024-01-04 09:36:59 -05:00
psychedelicious
6930d8ba41 feat(ui): do not wrap expander content in box 2024-01-04 09:36:59 -05:00
psychedelicious
7ad74e680d feat(ui): make invexpander button styles less complex
just make it like a normal button - normal and hover state, no difference when its expanded. the icon clearly indicates this, and you see the extra components
2024-01-04 09:36:59 -05:00
psychedelicious
c56a6a4ddd feat(ui): make expander divider button, add hover, remove color
On one hand I like the color but on the other it makes this divider a focus point, which doesn't really makes sense to me. I tried several shades but think it adds a bit too much distraction for your eyes.
2024-01-04 09:36:59 -05:00
psychedelicious
afad764a00 feat(ui): make badges a bit paler
too stabby in the eye region
2024-01-04 09:36:59 -05:00
psychedelicious
49a72bd714 feat(ui): use wrench icon in settings menu for settings 2024-01-04 09:36:59 -05:00
psychedelicious
8cf14287b6 feat(ui): simplify App.tsx layout
There was an extra div, needed for the fullscreen file upload dropzone, that made styling the main app containers a bit awkward.

Refactor the uploader a bit to simplify this - no longer need so many app-level wrappers. Much cleaner.
2024-01-04 09:36:59 -05:00
blessedcoolant
0db47dd5e7 ui: Bolden text & add activation color for expanded state 2024-01-04 09:36:59 -05:00
blessedcoolant
71f6f77ae8 ui: Change background and padding of advanced settings 2024-01-04 09:36:59 -05:00
blessedcoolant
6f16229c41 fix: tone down the base color saturation by one step 2024-01-04 09:36:59 -05:00
blessedcoolant
0cc0d794d1 fix: Minor alignment issues with the queue badge 2024-01-04 09:36:59 -05:00
blessedcoolant
535639cb95 feat: Update status and progress colors to match new theme 2024-01-04 09:36:59 -05:00
blessedcoolant
2250bca8d9 feat: Remove Header
Remove header and incorporate everything else into the side bar and other areas
2024-01-04 09:36:59 -05:00
psychedelicious
4ce39a5974 fix(ui): remove unused icons 2024-01-04 13:59:25 +11:00
psychedelicious
644e9287f0 chore(ui): control adapter docstrings 2024-01-04 13:59:25 +11:00
psychedelicious
6a5e0be022 fix(ui): reduce minStepsBetweenThumbs for ca begin/end 2024-01-04 13:59:25 +11:00
psychedelicious
707f0f7091 feat(ui): update favicon to new logo 2024-01-04 13:59:25 +11:00
psychedelicious
8e709fe05a fix(ui): remove shift+enter to cancel
Whoops!
2024-01-04 13:59:25 +11:00
Hosted Weblate
154da609cb translationBot(ui): update translation files
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI
2024-01-04 01:19:31 +11:00
psychedelicious
21975d6268 chore(ui): lint 2024-01-03 09:09:50 -05:00
psychedelicious
31035b3e63 fix(ui): fix scaled bbox sliders
Removed logic related to aspect ratio from the components.

When the main bbox changes, if the scale method is auto, the reducers will handle the scaled bbox size appropriately.

Somehow linking up the manual mode to the aspect ratio is tricky, and instead of adding complexity for a rarely-used mode, I'm leaving manual mode as fully manual.
2024-01-03 09:09:50 -05:00
psychedelicious
6c05818887 fix(ui): workaround canvas weirdness with locked aspect ratio
Cannot figure out how to allow the bbox to be transformed when aspect ratio is locked from all handles. Only the bottom right handle works as expected.

As a workaround, when the aspect ratio is locked, you can only resize the bbox from the bottom right handle.
2024-01-03 09:09:50 -05:00
psychedelicious
77c5b051f0 fix(ui): clean up actionsDenylist 2024-01-03 09:09:50 -05:00
psychedelicious
4fdc4c15f9 feat(ui): add optimal size handling 2024-01-03 09:09:50 -05:00
psychedelicious
1a4be78013 fix(ui): make aspect ratio preview pixel-perfect size 2024-01-03 09:09:50 -05:00
psychedelicious
eb16ad3d6f fix(ui): remove old esc hotkey 2024-01-03 09:09:50 -05:00
psychedelicious
1fee08639d feat(ui): tweak board search UI 2024-01-03 09:09:50 -05:00
psychedelicious
7caaf40835 feat(ui): reworked hotkeys modal
- Displays all as list
- Uses chakra `Kbd` component for keys
- Provides search box
2024-01-03 09:09:50 -05:00
psychedelicious
6bfe994622 feat(ui): bump fontSize in fallback compoennt 2024-01-03 09:09:50 -05:00
psychedelicious
8a6f03cd46 feat(ui): improved panel interactions 2024-01-03 09:09:50 -05:00
psychedelicious
4ce9f9dc36 fix(ui): scaled bounding box uses canvas aspect ratio 2024-01-03 09:09:50 -05:00
Millun Atluri
00297716d6 Release: v3.6.0rc2 (#5386)
## What type of PR is this? (check all applicable)

Release

## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description
v3.6.0rc2 release

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings
Test latest main & [Uploading
InvokeAI-installer-v3.6.0rc2.zip…](Installer zip)

## Merge Plan
PR can be merged immediately
<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->

## Added/updated tests?

- [ ] Yes
- [X] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
Publish release on PyPI and GitHub
2024-01-03 14:58:45 +11:00
Millun Atluri
50c0dc71eb {release} v3.6.0rc2 2024-01-03 14:21:10 +11:00
psychedelicious
29ccc6a3d8 chore(ui): lint 2024-01-03 13:18:50 +11:00
psychedelicious
f92a5cbabc fix(ui): fix up hotkeys
- Add Shift+X back (this has been missing for a long time)
- Add secondary toggle options hotkey
2024-01-03 13:18:50 +11:00
psychedelicious
acbf10f7ba feat(ui): improve error indicator for model fields 2024-01-03 13:18:50 +11:00
psychedelicious
46d830b9fa feat(ui): new logo! 2024-01-03 13:18:50 +11:00
psychedelicious
db17ec7a4b feat(ui): use dropzone noKeyboard opt instead of manual listener to disable on spacebar 2024-01-03 13:18:50 +11:00
psychedelicious
6320d18846 fix(ui): upscale dropdown activates when clicking image actions
Weird issue with `react-select`... Made the popover lazy as a workaround.

Also updated styling of the popover.
2024-01-03 13:18:50 +11:00
psychedelicious
37c8b9d06a fix(ui): fix sdxl style prompts
- Do not _merge_ prompt and style prompt when concat is enabled - either use the prompt as style, or use the style directly.
- Set style prompt metadata correctly.
- Add metadata recall for style prompt.
2024-01-03 13:18:50 +11:00
psychedelicious
7ba2108eb0 fix(ui): increase contrast between disabled and enabled inputs 2024-01-03 13:18:50 +11:00
psychedelicious
8aeeee4752 fix(ui): fix erroneous vae model display
`react-select` has some weird behaviour where if the value is `undefined`, it shows the last-selected value instead of nothing. Must fall back to `null`
2024-01-03 13:18:50 +11:00
psychedelicious
930de51910 feat(ui): add badges for advanced settings 2024-01-03 13:18:50 +11:00
psychedelicious
b1b5c0d3b2 fix(ui): fix workflow editor model selector, excise ONNX
Ensure workflow editor model selector component gets a value

This introduced some funky type issues related to ONNX models. ONNX doesn't work anyways (unmaintained). Instead of fixing the types to work with a non-working feature, ONNX is now removed entirely from the UI.

- Remove all refs to ONNX (and Olives)
- Fix some type issues
- Add ONNX nodes to the nodes denylist (so they are not visible in UI)
- Update VAE graph helper, which still had some ONNX logic. It's a very simple change and doesn't change any logic. Just removes some conditions that were for ONNX. I tested it and nothing broke.
- Regenerate types
- Fix prettier and eslint ignores for generated types
- Lint
2024-01-03 13:18:50 +11:00
psychedelicious
ebe717099e feat(ui): add $store to window in dev mode
Helpful for troubleshooting.
2024-01-03 13:18:50 +11:00
psychedelicious
06245bc761 feat(ui): add support for default values for sliders 2024-01-03 13:18:50 +11:00
psychedelicious
b4c0dafdc8 feat(ui): remove unused iterations component 2024-01-03 13:18:50 +11:00
psychedelicious
0cefacb3a2 feat(ui): add support for default values for numberinputs 2024-01-03 13:18:50 +11:00
psychedelicious
baa5f75976 fix(ui): fix node styles
Got borked when adjusting control adapter styling. Should revisit this later.
2024-01-03 13:18:50 +11:00
psychedelicious
989aaedc7f feat(nodes): add title for cfg rescale mult on denoise_latents 2024-01-03 13:18:50 +11:00
psychedelicious
93e08df849 fix(ui): min fallback on nodes number fields -> -NUMPY_RAND_MAX 2024-01-03 13:18:50 +11:00
psychedelicious
4a43e1c1b8 fix(ui): restore global hotkeys 2024-01-03 13:18:50 +11:00
Millun Atluri
2bbab9d94e Update recommends db backup when installing RC (#5381)
* Udpater suggest db backup when installing RC

* Update invokeai_update.py to be more specific

* Update invokeai_update.py

* Update invokeai_update.py

* Update invokeai_update.py

* Update invokeai_update.py
2024-01-02 22:44:45 +00:00
Mary Hipp
a456f6e6f0 lint 2024-01-02 10:02:33 -05:00
Mary Hipp
a408f562d6 option to use new brand for loader 2024-01-02 10:02:33 -05:00
Mary Hipp
cefdf9ed00 define text color for tooltips 2024-01-02 10:02:33 -05:00
David Sisco
5413bf07e2 Sisco/docker allow relative paths for invokeai data (#5344)
* Update docker-compose.yml to bind local data path

* Update LOCAL_DATA_PATH in .env.sample

* Add fallback to INVOKEAI_ROOT envar if LOCAL_DATA_PATH not present.

* rename LOCAL_DATA_PATH to INVOKAI_LOCAL_ROOT

* Whoops, didnt mean to include this

* Update docker/docker-compose.yml

Co-authored-by: Eugene Brodsky <ebr@users.noreply.github.com>

* [chore] rename envar

* Apply suggestions from code review

---------

Co-authored-by: Eugene Brodsky <ebr@users.noreply.github.com>
2024-01-02 13:17:57 +00:00
psychedelicious
4cffe282bd feat(ui): disable scan models tab
not working yet WIP
2024-01-02 07:28:53 -05:00
psychedelicious
ae8ffe9d51 chore(ui): lint 2024-01-02 07:28:53 -05:00
psychedelicious
870cc5b733 feat(ui): dynamic prompts loading ux
- Prompt must have an open curly brace followed by a close curly brace to enable dynamic prompts processing
- If a the given prompt already had a dynamic prompt cached, do not re-process
- If processing is not needed, user may invoke immediately
- Invoke button shows loading state when dynamic prompts are processing, tooltip says generating
- Dynamic prompts preview icon in prompt box shows loading state when processing, tooltip says generating
2024-01-02 07:28:53 -05:00
psychedelicious
0b4eb888c5 feat(ui): canvas bbox interaction tweaks
Making the math match the previous implementation
2024-01-02 07:28:53 -05:00
psychedelicious
11f1cb5391 fix(ui): fix canvas bbox style when cursor leaves canvas 2024-01-02 07:28:53 -05:00
psychedelicious
1e2e26cfc2 feat(ui): add open queue to queue action menu 2024-01-02 07:28:53 -05:00
psychedelicious
e9bce6e1c3 fix(ui): fix cut off badge on queue actions menu 2024-01-02 07:28:53 -05:00
psychedelicious
799ef0e7c1 fix(ui): control adapter models select disable if incompatible 2024-01-02 07:28:53 -05:00
psychedelicious
61c10a7ca8 fix(ui): fix canvas bbox interactions 2024-01-02 07:28:53 -05:00
psychedelicious
93880223e6 feat(ui): move strength up one 2024-01-02 07:28:53 -05:00
psychedelicious
271456b745 fix(ui): fix badges for image settings canvas 2024-01-02 07:28:53 -05:00
psychedelicious
cecee33bc0 feat(ui): support grid size of 8 on canvas
- Support grid size of 8 on canvas
- Internal canvas math works on 8
- Update gridlines rendering to show 64 spaced lines and 32/16/8 when zoomed in
- Bbox manipulation defaults to grid of 64 - hold shift to get grid of 8

Besides being something we support internally, supporting 8 on canvas avoids a lot of hacky logic needed to work well with aspect ratios.
2024-01-02 07:28:53 -05:00
psychedelicious
4f43eda09b feat(ui): modularize imagesize components
Canvas and non-canvas have separate width and height and need their own separate aspect ratios. In order to not duplicate a lot of aspect ratio logic, the components relating to image size have been modularized.
2024-01-02 07:28:53 -05:00
psychedelicious
011757c497 fix(ui): add numberinput to control adapter weight
Required some rejiggering of the InvControl and InvSlider styles.
2024-01-02 07:28:53 -05:00
psychedelicious
2700d0e769 fix(nodes): fix constraints/validation for controlnet
- Fix `weight` and `begin_step_percent`, the constraints were mixed up
- Add model validatort to ensure `begin_step_percent < end_step_percent`
- Bump version
2024-01-02 07:28:53 -05:00
psychedelicious
d256d93a2a feat(ui): use larger chevrons for number input steppers 2024-01-02 07:28:53 -05:00
psychedelicious
f3c8e986a5 feat(ui): bump badge fontsize to 10px 2024-01-02 07:28:53 -05:00
psychedelicious
48f5e4f313 fix(ui): missing denoise strength
accidentally hid it from everywhere
2024-01-02 07:28:53 -05:00
Millun Atluri
5950ffe064 Release/v3.6.0rc1 (#5372)
## What type of PR is this? (check all applicable)

InvokeAI 3.6.0rc1 Release 


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description
Update version & frontend build for Invoke v3.6.0rc1

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Merge Plan

<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?

Upload release to PyPI & create release on GitHub
2024-01-02 11:21:37 +11:00
Millun Atluri
49ca949cd6 {release} Update version to 3.6.0rc1 2024-01-02 10:23:55 +11:00
Millun Atluri
5d69f1cbf5 Remove frontend build from repo permanantly 2024-01-02 10:18:11 +11:00
psychedelicious
9169006171 chore(ui): lint 2024-01-01 08:13:23 -05:00
psychedelicious
28b74523d0 fix(ui): fix dynamic prompts with single prompt
Closes #5292

The special handling for single prompt is totally extraneous and caused a bug.
2024-01-01 08:13:23 -05:00
psychedelicious
9359c03c3c feat(ui): use zod-less workflow builder when appropriate 2024-01-01 08:13:23 -05:00
psychedelicious
598241e0f2 fix(ui): InvContextMenu.placement = 'auto-end'
This ensures the context menus don't get cut off when the window size is very small.
2024-01-01 08:13:23 -05:00
psychedelicious
e698a8006c feat(ui): use lruMemoize for argsMemoize on selectors
This provides a small performance improvement, on the order of a few ms per interaction.
2024-01-01 08:13:23 -05:00
psychedelicious
34e7b5a7fb chore(ui): lint 2024-01-01 08:13:23 -05:00
psychedelicious
5c3dd62ae0 feat(ui): update useGlobalModifiers to store each key independently
This reduces rerenders when the user presses a modifier key.
2024-01-01 08:13:23 -05:00
psychedelicious
7e2eeec1f3 feat(ui): optimized workflow building
- Store workflow in nanostore as singleton instead of building for each consumer
- Debounce the build (already was indirectly debounced)
- When the workflow is needed, imperatively grab it from the nanostores, instead of letting react handle it via reactivity
2024-01-01 08:13:23 -05:00
psychedelicious
7eb79266c4 feat(ui): split dnd overlay to separate component
This reduces top-level rerenders when zooming in and out on workflow editor
2024-01-01 08:13:23 -05:00
psychedelicious
5d4610d981 feat(ui): store node templates in separate slice
Flattens the `nodes` slice. May offer minor perf improvements in addition to just being cleaner.
2024-01-01 08:13:23 -05:00
psychedelicious
7c548c5bf3 feat(ui): move canvas interaction state to nanostores
This drastically reduces the computation needed when moving the cursor. It also correctly separates ephemeral interaction state from redux, where it is not needed.

Also removed some unused canvas state.
2024-01-01 08:13:23 -05:00
psychedelicious
2a38606342 fix(ui): show denoising strength on canvas 2024-01-01 08:13:23 -05:00
psychedelicious
793cf39964 feat(ui): bump react-resizable-panels & improve usePanel hook 2024-01-01 08:13:23 -05:00
psychedelicious
ab3e689ee0 fix(ui): fix workflow library new workflow/settings closing
Need to make the menu not lazy. A better solution is to refactor how the settings works, rendering it in a different part of the component tree
2024-01-01 08:13:23 -05:00
psychedelicious
20f497054f feat(ui): optimized useMouseOverNode
Manually hook into pubsub to eliminate extraneous rerenders on hook change
2024-01-01 08:13:23 -05:00
psychedelicious
6209fef63d fix(ui): focus add node popover on open
Need an extra ref to pass to the InvSelect component.
2024-01-01 08:13:23 -05:00
psychedelicious
5168415999 feat(ui): use nanostores for useMouseOverNode
This greatly reduces the weight of the event handlers.
2024-01-01 08:13:23 -05:00
psychedelicious
b490c8ae27 chore(ui): bump deps
Includes vite v5 - only change needed is to set .mts for vite config files.
2024-01-01 08:13:23 -05:00
psychedelicious
6f354f16ba feat(ui): canvas perf improvements 2024-01-01 08:13:23 -05:00
psychedelicious
e108a2302e fix(ui): fix uninteractable canvas bbox 2024-01-01 08:13:23 -05:00
psychedelicious
2ffecef792 feat(ui): bump react-resizable-panels, improve panel resize logic 2024-01-01 08:13:23 -05:00
psychedelicious
2663a07e94 feat(ui): misc canvas perf improvements
- disable listening when not needed
- use useMemo for gridlines
2024-01-01 08:13:23 -05:00
psychedelicious
8d2ef5afc3 feat(ui): disable onlyRenderVisibleElements on Flow
This can cause stuttering when nodes are being moved in and out of the viewport. I think it's better to improve rendering/perf in other ways.
2024-01-01 08:13:23 -05:00
psychedelicious
539887b215 feat(ui): misc perf/rerender improvements
More efficient selectors, memoized/stable references to objects, lazy popover/menu rendering.
2024-01-01 08:13:23 -05:00
psychedelicious
2ba505cce9 feat(ui): use pubsub to for globalcontextmenuclose
Far more efficient than the crude redux incrementor thing.
2024-01-01 08:13:23 -05:00
psychedelicious
bd92a31d15 feat(ui): add createLruSelector
This uses the previous implementation of the memoization function in reselect. It's possible for the new weakmap-based memoization to cause memory leaks in certain scenarios, so we will avoid it for now.
2024-01-01 08:13:23 -05:00
psychedelicious
ee2529f3fd lru 2024-01-01 08:13:23 -05:00
psychedelicious
89b7082bc0 fix(ui): remove debug stmts 2024-01-01 08:13:23 -05:00
psychedelicious
55dfabb892 feat(ui): use make label widths grow
Fixes issue where translations overflowed due to hardcoded widths.
2024-01-01 08:13:23 -05:00
psychedelicious
2a41fd0b29 fix(ui): fix field title styling 2024-01-01 08:13:23 -05:00
Васянатор
966919ea4a translationBot(ui): update translation (Russian)
Currently translated at 98.1% (1335 of 1360 strings)

Co-authored-by: Васянатор <ilabulanov339@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/ru/
Translation: InvokeAI/Web UI
2024-01-01 11:38:27 +11:00
Hosted Weblate
d3acdcf12f translationBot(ui): update translation files
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI
2024-01-01 11:38:27 +11:00
psychedelicious
52f9749bf5 feat(ui): partial rebuild of model manager internal logic 2023-12-29 08:26:14 -05:00
psychedelicious
2a661450c3 feat(ui): increase size of clear icon on selects 2023-12-29 08:26:14 -05:00
psychedelicious
2d96c62fdb feat(ui): more memoization 2023-12-29 08:26:14 -05:00
psychedelicious
3e6173ee8c feat(ui): only show refiner models on refiner model select 2023-12-29 08:26:14 -05:00
psychedelicious
4e9841c924 feat(ui): add refiner cfg scale & steps defaults & marks 2023-12-29 08:26:14 -05:00
psychedelicious
f4ea495d23 feat(ui): InvSwitch and InvSliderThumb are round 2023-12-29 08:26:14 -05:00
psychedelicious
43a4b815e8 fix(ui): fix InvSlider vertical thumb styling 2023-12-29 08:26:14 -05:00
psychedelicious
4134f18319 fix(ui): InvEditable, linear field view styling 2023-12-29 08:26:14 -05:00
psychedelicious
cd292f6c1c fix(ui): remove errant console.log 2023-12-29 08:26:14 -05:00
psychedelicious
3ce8f3d6fe feat(ui): more memoization 2023-12-29 08:26:14 -05:00
psychedelicious
10fd4f6a61 feat(ui): update panel lib, move gallery to percentages 2023-12-29 08:26:14 -05:00
psychedelicious
47b1fd4bce chore(ui): bump deps 2023-12-29 08:26:14 -05:00
psychedelicious
300805a25a fix(ui): fix typing issues 2023-12-29 08:26:14 -05:00
psychedelicious
56527da73e feat(ui): memoize all components 2023-12-29 08:26:14 -05:00
psychedelicious
ca4b8e65c1 feat(ui): use stable objects for animation/native element styles 2023-12-29 08:26:14 -05:00
psychedelicious
f5194f9e2d feat(ui): generation accordion badges 2023-12-29 08:26:14 -05:00
psychedelicious
ccbbb417f9 feat(ui): fix control adapters styling 2023-12-29 08:26:14 -05:00
psychedelicious
37786a26a5 feat(ui): move scaling up to image settings -> advanced 2023-12-29 08:26:14 -05:00
psychedelicious
4f2930412e feat(ui): use primitive style props or memoized sx objects 2023-12-29 08:26:14 -05:00
psychedelicious
83049a3a5b fix(ui): typo in canvas model handler 2023-12-29 08:26:14 -05:00
psychedelicious
38256f97b3 fix(ui): fix word break on LoRACard 2023-12-29 08:26:14 -05:00
psychedelicious
77f2aabda4 feat(ui): sort model select options with compatible base model first 2023-12-29 08:26:14 -05:00
psychedelicious
e32eb2a649 fix(ui): restore labels in model manager selects 2023-12-29 08:26:14 -05:00
psychedelicious
f4cdfa3b9c fix(ui): canvas layer select cut off 2023-12-29 08:26:14 -05:00
psychedelicious
e99b715e9e fix(ui): board collapse button styling 2023-12-29 08:26:14 -05:00
psychedelicious
ed96c40239 feat(ui): change queue icon 2023-12-29 08:26:14 -05:00
psychedelicious
1b3bb932b9 feat(ui): reduce button fontweight to semibold 2023-12-29 08:26:14 -05:00
psychedelicious
f0b102d830 feat(ui): ux improvements & redesign
This is a squash merge of a bajillion messy small commits created while iterating on the UI component library and redesign.
2023-12-29 08:26:14 -05:00
psychedelicious
a47d91f0e7 feat(api): add max_prompts constraints 2023-12-29 08:26:14 -05:00
Millun Atluri
358c1f5791 Release/v3.5.1 (#5363)
## What type of PR is this? (check all applicable)

Release v3.5.1

## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description
InvokeAI v3.5.1 release

## [optional] Are there any post deployment tasks we need to perform?
1. Release on PyPi
2. Create GH release
3. Annonce on Discord
2023-12-29 15:20:24 +11:00
Millun Atluri
faec320d48 {release} v3.5.1 2023-12-29 13:33:47 +11:00
Millun Atluri
fd074abdc4 Add frontend build 2023-12-29 13:16:23 +11:00
Millun Atluri
d8eb58cd58 Add frontend build 2023-12-29 13:15:37 +11:00
Millun Atluri
8937d66412 Add Tiled Upscaling to default workflows (#5362)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [X] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [X] No


## Description
Add Tiled Upscaling to default workflows

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Merge Plan

<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-12-29 12:43:47 +11:00
Millun Atluri
a6935ae7fb Add Tiled Upscaling to default workflows 2023-12-29 12:26:50 +11:00
Millun Atluri
69968eb67b add nightmare promptgen to communityNodes.md (#5360)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [x] Community Node Submission

## Description
Adds nightmare promptgen to the community nodes list.
2023-12-29 08:06:44 +11:00
gogurtenjoyer
e57f5f129c add nightmare promptgen to communityNodes.md 2023-12-28 13:15:52 -05:00
psychedelicious
1b8651fa26 fix(ui): do no create extraneous pos var 2023-12-28 20:44:02 +11:00
Jonathan
f6664960ca Update useBuildNode.ts
Added addition of the rect's top left coordinates to get equivalent behavior.
2023-12-28 20:44:02 +11:00
Jonathan
84a001720c Added back bounds check 2023-12-28 20:44:02 +11:00
Jonathan
c9951cd86b Eliminate constant console deprecation warnings
React Flow 11.10 eliminates the need to use project() and issues a deprecation warning to the console every time that onMouseMove is called (see https://reactflow.dev/whats-new/2023-11-10#rename-usereactflowproject-to-usereactflowscreentoflowposition). This code change eliminates that warning,
2023-12-28 20:44:02 +11:00
1047 changed files with 28664 additions and 23769 deletions

View File

@@ -1,10 +1,10 @@
<div align="center">
![project hero](https://github.com/invoke-ai/InvokeAI/assets/31807370/1a917d94-e099-4fa1-a70f-7dd8d0691018)
![project hero](https://github.com/invoke-ai/InvokeAI/assets/31807370/6e3728c7-e90e-4711-905c-3b55844ff5be)
# Invoke AI - Generative AI for Professional Creatives
## Professional Creative Tools for Stable Diffusion, Custom-Trained Models, and more.
To learn more about Invoke AI, get started instantly, or implement our Business solutions, visit [invoke.ai](https://invoke.ai)
# Invoke - Professional Creative AI Tools for Visual Media
## To learn more about Invoke, or implement our Business solutions, visit [invoke.com](https://www.invoke.com/about)
[![discord badge]][discord link]
@@ -56,7 +56,9 @@ the foundation for multiple commercial products.
<div align="center">
![canvas preview](https://github.com/invoke-ai/InvokeAI/raw/main/docs/assets/canvas_preview.png)
![Highlighted Features - Canvas and Workflows](https://github.com/invoke-ai/InvokeAI/assets/31807370/708f7a82-084f-4860-bfbe-e2588c53548d)
</div>

View File

@@ -2,10 +2,13 @@
## Any environment variables supported by InvokeAI can be specified here,
## in addition to the examples below.
# INVOKEAI_ROOT is the path to a path on the local filesystem where InvokeAI will store data.
# HOST_INVOKEAI_ROOT is the path on the docker host's filesystem where InvokeAI will store data.
# Outputs will also be stored here by default.
# This **must** be an absolute path.
INVOKEAI_ROOT=
# If relative, it will be relative to the docker directory in which the docker-compose.yml file is located
#HOST_INVOKEAI_ROOT=../../invokeai-data
# INVOKEAI_ROOT is the path to the root of the InvokeAI repository within the container.
# INVOKEAI_ROOT=~/invokeai
# Get this value from your HuggingFace account settings page.
# HUGGING_FACE_HUB_TOKEN=

View File

@@ -21,7 +21,9 @@ x-invokeai: &invokeai
ports:
- "${INVOKEAI_PORT:-9090}:9090"
volumes:
- ${INVOKEAI_ROOT:-~/invokeai}:${INVOKEAI_ROOT:-/invokeai}
- type: bind
source: ${HOST_INVOKEAI_ROOT:-${INVOKEAI_ROOT:-~/invokeai}}
target: ${INVOKEAI_ROOT:-/invokeai}
- ${HF_HOME:-~/.cache/huggingface}:${HF_HOME:-/invokeai/.cache/huggingface}
# - ${INVOKEAI_MODELS_DIR:-${INVOKEAI_ROOT:-/invokeai/models}}
# - ${INVOKEAI_MODELS_CONFIG_PATH:-${INVOKEAI_ROOT:-/invokeai/configs/models.yaml}}

View File

@@ -36,7 +36,8 @@ To use a community workflow, download the the `.json` node graph file and load i
+ [Mask Operations](#mask-operations)
+ [Match Histogram](#match-histogram)
+ [Metadata-Linked](#metadata-linked-nodes)
+ [Negative Image](#negative-image)
+ [Negative Image](#negative-image)
+ [Nightmare Promptgen](#nightmare-promptgen)
+ [Oobabooga](#oobabooga)
+ [Prompt Tools](#prompt-tools)
+ [Remote Image](#remote-image)
@@ -346,6 +347,13 @@ Node Link: https://github.com/VeyDlin/negative-image-node
View:
</br><img src="https://raw.githubusercontent.com/VeyDlin/negative-image-node/master/.readme/node.png" width="500" />
--------------------------------
### Nightmare Promptgen
**Description:** Nightmare Prompt Generator - Uses a local text generation model to create unique imaginative (but usually nightmarish) prompts for InvokeAI. By default, it allows you to choose from some gpt-neo models I finetuned on over 2500 of my own InvokeAI prompts in Compel format, but you're able to add your own, as well. Offers support for replacing any troublesome words with a random choice from list you can also define.
**Node Link:** [https://github.com/gogurtenjoyer/nightmare-promptgen](https://github.com/gogurtenjoyer/nightmare-promptgen)
--------------------------------
### Oobabooga

View File

@@ -23,10 +23,11 @@ class DynamicPromptsResponse(BaseModel):
)
async def parse_dynamicprompts(
prompt: str = Body(description="The prompt to parse with dynamicprompts"),
max_prompts: int = Body(default=1000, description="The max number of prompts to generate"),
max_prompts: int = Body(ge=1, le=10000, default=1000, description="The max number of prompts to generate"),
combinatorial: bool = Body(default=True, description="Whether to use the combinatorial generator"),
) -> DynamicPromptsResponse:
"""Creates a batch process"""
max_prompts = min(max_prompts, 10000)
generator: Union[RandomPromptGenerator, CombinatorialPromptGenerator]
try:
error: Optional[str] = None

View File

@@ -24,9 +24,10 @@ from controlnet_aux import (
)
from controlnet_aux.util import HWC3, ade_palette
from PIL import Image
from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from invokeai.app.invocations.primitives import ImageField, ImageOutput
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.app.shared.fields import FieldDescriptions
@@ -75,17 +76,16 @@ class ControlField(BaseModel):
resize_mode: CONTROLNET_RESIZE_VALUES = Field(default="just_resize", description="The resize mode to use")
@field_validator("control_weight")
@classmethod
def validate_control_weight(cls, v):
"""Validate that all control weights in the valid range"""
if isinstance(v, list):
for i in v:
if i < -1 or i > 2:
raise ValueError("Control weights must be within -1 to 2 range")
else:
if v < -1 or v > 2:
raise ValueError("Control weights must be within -1 to 2 range")
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
@invocation_output("control_output")
class ControlOutput(BaseInvocationOutput):
@@ -95,17 +95,17 @@ class ControlOutput(BaseInvocationOutput):
control: ControlField = OutputField(description=FieldDescriptions.control)
@invocation("controlnet", title="ControlNet", tags=["controlnet"], category="controlnet", version="1.1.0")
@invocation("controlnet", title="ControlNet", tags=["controlnet"], category="controlnet", version="1.1.1")
class ControlNetInvocation(BaseInvocation):
"""Collects ControlNet info to pass to other nodes"""
image: ImageField = InputField(description="The control image")
control_model: ControlNetModelField = InputField(description=FieldDescriptions.controlnet_model, input=Input.Direct)
control_weight: Union[float, List[float]] = InputField(
default=1.0, description="The weight given to the ControlNet"
default=1.0, ge=-1, le=2, description="The weight given to the ControlNet"
)
begin_step_percent: float = InputField(
default=0, ge=-1, le=2, description="When the ControlNet is first applied (% of total steps)"
default=0, ge=0, le=1, description="When the ControlNet is first applied (% of total steps)"
)
end_step_percent: float = InputField(
default=1, ge=0, le=1, description="When the ControlNet is last applied (% of total steps)"
@@ -113,6 +113,17 @@ class ControlNetInvocation(BaseInvocation):
control_mode: CONTROLNET_MODE_VALUES = InputField(default="balanced", description="The control mode used")
resize_mode: CONTROLNET_RESIZE_VALUES = InputField(default="just_resize", description="The resize mode used")
@field_validator("control_weight")
@classmethod
def validate_control_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self) -> "ControlNetInvocation":
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
def invoke(self, context: InvocationContext) -> ControlOutput:
return ControlOutput(
control=ControlField(

View File

@@ -2,7 +2,7 @@ import os
from builtins import float
from typing import List, Union
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from invokeai.app.invocations.baseinvocation import (
BaseInvocation,
@@ -15,6 +15,7 @@ from invokeai.app.invocations.baseinvocation import (
invocation_output,
)
from invokeai.app.invocations.primitives import ImageField
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.model_management.models.base import BaseModelType, ModelType
from invokeai.backend.model_management.models.ip_adapter import get_ip_adapter_image_encoder_model_id
@@ -39,7 +40,6 @@ class IPAdapterField(BaseModel):
ip_adapter_model: IPAdapterModelField = Field(description="The IP-Adapter model to use.")
image_encoder_model: CLIPVisionModelField = Field(description="The name of the CLIP image encoder model.")
weight: Union[float, List[float]] = Field(default=1, description="The weight given to the ControlNet")
# weight: float = Field(default=1.0, ge=0, description="The weight of the IP-Adapter.")
begin_step_percent: float = Field(
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
)
@@ -47,6 +47,17 @@ class IPAdapterField(BaseModel):
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
)
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
@invocation_output("ip_adapter_output")
class IPAdapterOutput(BaseInvocationOutput):
@@ -54,7 +65,7 @@ class IPAdapterOutput(BaseInvocationOutput):
ip_adapter: IPAdapterField = OutputField(description=FieldDescriptions.ip_adapter, title="IP-Adapter")
@invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.1.0")
@invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.1.1")
class IPAdapterInvocation(BaseInvocation):
"""Collects IP-Adapter info to pass to other nodes."""
@@ -64,18 +75,27 @@ class IPAdapterInvocation(BaseInvocation):
description="The IP-Adapter model.", title="IP-Adapter Model", input=Input.Direct, ui_order=-1
)
# weight: float = InputField(default=1.0, description="The weight of the IP-Adapter.", ui_type=UIType.Float)
weight: Union[float, List[float]] = InputField(
default=1, ge=-1, description="The weight given to the IP-Adapter", title="Weight"
default=1, description="The weight given to the IP-Adapter", title="Weight"
)
begin_step_percent: float = InputField(
default=0, ge=-1, le=2, description="When the IP-Adapter is first applied (% of total steps)"
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
)
end_step_percent: float = InputField(
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
)
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
def invoke(self, context: InvocationContext) -> IPAdapterOutput:
# Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model.
ip_adapter_info = context.services.model_manager.model_info(

View File

@@ -1,5 +1,6 @@
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
import contextlib
from contextlib import ExitStack
from functools import singledispatchmethod
from typing import List, Literal, Optional, Union
@@ -220,7 +221,7 @@ def get_scheduler(
title="Denoise Latents",
tags=["latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"],
category="latents",
version="1.5.0",
version="1.5.1",
)
class DenoiseLatentsInvocation(BaseInvocation):
"""Denoises noisy latents to decodable images"""
@@ -279,7 +280,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
ui_order=7,
)
cfg_rescale_multiplier: float = InputField(
default=0, ge=0, lt=1, description=FieldDescriptions.cfg_rescale_multiplier
title="CFG Rescale Multiplier", default=0, ge=0, lt=1, description=FieldDescriptions.cfg_rescale_multiplier
)
latents: Optional[LatentsField] = InputField(
default=None,
@@ -716,10 +717,23 @@ class DenoiseLatentsInvocation(BaseInvocation):
**self.unet.unet.model_dump(),
context=context,
)
# Prepare seamless context, if configured.
seamless_context = contextlib.nullcontext()
seamless_config = self.unet.seamless
if seamless_config is not None:
seamless_context = set_seamless(
model=unet_info.context.model,
axes=seamless_config.axes,
skipped_layers=seamless_config.skipped_layers,
skip_second_resnet=seamless_config.skip_second_resnet,
skip_conv2=seamless_config.skip_conv2,
)
with (
ExitStack() as exit_stack,
ModelPatcher.apply_freeu(unet_info.context.model, self.unet.freeu_config),
set_seamless(unet_info.context.model, self.unet.seamless_axes),
seamless_context,
unet_info as unet,
# Apply the LoRA after unet has been moved to its target device for faster patching.
ModelPatcher.apply_lora_unet(unet, _lora_loader()),
@@ -826,7 +840,19 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata):
context=context,
)
with set_seamless(vae_info.context.model, self.vae.seamless_axes), vae_info as vae:
# Prepare seamless context, if configured.
seamless_context = contextlib.nullcontext()
seamless_config = self.vae.seamless
if seamless_config is not None:
seamless_context = set_seamless(
model=vae_info.context.model,
axes=seamless_config.axes,
skipped_layers=seamless_config.skipped_layers,
skip_second_resnet=seamless_config.skip_second_resnet,
skip_conv2=seamless_config.skip_conv2,
)
with seamless_context, vae_info as vae:
latents = latents.to(vae.device)
if self.fp32:
vae.to(dtype=torch.float32)

View File

@@ -19,6 +19,13 @@ from .baseinvocation import (
)
class SeamlessSettings(BaseModel):
axes: List[str] = Field(description="Axes('x' and 'y') to which apply seamless")
skipped_layers: int = Field(description="How much down layers skip when applying seamless")
skip_second_resnet: bool = Field(description="Skip or not second resnet in down blocks when applying seamless")
skip_conv2: bool = Field(description="Skip or not conv2 in down blocks when applying seamless")
class ModelInfo(BaseModel):
model_name: str = Field(description="Info to load submodel")
base_model: BaseModelType = Field(description="Base model")
@@ -36,8 +43,8 @@ class UNetField(BaseModel):
unet: ModelInfo = Field(description="Info to load unet submodel")
scheduler: ModelInfo = Field(description="Info to load scheduler submodel")
loras: List[LoraInfo] = Field(description="Loras to apply on model loading")
seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
freeu_config: Optional[FreeUConfig] = Field(default=None, description="FreeU configuration")
seamless: Optional[SeamlessSettings] = Field(default=None, description="Seamless settings applied to model")
class ClipField(BaseModel):
@@ -50,7 +57,7 @@ class ClipField(BaseModel):
class VaeField(BaseModel):
# TODO: better naming?
vae: ModelInfo = Field(description="Info to load vae submodel")
seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
seamless: Optional[SeamlessSettings] = Field(default=None, description="Seamless settings applied to model")
@invocation_output("unet_output")
@@ -451,6 +458,11 @@ class SeamlessModeInvocation(BaseInvocation):
)
seamless_y: bool = InputField(default=True, input=Input.Any, description="Specify whether Y axis is seamless")
seamless_x: bool = InputField(default=True, input=Input.Any, description="Specify whether X axis is seamless")
skipped_layers: int = InputField(default=0, input=Input.Any, description="How much model's down layers to skip")
skip_second_resnet: bool = InputField(
default=True, input=Input.Any, description="Skip or not second resnet in down layers"
)
skip_conv2: bool = InputField(default=True, input=Input.Any, description="Skip or not conv2 in down layers")
def invoke(self, context: InvocationContext) -> SeamlessModeOutput:
# Conditionally append 'x' and 'y' based on seamless_x and seamless_y
@@ -465,9 +477,19 @@ class SeamlessModeInvocation(BaseInvocation):
seamless_axes_list.append("y")
if unet is not None:
unet.seamless_axes = seamless_axes_list
unet.seamless = SeamlessSettings(
axes=seamless_axes_list,
skipped_layers=self.skipped_layers,
skip_second_resnet=self.skip_second_resnet,
skip_conv2=self.skip_conv2,
)
if vae is not None:
vae.seamless_axes = seamless_axes_list
vae.seamless = SeamlessSettings(
axes=seamless_axes_list,
skipped_layers=self.skipped_layers,
skip_second_resnet=self.skip_second_resnet,
skip_conv2=self.skip_conv2,
)
return SeamlessModeOutput(unet=unet, vae=vae)

View File

@@ -1,6 +1,6 @@
from typing import Union
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from invokeai.app.invocations.baseinvocation import (
BaseInvocation,
@@ -14,6 +14,7 @@ from invokeai.app.invocations.baseinvocation import (
)
from invokeai.app.invocations.controlnet_image_processors import CONTROLNET_RESIZE_VALUES
from invokeai.app.invocations.primitives import ImageField
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.model_management.models.base import BaseModelType
@@ -37,6 +38,17 @@ class T2IAdapterField(BaseModel):
)
resize_mode: CONTROLNET_RESIZE_VALUES = Field(default="just_resize", description="The resize mode to use")
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
@invocation_output("t2i_adapter_output")
class T2IAdapterOutput(BaseInvocationOutput):
@@ -44,7 +56,7 @@ class T2IAdapterOutput(BaseInvocationOutput):
@invocation(
"t2i_adapter", title="T2I-Adapter", tags=["t2i_adapter", "control"], category="t2i_adapter", version="1.0.0"
"t2i_adapter", title="T2I-Adapter", tags=["t2i_adapter", "control"], category="t2i_adapter", version="1.0.1"
)
class T2IAdapterInvocation(BaseInvocation):
"""Collects T2I-Adapter info to pass to other nodes."""
@@ -61,7 +73,7 @@ class T2IAdapterInvocation(BaseInvocation):
default=1, ge=0, description="The weight given to the T2I-Adapter", title="Weight"
)
begin_step_percent: float = InputField(
default=0, ge=-1, le=2, description="When the T2I-Adapter is first applied (% of total steps)"
default=0, ge=0, le=1, description="When the T2I-Adapter is first applied (% of total steps)"
)
end_step_percent: float = InputField(
default=1, ge=0, le=1, description="When the T2I-Adapter is last applied (% of total steps)"
@@ -71,6 +83,17 @@ class T2IAdapterInvocation(BaseInvocation):
description="The resize mode applied to the T2I-Adapter input image so that it matches the target output size.",
)
@field_validator("weight")
@classmethod
def validate_ip_adapter_weight(cls, v):
validate_weights(v)
return v
@model_validator(mode="after")
def validate_begin_end_step_percent(self):
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
return self
def invoke(self, context: InvocationContext) -> T2IAdapterOutput:
return T2IAdapterOutput(
t2i_adapter=T2IAdapterField(

View File

@@ -0,0 +1,14 @@
from typing import Union
def validate_weights(weights: Union[float, list[float]]) -> None:
"""Validate that all control weights in the valid range"""
to_validate = weights if isinstance(weights, list) else [weights]
if any(i < -1 or i > 2 for i in to_validate):
raise ValueError("Control weights must be within -1 to 2 range")
def validate_begin_end_step(begin_step_percent: float, end_step_percent: float) -> None:
"""Validate that begin_step_percent is less than end_step_percent"""
if begin_step_percent >= end_step_percent:
raise ValueError("Begin step percent must be less than or equal to end step percent")

File diff suppressed because it is too large Load Diff

View File

@@ -25,71 +25,55 @@ def _conv_forward_asymmetric(self, input, weight, bias):
@contextmanager
def set_seamless(model: Union[UNet2DConditionModel, AutoencoderKL], seamless_axes: List[str]):
def set_seamless(
model: Union[UNet2DConditionModel, AutoencoderKL],
axes: List[str],
skipped_layers: int,
skip_second_resnet: bool,
skip_conv2: bool,
):
try:
to_restore = []
for m_name, m in model.named_modules():
if isinstance(model, UNet2DConditionModel):
if ".attentions." in m_name:
if not isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
continue
if isinstance(model, UNet2DConditionModel) and m_name.startswith("down_blocks.") and ".resnets." in m_name:
# down_blocks.1.resnets.1.conv1
_, block_num, _, resnet_num, submodule_name = m_name.split(".")
block_num = int(block_num)
resnet_num = int(resnet_num)
# if block_num >= seamless_down_blocks:
if block_num >= len(model.down_blocks) - skipped_layers:
continue
if ".resnets." in m_name:
if ".conv2" in m_name:
continue
if ".conv_shortcut" in m_name:
continue
"""
if isinstance(model, UNet2DConditionModel):
if False and ".upsamplers." in m_name:
if resnet_num > 0 and skip_second_resnet:
continue
if False and ".downsamplers." in m_name:
if submodule_name == "conv2" and skip_conv2:
continue
if True and ".resnets." in m_name:
if True and ".conv1" in m_name:
if False and "down_blocks" in m_name:
continue
if False and "mid_block" in m_name:
continue
if False and "up_blocks" in m_name:
continue
m.asymmetric_padding_mode = {}
m.asymmetric_padding = {}
m.asymmetric_padding_mode["x"] = "circular" if ("x" in axes) else "constant"
m.asymmetric_padding["x"] = (
m._reversed_padding_repeated_twice[0],
m._reversed_padding_repeated_twice[1],
0,
0,
)
m.asymmetric_padding_mode["y"] = "circular" if ("y" in axes) else "constant"
m.asymmetric_padding["y"] = (
0,
0,
m._reversed_padding_repeated_twice[2],
m._reversed_padding_repeated_twice[3],
)
if True and ".conv2" in m_name:
continue
if True and ".conv_shortcut" in m_name:
continue
if True and ".attentions." in m_name:
continue
if False and m_name in ["conv_in", "conv_out"]:
continue
"""
if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
m.asymmetric_padding_mode = {}
m.asymmetric_padding = {}
m.asymmetric_padding_mode["x"] = "circular" if ("x" in seamless_axes) else "constant"
m.asymmetric_padding["x"] = (
m._reversed_padding_repeated_twice[0],
m._reversed_padding_repeated_twice[1],
0,
0,
)
m.asymmetric_padding_mode["y"] = "circular" if ("y" in seamless_axes) else "constant"
m.asymmetric_padding["y"] = (
0,
0,
m._reversed_padding_repeated_twice[2],
m._reversed_padding_repeated_twice[3],
)
to_restore.append((m, m._conv_forward))
m._conv_forward = _conv_forward_asymmetric.__get__(m, nn.Conv2d)
to_restore.append((m, m._conv_forward))
m._conv_forward = _conv_forward_asymmetric.__get__(m, nn.Conv2d)
yield

View File

@@ -44,7 +44,7 @@ def torch_dtype(device: torch.device) -> torch.dtype:
if config.full_precision:
return torch.float32
if choose_precision(device) == "float16":
return torch.float16
return torch.bfloat16 if device.type == "cuda" else torch.float16
else:
return torch.float32

View File

@@ -68,12 +68,9 @@ def welcome(latest_release: str, latest_prerelease: str):
yield ""
yield "This script will update InvokeAI to the latest release, or to the development version of your choice."
yield ""
yield "When updating to an arbitrary tag or branch, be aware that the front end may be mismatched to the backend,"
yield "making the web frontend unusable. Please downgrade to the latest release if this happens."
yield ""
yield "[bold yellow]Options:"
yield f"""[1] Update to the latest [bold]official release[/bold] ([italic]{latest_release}[/italic])
[2] Update to the latest [bold]pre-release[/bold] (may be buggy; caveat emptor!) ([italic]{latest_prerelease}[/italic])
[2] Update to the latest [bold]pre-release[/bold] (may be buggy, database backups are recommended before installation; caveat emptor!) ([italic]{latest_prerelease}[/italic])
[3] Manually enter the [bold]version[/bold] you wish to update to"""
console.rule()

View File

@@ -7,4 +7,4 @@ stats.html
index.html
.yarn/
*.scss
src/services/api/schema.d.ts
src/services/api/schema.ts

View File

@@ -28,12 +28,16 @@ module.exports = {
'i18next',
'path',
'unused-imports',
'simple-import-sort',
'eslint-plugin-import',
// These rules are too strict for normal usage, but are useful for optimizing rerenders
// '@arthurgeron/react-usememo',
],
root: true,
rules: {
'path/no-relative-imports': ['error', { maxDepth: 0 }],
curly: 'error',
'i18next/no-literal-string': 2,
'i18next/no-literal-string': 'warn',
'react/jsx-no-bind': ['error', { allowBind: true }],
'react/jsx-curly-brace-presence': [
'error',
@@ -43,6 +47,7 @@ module.exports = {
'no-var': 'error',
'brace-style': 'error',
'prefer-template': 'error',
'import/no-duplicates': 'error',
radix: 'error',
'space-before-blocks': 'error',
'import/prefer-default-export': 'off',
@@ -57,6 +62,18 @@ module.exports = {
argsIgnorePattern: '^_',
},
],
// These rules are too strict for normal usage, but are useful for optimizing rerenders
// '@arthurgeron/react-usememo/require-usememo': [
// 'warn',
// {
// strict: false,
// checkHookReturnObject: false,
// fix: { addImports: true },
// checkHookCalls: false,
// },
// ],
// '@arthurgeron/react-usememo/require-memo': 'warn',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-empty-interface': [
@@ -65,7 +82,26 @@ module.exports = {
allowSingleExtends: true,
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'separate-type-imports',
disallowTypeAnnotations: true,
},
],
'@typescript-eslint/no-import-type-side-effects': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
overrides: [
{
files: ['*.stories.tsx'],
rules: {
'i18next/no-literal-string': 'off',
},
},
],
settings: {
react: {
version: 'detect',

View File

@@ -9,7 +9,8 @@ index.html
.yarn/
.yalc/
*.scss
src/services/api/schema.d.ts
src/services/api/schema.ts
static/
src/theme/css/overlayscrollbars.css
src/theme_/css/overlayscrollbars.css
pnpm-lock.yaml

View File

@@ -0,0 +1,25 @@
import { PropsWithChildren, memo, useEffect } from 'react';
import { modelChanged } from '../src/features/parameters/store/generationSlice';
import { useAppDispatch } from '../src/app/store/storeHooks';
import { useGlobalModifiersInit } from '../src/common/hooks/useGlobalModifiers';
/**
* Initializes some state for storybook. Must be in a different component
* so that it is run inside the redux context.
*/
export const ReduxInit = memo((props: PropsWithChildren) => {
const dispatch = useAppDispatch();
useGlobalModifiersInit();
useEffect(() => {
dispatch(
modelChanged({
model_name: 'test_model',
base_model: 'sd-1',
model_type: 'main',
})
);
}, []);
return props.children;
});
ReduxInit.displayName = 'ReduxInit';

View File

@@ -6,6 +6,7 @@ const config: StorybookConfig = {
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-storysource',
],
framework: {
name: '@storybook/react-vite',

View File

@@ -1,16 +1,17 @@
import { Preview } from '@storybook/react';
import { themes } from '@storybook/theming';
import i18n from 'i18next';
import React from 'react';
import { initReactI18next } from 'react-i18next';
import { Provider } from 'react-redux';
import GlobalHotkeys from '../src/app/components/GlobalHotkeys';
import ThemeLocaleProvider from '../src/app/components/ThemeLocaleProvider';
import { $baseUrl } from '../src/app/store/nanostores/baseUrl';
import { createStore } from '../src/app/store/store';
import { Container } from '@chakra-ui/react';
// TODO: Disabled for IDE performance issues with our translation JSON
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import translationEN from '../public/locales/en.json';
import { ReduxInit } from './ReduxInit';
i18n.use(initReactI18next).init({
lng: 'en',
@@ -25,17 +26,21 @@ i18n.use(initReactI18next).init({
});
const store = createStore(undefined, false);
$baseUrl.set('http://localhost:9090');
const preview: Preview = {
decorators: [
(Story) => (
<Provider store={store}>
<ThemeLocaleProvider>
<GlobalHotkeys />
<Story />
</ThemeLocaleProvider>
</Provider>
),
(Story) => {
return (
<Provider store={store}>
<ThemeLocaleProvider>
<ReduxInit>
<Story />
</ReduxInit>
</ThemeLocaleProvider>
</Provider>
);
},
],
parameters: {
docs: {

View File

@@ -0,0 +1,15 @@
{
"entry": ["src/main.tsx"],
"extensions": [".ts", ".tsx"],
"ignorePatterns": [
"**/node_modules/**",
"dist/**",
"public/**",
"**/*.stories.tsx",
"config/**"
],
"ignoreUnresolved": [],
"ignoreUnimported": ["src/i18.d.ts", "vite.config.ts", "src/vite-env.d.ts"],
"respectGitignore": true,
"ignoreUnused": []
}

View File

@@ -1,6 +1,6 @@
import react from '@vitejs/plugin-react-swc';
import { visualizer } from 'rollup-plugin-visualizer';
import { PluginOption, UserConfig } from 'vite';
import type { PluginOption, UserConfig } from 'vite';
import eslint from 'vite-plugin-eslint';
import tsconfigPaths from 'vite-tsconfig-paths';

View File

@@ -1,5 +1,6 @@
import { UserConfig } from 'vite';
import { commonPlugins } from './common';
import type { UserConfig } from 'vite';
import { commonPlugins } from './common.mjs';
export const appConfig: UserConfig = {
base: './',

View File

@@ -1,8 +1,9 @@
import path from 'path';
import { UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import type { UserConfig } from 'vite';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import { commonPlugins } from './common';
import dts from 'vite-plugin-dts';
import { commonPlugins } from './common.mjs';
export const packageConfig: UserConfig = {
base: './',

View File

@@ -1,24 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="favicon.ico" />
<style>
html,
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body dir="ltr">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="mask-icon" href="/invoke-key-ylw-sm.svg" color="#E6FD13" sizes="any" />
<link rel="icon" href="/invoke-key-char-on-ylw.svg" />
<style>
html,
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body dir="ltr">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -31,13 +31,17 @@
"lint": "concurrently -g -n eslint,prettier,tsc,madge -c cyan,green,magenta,yellow \"pnpm run lint:eslint\" \"pnpm run lint:prettier\" \"pnpm run lint:tsc\" \"pnpm run lint:madge\"",
"fix": "eslint --fix . && prettier --log-level warn --write .",
"preinstall": "npx only-allow pnpm",
"postinstall": "patch-package && pnpm run theme",
"postinstall": "pnpm run theme",
"theme": "chakra-cli tokens src/theme/theme.ts",
"theme:watch": "chakra-cli tokens src/theme/theme.ts --watch",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"unimported": "npx unimported"
},
"madge": {
"excludeRegExp": [
"^index.ts$"
],
"detectiveOptions": {
"ts": {
"skipTypeImports": true
@@ -53,56 +57,56 @@
"@chakra-ui/layout": "^2.3.1",
"@chakra-ui/portal": "^2.1.0",
"@chakra-ui/react": "^2.8.2",
"@chakra-ui/react-use-size": "^2.1.0",
"@chakra-ui/styled-system": "^2.9.2",
"@chakra-ui/theme-tools": "^2.1.2",
"@dagrejs/graphlib": "^2.1.13",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.11.1",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@fontsource-variable/inter": "^5.0.16",
"@mantine/core": "^6.0.19",
"@mantine/form": "^6.0.19",
"@mantine/hooks": "^6.0.19",
"@mantine/form": "6.0.21",
"@nanostores/react": "^0.7.1",
"@reduxjs/toolkit": "^2.0.1",
"@reduxjs/toolkit": "2.0.1",
"@roarr/browser-log-writer": "^1.3.0",
"@storybook/manager-api": "^7.6.4",
"@storybook/theming": "^7.6.4",
"chakra-react-select": "^4.7.6",
"compare-versions": "^6.1.0",
"dateformat": "^5.0.3",
"framer-motion": "^10.16.15",
"i18next": "^23.7.8",
"framer-motion": "^10.16.16",
"i18next": "^23.7.13",
"i18next-http-backend": "^2.4.2",
"idb-keyval": "^6.2.1",
"konva": "^9.2.3",
"konva": "^9.3.0",
"lodash-es": "^4.17.21",
"nanostores": "^0.9.5",
"new-github-issue-url": "^1.0.0",
"overlayscrollbars": "^2.4.5",
"overlayscrollbars": "^2.4.6",
"overlayscrollbars-react": "^0.5.3",
"patch-package": "^8.0.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.11",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.49.2",
"react-hotkeys-hook": "4.4.1",
"react-i18next": "^13.5.0",
"react-i18next": "^14.0.0",
"react-icons": "^4.12.0",
"react-konva": "^18.2.10",
"react-redux": "^9.0.2",
"react-resizable-panels": "^0.0.55",
"react-redux": "9.0.4",
"react-resizable-panels": "^1.0.7",
"react-select": "5.8.0",
"react-textarea-autosize": "^8.5.3",
"react-use": "^17.4.2",
"react-virtuoso": "^4.6.2",
"reactflow": "^11.10.1",
"redux-dynamic-middlewares": "^2.2.0",
"redux-remember": "^5.0.0",
"redux-remember": "^5.0.1",
"roarr": "^7.21.0",
"serialize-error": "^11.0.3",
"socket.io-client": "^4.7.2",
"type-fest": "^4.8.3",
"type-fest": "^4.9.0",
"use-debounce": "^10.0.0",
"use-image": "^1.1.1",
"uuid": "^9.0.1",
@@ -117,44 +121,56 @@
"ts-toolbelt": "^9.6.0"
},
"devDependencies": {
"@arthurgeron/eslint-plugin-react-usememo": "^2.2.2",
"@chakra-ui/cli": "^2.4.1",
"@storybook/addon-essentials": "^7.6.4",
"@storybook/addon-interactions": "^7.6.4",
"@storybook/addon-links": "^7.6.4",
"@storybook/blocks": "^7.6.4",
"@storybook/react": "^7.6.4",
"@storybook/react-vite": "^7.6.4",
"@storybook/test": "^7.6.4",
"@storybook/addon-docs": "^7.6.6",
"@storybook/addon-essentials": "^7.6.6",
"@storybook/addon-interactions": "^7.6.6",
"@storybook/addon-links": "^7.6.6",
"@storybook/addon-storysource": "^7.6.6",
"@storybook/blocks": "^7.6.6",
"@storybook/manager-api": "^7.6.6",
"@storybook/react": "^7.6.6",
"@storybook/react-vite": "^7.6.6",
"@storybook/test": "^7.6.6",
"@storybook/theming": "^7.6.6",
"@types/dateformat": "^5.0.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.9.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.17",
"@types/node": "^20.10.6",
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.18",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"concurrently": "^8.2.2",
"eslint": "^8.55.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-i18next": "^6.0.3",
"eslint-plugin-path": "^1.2.2",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-path": "^1.2.3",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-unused-imports": "^3.0.0",
"madge": "^6.1.0",
"openapi-types": "^12.1.3",
"openapi-typescript": "^6.7.2",
"prettier": "^3.1.0",
"rollup-plugin-visualizer": "^5.10.0",
"storybook": "^7.6.4",
"openapi-typescript": "^6.7.3",
"prettier": "^3.1.1",
"rollup-plugin-visualizer": "^5.12.0",
"storybook": "^7.6.6",
"ts-toolbelt": "^9.6.0",
"typescript": "^5.3.3",
"vite": "^4.5.1",
"vite-plugin-css-injected-by-js": "^3.3.0",
"vite-plugin-dts": "^3.6.4",
"vite": "^5.0.10",
"vite-plugin-css-injected-by-js": "^3.3.1",
"vite-plugin-dts": "^3.7.0",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.2"
"vite-tsconfig-paths": "^4.2.3"
},
"pnpm": {
"patchedDependencies": {
"reselect@5.0.1": "patches/reselect@5.0.1.patch"
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="6" fill="#E6FD13"/>
<path d="M19.2378 10.9H25V7H7V10.9H12.7622L19.2378 21.1H25V25H7V21.1H12.7622" stroke="#181818" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -83,10 +83,6 @@
"title": "خيارات التثبيت",
"desc": "ثبت لوحة الخيارات"
},
"toggleViewer": {
"title": "تبديل العارض",
"desc": "فتح وإغلاق مشاهد الصور"
},
"toggleGallery": {
"title": "تبديل المعرض",
"desc": "فتح وإغلاق درابزين المعرض"
@@ -147,10 +143,6 @@
"title": "الصورة التالية",
"desc": "عرض الصورة التالية في الصالة"
},
"toggleGalleryPin": {
"title": "تبديل تثبيت الصالة",
"desc": "يثبت ويفتح تثبيت الصالة على الواجهة الرسومية"
},
"increaseGalleryThumbSize": {
"title": "زيادة حجم صورة الصالة",
"desc": "يزيد حجم الصور المصغرة في الصالة"

View File

@@ -168,10 +168,6 @@
"title": "Optionen anheften",
"desc": "Anheften des Optionsfeldes"
},
"toggleViewer": {
"title": "Bildbetrachter umschalten",
"desc": "Bildbetrachter öffnen und schließen"
},
"toggleGallery": {
"title": "Galerie umschalten",
"desc": "Öffnen und Schließen des Galerie-Schubfachs"
@@ -232,10 +228,6 @@
"title": "Nächstes Bild",
"desc": "Nächstes Bild in Galerie anzeigen"
},
"toggleGalleryPin": {
"title": "Galerie anheften umschalten",
"desc": "Heftet die Galerie an die Benutzeroberfläche bzw. löst die sie"
},
"increaseGalleryThumbSize": {
"title": "Größe der Galeriebilder erhöhen",
"desc": "Vergrößert die Galerie-Miniaturansichten"
@@ -760,7 +752,6 @@
"w": "W",
"addControlNet": "$t(common.controlNet) hinzufügen",
"none": "Kein",
"incompatibleBaseModel": "Inkompatibles Basismodell:",
"enableControlnet": "Aktiviere ControlNet",
"detectResolution": "Auflösung erkennen",
"controlNetT2IMutexDesc": "$t(common.controlNet) und $t(common.t2iAdapter) zur gleichen Zeit wird nicht unterstützt.",

View File

@@ -50,9 +50,33 @@
"uncategorized": "Uncategorized",
"downloadBoard": "Download Board"
},
"accordions": {
"generation": {
"title": "Generation",
"modelTab": "Model",
"conceptsTab": "Concepts"
},
"image": {
"title": "Image"
},
"advanced": {
"title": "Advanced"
},
"control": {
"title": "Control",
"controlAdaptersTab": "Control Adapters",
"ipTab": "Image Prompts"
},
"compositing": {
"title": "Compositing",
"coherenceTab": "Coherence Pass",
"infillTab": "Infill"
}
},
"common": {
"accept": "Accept",
"advanced": "Advanced",
"advancedOptions": "Advanced Options",
"ai": "ai",
"areYouSure": "Are you sure?",
"auto": "Auto",
@@ -62,6 +86,7 @@
"copyError": "$t(gallery.copy) Error",
"close": "Close",
"on": "On",
"or": "or",
"checkpoint": "Checkpoint",
"communityLabel": "Community",
"controlNet": "ControlNet",
@@ -79,6 +104,7 @@
"file": "File",
"folder": "Folder",
"format": "format",
"free": "Free",
"generate": "Generate",
"githubLabel": "Github",
"hotkeysLabel": "Hotkeys",
@@ -131,6 +157,7 @@
"save": "Save",
"saveAs": "Save As",
"settingsLabel": "Settings",
"preferencesLabel": "Preferences",
"simple": "Simple",
"somethingWentWrong": "Something went wrong",
"statusConnected": "Connected",
@@ -221,7 +248,6 @@
"colorMapTileSize": "Tile Size",
"importImageFromCanvas": "Import Image From Canvas",
"importMaskFromCanvas": "Import Mask From Canvas",
"incompatibleBaseModel": "Incompatible base model:",
"lineart": "Lineart",
"lineartAnime": "Lineart Anime",
"lineartAnimeDescription": "Anime-style lineart processing",
@@ -246,6 +272,7 @@
"prompt": "Prompt",
"resetControlImage": "Reset Control Image",
"resize": "Resize",
"resizeSimple": "Resize (Simple)",
"resizeMode": "Resize Mode",
"safe": "Safe",
"saveControlImage": "Save Control Image",
@@ -284,7 +311,7 @@
"queue": "Queue",
"queueFront": "Add to Front of Queue",
"queueBack": "Add to Queue",
"queueCountPrediction": "Add {{predicted}} to Queue",
"queueCountPrediction": "{{promptsCount}} prompts × {{iterations}} iterations -> {{count}} generations",
"queueMaxExceeded": "Max of {{max_queue_size}} exceeded, would skip {{skip}}",
"queuedCount": "{{pending}} Pending",
"queueTotal": "{{total}} Total",
@@ -340,7 +367,8 @@
"back": "back",
"batchFailedToQueue": "Failed to Queue Batch",
"graphQueued": "Graph queued",
"graphFailedToQueue": "Failed to queue graph"
"graphFailedToQueue": "Failed to queue graph",
"openQueue": "Open Queue"
},
"invocationCache": {
"invocationCache": "Invocation Cache",
@@ -400,6 +428,9 @@
"problemDeletingImagesDesc": "One or more images could not be deleted"
},
"hotkeys": {
"searchHotkeys": "Search Hotkeys",
"clearSearch": "Clear Search",
"noHotkeysFound": "No Hotkeys Found",
"acceptStagingImage": {
"desc": "Accept Current Staging Area Image",
"title": "Accept Staging Image"
@@ -408,11 +439,15 @@
"desc": "Opens the add node menu",
"title": "Add Nodes"
},
"appHotkeys": "App Hotkeys",
"appHotkeys": "App",
"cancel": {
"desc": "Cancel image generation",
"desc": "Cancel current queue item",
"title": "Cancel"
},
"cancelAndClear": {
"desc": "Cancel current queue item and clear all pending items",
"title": "Cancel and Clear"
},
"changeTabs": {
"desc": "Switch to another workspace",
"title": "Change Tabs"
@@ -469,8 +504,8 @@
"desc": "Focus the prompt input area",
"title": "Focus Prompt"
},
"galleryHotkeys": "Gallery Hotkeys",
"generalHotkeys": "General Hotkeys",
"galleryHotkeys": "Gallery",
"generalHotkeys": "General",
"hideMask": {
"desc": "Hide and unhide mask",
"title": "Hide Mask"
@@ -491,7 +526,7 @@
"desc": "Generate an image",
"title": "Invoke"
},
"keyboardShortcuts": "Keyboard Shortcuts",
"keyboardShortcuts": "Hotkeys",
"maximizeWorkSpace": {
"desc": "Close panels and maximize work area",
"title": "Maximize Workspace"
@@ -512,7 +547,7 @@
"desc": "Next Staging Area Image",
"title": "Next Staging Image"
},
"nodesHotkeys": "Nodes Hotkeys",
"nodesHotkeys": "Nodes",
"pinOptions": {
"desc": "Pin the options panel",
"title": "Pin Options"
@@ -581,31 +616,31 @@
"desc": "Open and close the gallery drawer",
"title": "Toggle Gallery"
},
"toggleGalleryPin": {
"desc": "Pins and unpins the gallery to the UI",
"title": "Toggle Gallery Pin"
"toggleOptions": {
"desc": "Open and close the options panel",
"title": "Toggle Options"
},
"toggleOptionsAndGallery": {
"desc": "Open and close the options and gallery panels",
"title": "Toggle Options and Gallery"
},
"resetOptionsAndGallery": {
"desc": "Resets the options and gallery panels",
"title": "Reset Options and Gallery"
},
"toggleLayer": {
"desc": "Toggles mask/base layer selection",
"title": "Toggle Layer"
},
"toggleOptions": {
"desc": "Open and close the options panel",
"title": "Toggle Options"
},
"toggleSnap": {
"desc": "Toggles Snap to Grid",
"title": "Toggle Snap"
},
"toggleViewer": {
"desc": "Open and close Image Viewer",
"title": "Toggle Viewer"
},
"undoStroke": {
"desc": "Undo a brush stroke",
"title": "Undo Stroke"
},
"unifiedCanvasHotkeys": "Unified Canvas Hotkeys",
"unifiedCanvasHotkeys": "Unified Canvas",
"upscale": {
"desc": "Upscale the current image",
"title": "Upscale"
@@ -788,17 +823,23 @@
},
"models": {
"addLora": "Add LoRA",
"allLoRAsAdded": "All LoRAs added",
"loraAlreadyAdded": "LoRA already added",
"esrganModel": "ESRGAN Model",
"loading": "loading",
"incompatibleBaseModel": "Incompatible base model",
"noMainModelSelected": "No main model selected",
"noLoRAsAvailable": "No LoRAs available",
"noLoRAsLoaded": "No LoRAs Loaded",
"noMatchingLoRAs": "No matching LoRAs",
"noMatchingModels": "No matching Models",
"noModelsAvailable": "No models available",
"lora": "LoRA",
"selectLoRA": "Select a LoRA",
"selectModel": "Select a Model",
"noLoRAsInstalled": "No LoRAs installed",
"noRefinerModelsInstalled": "No SDXL Refiner models installed"
"noRefinerModelsInstalled": "No SDXL Refiner models installed",
"defaultVAE": "Default VAE"
},
"nodes": {
"addNode": "Add Node",
@@ -1037,8 +1078,14 @@
"prototypeDesc": "This invocation is a prototype. It may have breaking changes during app updates and may be removed at any time."
},
"parameters": {
"aspect": "Aspect",
"aspectRatio": "Aspect Ratio",
"aspectRatioFree": "Free",
"lockAspectRatio": "Lock Aspect Ratio",
"swapDimensions": "Swap Dimensions",
"setToOptimalSize": "Optimize size for model",
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (may be too small)",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (may be too large)",
"boundingBoxHeader": "Bounding Box",
"boundingBoxHeight": "Bounding Box Height",
"boundingBoxWidth": "Bounding Box Width",
@@ -1077,6 +1124,7 @@
"imageFit": "Fit Initial Image To Output Size",
"images": "Images",
"imageToImage": "Image to Image",
"imageSize": "Image Size",
"img2imgStrength": "Image To Image Strength",
"infillMethod": "Infill Method",
"infillScalingHeader": "Infill and Scaling",
@@ -1091,7 +1139,7 @@
"noControlImageForControlAdapter": "Control Adapter #{{number}} has no control image",
"noInitialImageSelected": "No initial image selected",
"noModelForControlAdapter": "Control Adapter #{{number}} has no model selected.",
"incompatibleBaseModelForControlAdapter": "Control Adapter #{{number}} model is invalid with main model.",
"incompatibleBaseModelForControlAdapter": "Control Adapter #{{number}} model is incompatible with main model.",
"noModelSelected": "No model selected",
"noPrompts": "No prompts generated",
"noNodesInGraph": "No nodes in graph",
@@ -1127,8 +1175,8 @@
"seamCorrectionHeader": "Seam Correction",
"seamHighThreshold": "High",
"seamlessTiling": "Seamless Tiling",
"seamlessXAxis": "X Axis",
"seamlessYAxis": "Y Axis",
"seamlessXAxis": "Seamless Tiling X Axis",
"seamlessYAxis": "Seamless Tiling Y Axis",
"seamlessX": "Seamless X",
"seamlessY": "Seamless Y",
"seamlessX&Y": "Seamless X & Y",
@@ -1171,6 +1219,7 @@
},
"dynamicPrompts": {
"combinatorial": "Combinatorial Generation",
"showDynamicPrompts": "Show Dynamic Prompts",
"dynamicPrompts": "Dynamic Prompts",
"enableDynamicPrompts": "Enable Dynamic Prompts",
"maxPrompts": "Max Prompts",
@@ -1183,11 +1232,13 @@
"perIterationDesc": "Use a different seed for each iteration",
"perPromptLabel": "Seed per Image",
"perPromptDesc": "Use a different seed for each image"
}
},
"loading": "Generating Dynamic Prompts..."
},
"sdxl": {
"cfgScale": "CFG Scale",
"concatPromptStyle": "Concatenate Prompt & Style",
"concatPromptStyle": "Linking Prompt & Style",
"freePromptStyle": "Manual Style Prompting",
"denoisingStrength": "Denoising Strength",
"loading": "Loading...",
"negAestheticScore": "Negative Aesthetic Score",

View File

@@ -127,10 +127,6 @@
"title": "Fijar opciones",
"desc": "Fijar el panel de opciones"
},
"toggleViewer": {
"title": "Alternar visor",
"desc": "Mostar y ocultar el visor de imágenes"
},
"toggleGallery": {
"title": "Alternar galería",
"desc": "Mostar y ocultar la galería de imágenes"
@@ -191,10 +187,6 @@
"title": "Imagen siguiente",
"desc": "Muetra la imagen siguiente en la galería"
},
"toggleGalleryPin": {
"title": "Alternar fijado de galería",
"desc": "Fijar o desfijar la galería en la interfaz"
},
"increaseGalleryThumbSize": {
"title": "Aumentar imagen en galería",
"desc": "Aumenta el tamaño de las miniaturas de la galería"

View File

@@ -96,10 +96,6 @@
"title": "Epinglage des options",
"desc": "Epingler le panneau d'options"
},
"toggleViewer": {
"title": "Affichage de la visionneuse",
"desc": "Afficher et masquer la visionneuse d'image"
},
"toggleGallery": {
"title": "Affichage de la galerie",
"desc": "Afficher et masquer la galerie"
@@ -160,10 +156,6 @@
"title": "Image suivante",
"desc": "Afficher l'image suivante dans la galerie"
},
"toggleGalleryPin": {
"title": "Activer/désactiver l'épinglage de la galerie",
"desc": "Épingle ou dépingle la galerie à l'interface"
},
"increaseGalleryThumbSize": {
"title": "Augmenter la taille des miniatures de la galerie",
"desc": "Augmente la taille des miniatures de la galerie"

View File

@@ -192,10 +192,6 @@
"title": "הצמד הגדרות",
"desc": "הצמד את פאנל ההגדרות"
},
"toggleViewer": {
"title": "הצג את חלון ההצגה",
"desc": "פתח וסגור את מציג התמונות"
},
"changeTabs": {
"title": "החלף לשוניות",
"desc": "החלף לאיזור עבודה אחר"
@@ -236,10 +232,6 @@
"title": "תמונה קודמת",
"desc": "הצג את התמונה הקודמת בגלריה"
},
"toggleGalleryPin": {
"title": "הצג את מצמיד הגלריה",
"desc": "הצמדה וביטול הצמדה של הגלריה לממשק המשתמש"
},
"decreaseGalleryThumbSize": {
"title": "הקטנת גודל תמונת גלריה",
"desc": "מקטין את גודל התמונות הממוזערות של הגלריה"

View File

@@ -114,7 +114,9 @@
"nextPage": "Pagina successiva",
"saveAs": "Salva come",
"unsaved": "Non salvato",
"direction": "Direzione"
"direction": "Direzione",
"advancedOptions": "Opzioni avanzate",
"free": "Libero"
},
"gallery": {
"generations": "Generazioni",
@@ -132,7 +134,7 @@
"noImagesInGallery": "Nessuna immagine da visualizzare",
"deleteImage": "Elimina l'immagine",
"deleteImagePermanent": "Le immagini eliminate non possono essere ripristinate.",
"deleteImageBin": "Le immagini eliminate verranno spostate nel Cestino del tuo sistema operativo.",
"deleteImageBin": "Le immagini eliminate verranno spostate nel cestino del tuo sistema operativo.",
"assets": "Risorse",
"autoAssignBoardOnClick": "Assegna automaticamente la bacheca al clic",
"featuresWillReset": "Se elimini questa immagine, quelle funzionalità verranno immediatamente ripristinate.",
@@ -157,18 +159,18 @@
"problemDeletingImages": "Problema durante l'eliminazione delle immagini"
},
"hotkeys": {
"keyboardShortcuts": "Tasti rapidi",
"appHotkeys": "Tasti di scelta rapida dell'applicazione",
"generalHotkeys": "Tasti di scelta rapida generali",
"galleryHotkeys": "Tasti di scelta rapida della galleria",
"unifiedCanvasHotkeys": "Tasti di scelta rapida Tela Unificata",
"keyboardShortcuts": "Tasti di scelta rapida",
"appHotkeys": "Applicazione",
"generalHotkeys": "Generale",
"galleryHotkeys": "Galleria",
"unifiedCanvasHotkeys": "Tela Unificata",
"invoke": {
"title": "Invoke",
"desc": "Genera un'immagine"
},
"cancel": {
"title": "Annulla",
"desc": "Annulla la generazione dell'immagine"
"desc": "Annulla l'elemento della coda corrente"
},
"focusPrompt": {
"title": "Metti a fuoco il Prompt",
@@ -182,12 +184,8 @@
"title": "Appunta le opzioni",
"desc": "Blocca il pannello delle opzioni"
},
"toggleViewer": {
"title": "Attiva/disattiva visualizzatore",
"desc": "Apre e chiude il visualizzatore immagini"
},
"toggleGallery": {
"title": "Attiva/disattiva Galleria",
"title": "Attiva/disattiva galleria",
"desc": "Apre e chiude il pannello della galleria"
},
"maximizeWorkSpace": {
@@ -246,10 +244,6 @@
"title": "Immagine successiva",
"desc": "Visualizza l'immagine successiva nella galleria"
},
"toggleGalleryPin": {
"title": "Attiva/disattiva il blocco della galleria",
"desc": "Blocca/sblocca la galleria dall'interfaccia utente"
},
"increaseGalleryThumbSize": {
"title": "Aumenta dimensione immagini nella galleria",
"desc": "Aumenta la dimensione delle miniature della galleria"
@@ -362,11 +356,26 @@
"title": "Accetta l'immagine della sessione",
"desc": "Accetta l'immagine dell'area della sessione corrente"
},
"nodesHotkeys": "Tasti di scelta rapida dei Nodi",
"nodesHotkeys": "Nodi",
"addNodes": {
"title": "Aggiungi Nodi",
"desc": "Apre il menu Aggiungi Nodi"
}
},
"cancelAndClear": {
"desc": "Annulla l'elemento della coda corrente e cancella tutti gli elementi in sospeso",
"title": "Annulla e cancella"
},
"resetOptionsAndGallery": {
"title": "Ripristina Opzioni e Galleria",
"desc": "Reimposta le opzioni e i pannelli della galleria"
},
"searchHotkeys": "Cerca tasti di scelta rapida",
"noHotkeysFound": "Nessun tasto di scelta rapida trovato",
"toggleOptionsAndGallery": {
"desc": "Apre e chiude le opzioni e i pannelli della galleria",
"title": "Attiva/disattiva le Opzioni e la Galleria"
},
"clearSearch": "Cancella ricerca"
},
"modelManager": {
"modelManager": "Gestione Modelli",
@@ -581,8 +590,8 @@
"hidePreview": "Nascondi l'anteprima",
"showPreview": "Mostra l'anteprima",
"noiseSettings": "Rumore",
"seamlessXAxis": "Asse X",
"seamlessYAxis": "Asse Y",
"seamlessXAxis": "Piastrella senza cucitura Asse X",
"seamlessYAxis": "Piastrella senza cucitura Asse Y",
"scheduler": "Campionatore",
"boundingBoxWidth": "Larghezza riquadro di delimitazione",
"boundingBoxHeight": "Altezza riquadro di delimitazione",
@@ -642,7 +651,14 @@
"unmasked": "No maschera",
"cfgRescaleMultiplier": "Moltiplicatore riscala CFG",
"cfgRescale": "Riscala CFG",
"useSize": "Usa Dimensioni"
"useSize": "Usa Dimensioni",
"setToOptimalSize": "Ottimizza le dimensioni per il modello",
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (potrebbe essere troppo piccolo)",
"imageSize": "Dimensione dell'immagine",
"lockAspectRatio": "Blocca proporzioni",
"swapDimensions": "Scambia dimensioni",
"aspect": "Aspetto",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (potrebbe essere troppo grande)"
},
"settings": {
"models": "Modelli",
@@ -1178,7 +1194,6 @@
"w": "W",
"processor": "Processore",
"none": "Nessuno",
"incompatibleBaseModel": "Modello base incompatibile:",
"pidiDescription": "Elaborazione immagini PIDI",
"fill": "Riempie",
"colorMapDescription": "Genera una mappa dei colori dall'immagine",
@@ -1214,12 +1229,13 @@
"minConfidence": "Confidenza minima",
"scribble": "Scribble",
"amult": "Angolo di illuminazione",
"coarse": "Approssimativo"
"coarse": "Approssimativo",
"resizeSimple": "Ridimensiona (semplice)"
},
"queue": {
"queueFront": "Aggiungi all'inizio della coda",
"queueBack": "Aggiungi alla coda",
"queueCountPrediction": "Aggiungi {{predicted}} alla coda",
"queueCountPrediction": "{{promptsCount}} prompt × {{iterations}} iterazioni -> {{count}} generazioni",
"queue": "Coda",
"status": "Stato",
"pruneSucceeded": "Rimossi {{item_count}} elementi completati dalla coda",
@@ -1277,7 +1293,8 @@
"graphFailedToQueue": "Impossibile mettere in coda il grafico",
"queueMaxExceeded": "È stato superato il limite massimo di {{max_queue_size}} e {{skip}} elementi verrebbero saltati",
"batchFieldValues": "Valori Campi Lotto",
"time": "Tempo"
"time": "Tempo",
"openQueue": "Apri coda"
},
"embedding": {
"noMatchingEmbedding": "Nessun Incorporamento corrispondente",
@@ -1297,7 +1314,12 @@
"noLoRAsInstalled": "Nessun LoRA installato",
"esrganModel": "Modello ESRGAN",
"addLora": "Aggiungi LoRA",
"noLoRAsLoaded": "Nessuna LoRA caricata"
"noLoRAsLoaded": "Nessun LoRA caricato",
"noMainModelSelected": "Nessun modello principale selezionato",
"allLoRAsAdded": "Tutti i LoRA aggiunti",
"defaultVAE": "VAE predefinito",
"incompatibleBaseModel": "Modello base incompatibile",
"loraAlreadyAdded": "LoRA già aggiunto"
},
"invocationCache": {
"disable": "Disabilita",
@@ -1331,7 +1353,9 @@
"promptsWithCount_many": "{{count}} Prompt",
"promptsWithCount_other": "{{count}} Prompt",
"dynamicPrompts": "Prompt dinamici",
"promptsPreview": "Anteprima dei prompt"
"promptsPreview": "Anteprima dei prompt",
"showDynamicPrompts": "Mostra prompt dinamici",
"loading": "Generazione prompt dinamici..."
},
"popovers": {
"paramScheduler": {
@@ -1557,7 +1581,7 @@
"scheduler": "Campionatore",
"noModelsAvailable": "Nessun modello disponibile",
"denoisingStrength": "Forza di riduzione del rumore",
"concatPromptStyle": "Concatena Prompt & Stile",
"concatPromptStyle": "Collega Prompt & Stile",
"loading": "Caricamento...",
"steps": "Passi",
"refinerStart": "Inizio Affinamento",
@@ -1568,7 +1592,8 @@
"useRefiner": "Utilizza l'affinatore",
"refinermodel": "Modello Affinatore",
"posAestheticScore": "Punteggio estetico positivo",
"posStylePrompt": "Prompt Stile positivo"
"posStylePrompt": "Prompt Stile positivo",
"freePromptStyle": "Prompt di stile manuale"
},
"metadata": {
"initImage": "Immagine iniziale",
@@ -1641,5 +1666,28 @@
},
"app": {
"storeNotInitialized": "Il negozio non è inizializzato"
},
"accordions": {
"compositing": {
"infillTab": "Riempimento",
"coherenceTab": "Passaggio di coerenza",
"title": "Composizione"
},
"control": {
"controlAdaptersTab": "Adattatori di Controllo",
"ipTab": "Prompt immagine",
"title": "Controllo"
},
"generation": {
"title": "Generazione",
"conceptsTab": "Concetti",
"modelTab": "Modello"
},
"advanced": {
"title": "Avanzate"
},
"image": {
"title": "Immagine"
}
}
}

View File

@@ -133,10 +133,6 @@
"title": "ピン",
"desc": "オプションパネルを固定"
},
"toggleViewer": {
"title": "ビュワーのトグル",
"desc": "ビュワーを開閉"
},
"toggleGallery": {
"title": "ギャラリーのトグル",
"desc": "ギャラリードロワーの開閉"
@@ -197,10 +193,6 @@
"title": "次の画像",
"desc": "ギャラリー内の1つ後の画像を表示"
},
"toggleGalleryPin": {
"title": "ギャラリードロワーの固定",
"desc": "ギャラリーをUIにピン留め/解除"
},
"increaseGalleryThumbSize": {
"title": "ギャラリーの画像を拡大",
"desc": "ギャラリーのサムネイル画像を拡大"
@@ -590,7 +582,6 @@
"processor": "プロセッサー",
"addControlNet": "$t(common.controlNet)を追加",
"none": "なし",
"incompatibleBaseModel": "互換性のないベースモデル:",
"enableControlnet": "コントロールネットを有効化",
"detectResolution": "検出解像度",
"controlNetT2IMutexDesc": "$t(common.controlNet)と$t(common.t2iAdapter)の同時使用は現在サポートされていません。",

View File

@@ -363,7 +363,6 @@
"processor": "프로세서",
"addControlNet": "$t(common.controlNet) 추가",
"none": "해당없음",
"incompatibleBaseModel": "호환되지 않는 기본 모델:",
"enableControlnet": "사용 가능한 ControlNet",
"detectResolution": "해상도 탐지",
"controlNetT2IMutexDesc": "$t(common.controlNet)와 $t(common.t2iAdapter)는 현재 동시에 지원되지 않습니다.",
@@ -408,10 +407,6 @@
"maxFaces": "Max Faces"
},
"hotkeys": {
"toggleGalleryPin": {
"title": "Gallery Pin 전환",
"desc": "갤러리를 UI에 고정했다가 풉니다"
},
"toggleSnap": {
"desc": "Snap을 Grid로 전환",
"title": "Snap 전환"
@@ -601,10 +596,6 @@
"desc": "노드 추가 메뉴 열기",
"title": "노드 추가"
},
"toggleViewer": {
"desc": "이미지 뷰어 열기 및 닫기",
"title": "Viewer 전환"
},
"undoStroke": {
"title": "Stroke 실행 취소",
"desc": "brush stroke 실행 취소"

View File

@@ -148,10 +148,6 @@
"title": "Zet Opties vast",
"desc": "Zet het deelscherm Opties vast"
},
"toggleViewer": {
"title": "Zet Viewer vast",
"desc": "Opent of sluit Afbeeldingsviewer"
},
"toggleGallery": {
"title": "Zet Galerij vast",
"desc": "Opent of sluit het deelscherm Galerij"
@@ -212,10 +208,6 @@
"title": "Volgende afbeelding",
"desc": "Toont de volgende afbeelding in de galerij"
},
"toggleGalleryPin": {
"title": "Zet galerij vast/los",
"desc": "Zet de galerij vast of los aan de gebruikersinterface"
},
"increaseGalleryThumbSize": {
"title": "Vergroot afbeeldingsgrootte galerij",
"desc": "Vergroot de grootte van de galerijminiaturen"
@@ -1071,7 +1063,6 @@
"processor": "Verwerker",
"addControlNet": "Voeg $t(common.controlNet) toe",
"none": "Geen",
"incompatibleBaseModel": "Niet-compatibel basismodel:",
"enableControlnet": "Schakel ControlNet in",
"detectResolution": "Herken resolutie",
"controlNetT2IMutexDesc": "Gelijktijdig gebruik van $t(common.controlNet) en $t(common.t2iAdapter) wordt op dit moment niet ondersteund.",

View File

@@ -86,10 +86,6 @@
"title": "Przypnij opcje",
"desc": "Przypina panel opcji"
},
"toggleViewer": {
"title": "Przełącz podgląd",
"desc": "Otwiera lub zamyka widok podglądu"
},
"toggleGallery": {
"title": "Przełącz galerię",
"desc": "Wysuwa lub chowa galerię"
@@ -150,10 +146,6 @@
"title": "Następny obraz",
"desc": "Aktywuje następny obraz z galerii"
},
"toggleGalleryPin": {
"title": "Przypnij galerię",
"desc": "Przypina lub odpina widok galerii"
},
"increaseGalleryThumbSize": {
"title": "Powiększ obrazy",
"desc": "Powiększa rozmiar obrazów w galerii"

View File

@@ -81,10 +81,6 @@
"hotkeys": {
"generalHotkeys": "Atalhos Gerais",
"galleryHotkeys": "Atalhos da Galeria",
"toggleViewer": {
"title": "Ativar Visualizador",
"desc": "Abrir e fechar o Visualizador de Imagens"
},
"maximizeWorkSpace": {
"desc": "Fechar painéis e maximixar área de trabalho",
"title": "Maximizar a Área de Trabalho"
@@ -232,10 +228,6 @@
"title": "Apagar Imagem",
"desc": "Apaga a imagem atual"
},
"toggleGalleryPin": {
"title": "Ativar Fixar Galeria",
"desc": "Fixa e desafixa a galeria na interface"
},
"increaseGalleryThumbSize": {
"title": "Aumentar Tamanho da Galeria de Imagem",
"desc": "Aumenta o tamanho das thumbs na galeria"

View File

@@ -103,10 +103,6 @@
"title": "Fixar Opções",
"desc": "Fixar o painel de opções"
},
"toggleViewer": {
"title": "Ativar Visualizador",
"desc": "Abrir e fechar o Visualizador de Imagens"
},
"toggleGallery": {
"title": "Ativar Galeria",
"desc": "Abrir e fechar a gaveta da galeria"
@@ -167,10 +163,6 @@
"title": "Próxima Imagem",
"desc": "Mostra a próxima imagem na galeria"
},
"toggleGalleryPin": {
"title": "Ativar Fixar Galeria",
"desc": "Fixa e desafixa a galeria na interface"
},
"increaseGalleryThumbSize": {
"title": "Aumentar Tamanho da Galeria de Imagem",
"desc": "Aumenta o tamanho das thumbs na galeria"

View File

@@ -189,10 +189,6 @@
"title": "Закрепить параметры",
"desc": "Закрепить панель параметров"
},
"toggleViewer": {
"title": "Показать просмотр",
"desc": "Открывать и закрывать просмотрщик изображений"
},
"toggleGallery": {
"title": "Показать галерею",
"desc": "Открывать и закрывать ящик галереи"
@@ -253,10 +249,6 @@
"title": "Следующее изображение",
"desc": "Отображение следующего изображения в галерее"
},
"toggleGalleryPin": {
"title": "Закрепить галерею",
"desc": "Закрепляет и открепляет галерею"
},
"increaseGalleryThumbSize": {
"title": "Увеличить размер миниатюр галереи",
"desc": "Увеличивает размер миниатюр галереи"
@@ -523,7 +515,7 @@
"parameters": {
"images": "Изображения",
"steps": "Шаги",
"cfgScale": "Точность следования запросу (CFG)",
"cfgScale": "Шкала точности (CFG)",
"width": "Ширина",
"height": "Высота",
"seed": "Сид",
@@ -1162,7 +1154,6 @@
"processor": "Процессор",
"addControlNet": "Добавить $t(common.controlNet)",
"none": "ничего",
"incompatibleBaseModel": "Несовместимая базовая модель:",
"controlNetT2IMutexDesc": "$t(common.controlNet) и $t(common.t2iAdapter) одновременно в настоящее время не поддерживаются.",
"ip_adapter": "$t(controlnet.controlAdapter_one) №{{number}} $t(common.ipAdapter)",
"pidiDescription": "PIDI-обработка изображений",

View File

@@ -129,10 +129,6 @@
"title": "Växla inställningar",
"desc": "Öppna och stäng alternativpanelen"
},
"toggleViewer": {
"title": "Växla visaren",
"desc": "Öppna och stäng bildvisaren"
},
"toggleGallery": {
"title": "Växla galleri",
"desc": "Öppna eller stäng galleribyrån"
@@ -193,10 +189,6 @@
"title": "Nästa bild",
"desc": "Visa nästa bild"
},
"toggleGalleryPin": {
"title": "Växla gallerinål",
"desc": "Nålar fast eller nålar av galleriet i gränssnittet"
},
"increaseGalleryThumbSize": {
"title": "Förstora galleriets bildstorlek",
"desc": "Förstora miniatyrbildernas storlek"

View File

@@ -111,10 +111,6 @@
"title": "Закріпити параметри",
"desc": "Закріпити панель параметрів"
},
"toggleViewer": {
"title": "Показати перегляд",
"desc": "Відкривати і закривати переглядач зображень"
},
"toggleGallery": {
"title": "Показати галерею",
"desc": "Відкривати і закривати скриньку галереї"
@@ -175,10 +171,6 @@
"title": "Наступне зображення",
"desc": "Відображення наступного зображення в галереї"
},
"toggleGalleryPin": {
"title": "Закріпити галерею",
"desc": "Закріплює і відкріплює галерею"
},
"increaseGalleryThumbSize": {
"title": "Збільшити розмір мініатюр галереї",
"desc": "Збільшує розмір мініатюр галереї"

View File

@@ -189,10 +189,6 @@
"title": "常开选项卡",
"desc": "保持选项浮窗常开"
},
"toggleViewer": {
"title": "切换图像查看器",
"desc": "打开或关闭图像查看器"
},
"toggleGallery": {
"title": "切换图库",
"desc": "打开或关闭图库"
@@ -253,10 +249,6 @@
"title": "下一张图像",
"desc": "显示图库中的下一张图像"
},
"toggleGalleryPin": {
"title": "切换图库常开",
"desc": "开关图库在界面中的常开模式"
},
"increaseGalleryThumbSize": {
"title": "增大预览尺寸",
"desc": "增大图库中预览的尺寸"
@@ -1149,7 +1141,6 @@
"crop": "裁剪",
"processor": "处理器",
"none": "无",
"incompatibleBaseModel": "不兼容的基础模型:",
"enableControlnet": "启用 ControlNet",
"detectResolution": "检测分辨率",
"pidiDescription": "像素差分 (PIDI) 图像处理",

View File

@@ -1,8 +1,9 @@
import fs from 'node:fs';
import openapiTS from 'openapi-typescript';
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
const OUTPUT_FILE = 'src/services/api/schema.d.ts';
const OUTPUT_FILE = 'src/services/api/schema.ts';
async function main() {
process.stdout.write(

View File

@@ -1,27 +1,29 @@
import { Flex, Grid } from '@chakra-ui/react';
import { useStore } from '@nanostores/react';
import { Box } from '@chakra-ui/react';
import { useSocketIO } from 'app/hooks/useSocketIO';
import { useLogger } from 'app/logging/useLogger';
import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
import { $headerComponent } from 'app/store/nanostores/headerComponent';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { PartialAppConfig } from 'app/types/invokeai';
import ImageUploader from 'common/components/ImageUploader';
import type { PartialAppConfig } from 'app/types/invokeai';
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
import { useClearStorage } from 'common/hooks/useClearStorage';
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
import { useGlobalModifiersInit } from 'common/hooks/useGlobalModifiers';
import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal';
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
import SiteHeader from 'features/system/components/SiteHeader';
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
import { configChanged } from 'features/system/store/configSlice';
import { languageSelector } from 'features/system/store/systemSelectors';
import InvokeTabs from 'features/ui/components/InvokeTabs';
import { AnimatePresence } from 'framer-motion';
import i18n from 'i18n';
import { size } from 'lodash-es';
import { memo, useCallback, useEffect } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
import GlobalHotkeys from './GlobalHotkeys';
import PreselectedImage from './PreselectedImage';
import Toaster from './Toaster';
import { useSocketIO } from 'app/hooks/useSocketIO';
import { useClearStorage } from 'common/hooks/useClearStorage';
const DEFAULT_CONFIG = {};
@@ -41,6 +43,11 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
// singleton!
useSocketIO();
useGlobalModifiersInit();
useGlobalHotkeys();
const { dropzone, isHandlingUpload, setIsHandlingUpload } =
useFullscreenDropzone();
const handleReset = useCallback(() => {
clearStorage();
@@ -63,41 +70,34 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
dispatch(appStarted());
}, [dispatch]);
const headerComponent = useStore($headerComponent);
return (
<ErrorBoundary
onReset={handleReset}
FallbackComponent={AppErrorBoundaryFallback}
>
<Grid w="100vw" h="100vh" position="relative" overflow="hidden">
<ImageUploader>
<Grid
sx={{
gap: 4,
p: 4,
gridAutoRows: 'min-content auto',
w: 'full',
h: 'full',
}}
>
{headerComponent || <SiteHeader />}
<Flex
sx={{
gap: 4,
w: 'full',
h: 'full',
}}
>
<InvokeTabs />
</Flex>
</Grid>
</ImageUploader>
</Grid>
<Box
id="invoke-app-wrapper"
w="100vw"
h="100vh"
position="relative"
overflow="hidden"
{...dropzone.getRootProps()}
>
<input {...dropzone.getInputProps()} />
<InvokeTabs />
<AnimatePresence>
{dropzone.isDragActive && isHandlingUpload && (
<ImageUploadOverlay
dropzone={dropzone}
setIsHandlingUpload={setIsHandlingUpload}
/>
)}
</AnimatePresence>
</Box>
<DeleteImageModal />
<ChangeBoardModal />
<DynamicPromptsModal />
<Toaster />
<GlobalHotkeys />
<PreselectedImage selectedImage={selectedImage} />
</ErrorBoundary>
);

View File

@@ -1,5 +1,6 @@
import { Flex, Heading, Link, Text, useToast } from '@chakra-ui/react';
import IAIButton from 'common/components/IAIButton';
import { Flex, Heading, Link, useToast } from '@chakra-ui/react';
import { InvButton } from 'common/components/InvButton/InvButton';
import { InvText } from 'common/components/InvText/wrapper';
import newGithubIssueUrl from 'new-github-issue-url';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -37,60 +38,48 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
return (
<Flex
layerStyle="body"
sx={{
w: '100vw',
h: '100vh',
alignItems: 'center',
justifyContent: 'center',
p: 4,
}}
w="100vw"
h="100vh"
alignItems="center"
justifyContent="center"
p={4}
>
<Flex
layerStyle="first"
sx={{
flexDir: 'column',
borderRadius: 'base',
justifyContent: 'center',
gap: 8,
p: 16,
}}
flexDir="column"
borderRadius="base"
justifyContent="center"
gap={8}
p={16}
>
<Heading>{t('common.somethingWentWrong')}</Heading>
<Flex
layerStyle="second"
sx={{
px: 8,
py: 4,
borderRadius: 'base',
gap: 4,
justifyContent: 'space-between',
alignItems: 'center',
}}
px={8}
py={4}
gap={4}
borderRadius="base"
justifyContent="space-between"
alignItems="center"
>
<Text
sx={{
fontWeight: 600,
color: 'error.500',
_dark: { color: 'error.400' },
}}
>
<InvText fontWeight="semibold" color="error.400">
{error.name}: {error.message}
</Text>
</InvText>
</Flex>
<Flex sx={{ gap: 4 }}>
<IAIButton
<Flex gap={4}>
<InvButton
leftIcon={<FaArrowRotateLeft />}
onClick={resetErrorBoundary}
>
{t('accessibility.resetUI')}
</IAIButton>
<IAIButton leftIcon={<FaCopy />} onClick={handleCopy}>
</InvButton>
<InvButton leftIcon={<FaCopy />} onClick={handleCopy}>
{t('common.copyError')}
</IAIButton>
</InvButton>
<Link href={url} isExternal>
<IAIButton leftIcon={<FaExternalLinkAlt />}>
<InvButton leftIcon={<FaExternalLinkAlt />}>
{t('accessibility.createIssue')}
</IAIButton>
</InvButton>
</Link>
</Flex>
</Flex>

View File

@@ -1,112 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
import { useQueueFront } from 'features/queue/hooks/useQueueFront';
import {
ctrlKeyPressed,
metaKeyPressed,
shiftKeyPressed,
} from 'features/ui/store/hotkeysSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import React, { memo } from 'react';
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
const globalHotkeysSelector = createMemoizedSelector(
[stateSelector],
({ hotkeys }) => {
const { shift, ctrl, meta } = hotkeys;
return { shift, ctrl, meta };
}
);
// TODO: Does not catch keypresses while focused in an input. Maybe there is a way?
/**
* Logical component. Handles app-level global hotkeys.
* @returns null
*/
const GlobalHotkeys: React.FC = () => {
const dispatch = useAppDispatch();
const { shift, ctrl, meta } = useAppSelector(globalHotkeysSelector);
const {
queueBack,
isDisabled: isDisabledQueueBack,
isLoading: isLoadingQueueBack,
} = useQueueBack();
useHotkeys(
['ctrl+enter', 'meta+enter'],
queueBack,
{
enabled: () => !isDisabledQueueBack && !isLoadingQueueBack,
preventDefault: true,
enableOnFormTags: ['input', 'textarea', 'select'],
},
[queueBack, isDisabledQueueBack, isLoadingQueueBack]
);
const {
queueFront,
isDisabled: isDisabledQueueFront,
isLoading: isLoadingQueueFront,
} = useQueueFront();
useHotkeys(
['ctrl+shift+enter', 'meta+shift+enter'],
queueFront,
{
enabled: () => !isDisabledQueueFront && !isLoadingQueueFront,
preventDefault: true,
enableOnFormTags: ['input', 'textarea', 'select'],
},
[queueFront, isDisabledQueueFront, isLoadingQueueFront]
);
useHotkeys(
'*',
() => {
if (isHotkeyPressed('shift')) {
!shift && dispatch(shiftKeyPressed(true));
} else {
shift && dispatch(shiftKeyPressed(false));
}
if (isHotkeyPressed('ctrl')) {
!ctrl && dispatch(ctrlKeyPressed(true));
} else {
ctrl && dispatch(ctrlKeyPressed(false));
}
if (isHotkeyPressed('meta')) {
!meta && dispatch(metaKeyPressed(true));
} else {
meta && dispatch(metaKeyPressed(false));
}
},
{ keyup: true, keydown: true },
[shift, ctrl, meta]
);
useHotkeys('1', () => {
dispatch(setActiveTab('txt2img'));
});
useHotkeys('2', () => {
dispatch(setActiveTab('img2img'));
});
useHotkeys('3', () => {
dispatch(setActiveTab('unifiedCanvas'));
});
useHotkeys('4', () => {
dispatch(setActiveTab('nodes'));
});
useHotkeys('5', () => {
dispatch(setActiveTab('modelManager'));
});
return null;
};
export default memo(GlobalHotkeys);

View File

@@ -1,29 +1,27 @@
import { Middleware } from '@reduxjs/toolkit';
import 'i18n';
import type { Middleware } from '@reduxjs/toolkit';
import { $socketOptions } from 'app/hooks/useSocketIO';
import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
import { $headerComponent } from 'app/store/nanostores/headerComponent';
import { $customNavComponent } from 'app/store/nanostores/customNavComponent';
import type { CustomStarUi } from 'app/store/nanostores/customStarUI';
import { $customStarUI } from 'app/store/nanostores/customStarUI';
import { $galleryHeader } from 'app/store/nanostores/galleryHeader';
import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { $logo } from 'app/store/nanostores/logo';
import { $projectId } from 'app/store/nanostores/projectId';
import { $queueId, DEFAULT_QUEUE_ID } from 'app/store/nanostores/queueId';
import { $store } from 'app/store/nanostores/store';
import { createStore } from 'app/store/store';
import { PartialAppConfig } from 'app/types/invokeai';
import type { PartialAppConfig } from 'app/types/invokeai';
import Loading from 'common/components/Loading/Loading';
import AppDndContext from 'features/dnd/components/AppDndContext';
import 'i18n';
import React, {
PropsWithChildren,
ReactNode,
lazy,
memo,
useEffect,
useMemo,
} from 'react';
import type { PropsWithChildren, ReactNode } from 'react';
import React, { lazy, memo, useEffect, useMemo } from 'react';
import { Provider } from 'react-redux';
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
import { ManagerOptions, SocketOptions } from 'socket.io-client';
import type { ManagerOptions, SocketOptions } from 'socket.io-client';
const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
@@ -32,9 +30,10 @@ interface Props extends PropsWithChildren {
apiUrl?: string;
token?: string;
config?: PartialAppConfig;
headerComponent?: ReactNode;
customNavComponent?: ReactNode;
middleware?: Middleware[];
projectId?: string;
galleryHeader?: ReactNode;
queueId?: string;
selectedImage?: {
imageName: string;
@@ -43,20 +42,23 @@ interface Props extends PropsWithChildren {
customStarUi?: CustomStarUi;
socketOptions?: Partial<ManagerOptions & SocketOptions>;
isDebugging?: boolean;
logo?: ReactNode;
}
const InvokeAIUI = ({
apiUrl,
token,
config,
headerComponent,
customNavComponent,
middleware,
projectId,
galleryHeader,
queueId,
selectedImage,
customStarUi,
socketOptions,
isDebugging = false,
logo,
}: Props) => {
useEffect(() => {
// configure API client token
@@ -112,14 +114,34 @@ const InvokeAIUI = ({
}, [customStarUi]);
useEffect(() => {
if (headerComponent) {
$headerComponent.set(headerComponent);
if (customNavComponent) {
$customNavComponent.set(customNavComponent);
}
return () => {
$headerComponent.set(undefined);
$customNavComponent.set(undefined);
};
}, [headerComponent]);
}, [customNavComponent]);
useEffect(() => {
if (galleryHeader) {
$galleryHeader.set(galleryHeader);
}
return () => {
$galleryHeader.set(undefined);
};
}, [galleryHeader]);
useEffect(() => {
if (logo) {
$logo.set(logo);
}
return () => {
$logo.set(undefined);
};
}, [logo]);
useEffect(() => {
if (socketOptions) {
@@ -145,6 +167,15 @@ const InvokeAIUI = ({
useEffect(() => {
$store.set(store);
if (import.meta.env.MODE === 'development') {
window.$store = $store;
}
() => {
$store.set(undefined);
if (import.meta.env.MODE === 'development') {
window.$store = undefined;
}
};
}, [store]);
return (

View File

@@ -1,24 +1,17 @@
import {
ChakraProvider,
createLocalStorageManager,
extendTheme,
} from '@chakra-ui/react';
import { ReactNode, memo, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { TOAST_OPTIONS, theme as invokeAITheme } from 'theme/theme';
import '@fontsource-variable/inter';
import { MantineProvider } from '@mantine/core';
import { useMantineTheme } from 'mantine-theme/theme';
import 'overlayscrollbars/overlayscrollbars.css';
import 'theme/css/overlayscrollbars.css';
import 'common/components/OverlayScrollbars/overlayscrollbars.css';
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
import type { ReactNode } from 'react';
import { memo, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { theme as invokeAITheme, TOAST_OPTIONS } from 'theme/theme';
type ThemeLocaleProviderProps = {
children: ReactNode;
};
const manager = createLocalStorageManager('@@invokeai-color-mode');
function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
const { i18n } = useTranslation();
@@ -35,18 +28,10 @@ function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
document.body.dir = direction;
}, [direction]);
const mantineTheme = useMantineTheme();
return (
<MantineProvider theme={mantineTheme}>
<ChakraProvider
theme={theme}
colorModeManager={manager}
toastOptions={TOAST_OPTIONS}
>
{children}
</ChakraProvider>
</MantineProvider>
<ChakraProvider theme={theme} toastOptions={TOAST_OPTIONS}>
{children}
</ChakraProvider>
);
}

View File

@@ -1,7 +1,8 @@
import { useToast } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
import { MakeToastArg, makeToast } from 'features/system/util/makeToast';
import type { MakeToastArg } from 'features/system/util/makeToast';
import { makeToast } from 'features/system/util/makeToast';
import { memo, useCallback, useEffect } from 'react';
/**
@@ -10,7 +11,7 @@ import { memo, useCallback, useEffect } from 'react';
*/
const Toaster = () => {
const dispatch = useAppDispatch();
const toastQueue = useAppSelector((state) => state.system.toastQueue);
const toastQueue = useAppSelector((s) => s.system.toastQueue);
const toast = useToast();
useEffect(() => {
toastQueue.forEach((t) => {

View File

@@ -1,94 +0,0 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type FeatureHelpInfo = {
text: string;
href: string;
guideImage: string;
};
export enum Feature {
PROMPT,
GALLERY,
OTHER,
SEED,
VARIATIONS,
UPSCALE,
FACE_CORRECTION,
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.
*
* 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 .
*/
const useFeatures = (): Record<Feature, FeatureHelpInfo> => {
const { t } = useTranslation();
return useMemo(
() => ({
[Feature.PROMPT]: {
text: t('tooltip.feature.prompt'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.GALLERY]: {
text: t('tooltip.feature.gallery'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.OTHER]: {
text: t('tooltip.feature.other'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.SEED]: {
text: t('tooltip.feature.seed'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.VARIATIONS]: {
text: t('tooltip.feature.variations'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.UPSCALE]: {
text: t('tooltip.feature.upscale'),
href: 'link/to/docs/feature1.html',
guideImage: 'asset/path.gif',
},
[Feature.FACE_CORRECTION]: {
text: t('tooltip.feature.faceCorrection'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.IMAGE_TO_IMAGE]: {
text: t('tooltip.feature.imageToImage'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.BOUNDING_BOX]: {
text: t('tooltip.feature.boundingBox'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.SEAM_CORRECTION]: {
text: t('tooltip.feature.seamCorrection'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.INFILL_AND_SCALING]: {
text: t('tooltip.feature.infillAndScaling'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
}),
[t]
);
};
export const useFeatureHelpInfo = (feature: Feature): FeatureHelpInfo => {
const features = useFeatures();
return features[feature];
};

View File

@@ -3,14 +3,16 @@ import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { useAppDispatch } from 'app/store/storeHooks';
import { MapStore, atom, map } from 'nanostores';
import type { MapStore } from 'nanostores';
import { atom, map } from 'nanostores';
import { useEffect, useMemo } from 'react';
import {
import type {
ClientToServerEvents,
ServerToClientEvents,
} from 'services/events/types';
import { setEventListeners } from 'services/events/util/setEventListeners';
import { ManagerOptions, Socket, SocketOptions, io } from 'socket.io-client';
import type { ManagerOptions, Socket, SocketOptions } from 'socket.io-client';
import { io } from 'socket.io-client';
// Inject socket options and url into window for debugging
declare global {

View File

@@ -1,6 +1,8 @@
import { createLogWriter } from '@roarr/browser-log-writer';
import { atom } from 'nanostores';
import { Logger, ROARR, Roarr } from 'roarr';
import type { Logger } from 'roarr';
import { ROARR, Roarr } from 'roarr';
import { z } from 'zod';
ROARR.write = createLogWriter();
@@ -26,19 +28,20 @@ export type LoggerNamespace =
export const logger = (namespace: LoggerNamespace) =>
$logger.get().child({ namespace });
export const VALID_LOG_LEVELS = [
export const zLogLevel = z.enum([
'trace',
'debug',
'info',
'warn',
'error',
'fatal',
] as const;
export type InvokeLogLevel = (typeof VALID_LOG_LEVELS)[number];
]);
export type LogLevel = z.infer<typeof zLogLevel>;
export const isLogLevel = (v: unknown): v is LogLevel =>
zLogLevel.safeParse(v).success;
// Translate human-readable log levels to numbers, used for log filtering
export const LOG_LEVEL_MAP: Record<InvokeLogLevel, number> = {
export const LOG_LEVEL_MAP: Record<LogLevel, number> = {
trace: 10,
debug: 20,
info: 30,

View File

@@ -1,28 +1,14 @@
import { createLogWriter } from '@roarr/browser-log-writer';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { useEffect, useMemo } from 'react';
import { ROARR, Roarr } from 'roarr';
import {
$logger,
BASE_CONTEXT,
LOG_LEVEL_MAP,
LoggerNamespace,
logger,
} from './logger';
const selector = createMemoizedSelector(stateSelector, ({ system }) => {
const { consoleLogLevel, shouldLogToConsole } = system;
return {
consoleLogLevel,
shouldLogToConsole,
};
});
import type { LoggerNamespace } from './logger';
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
export const useLogger = (namespace: LoggerNamespace) => {
const { consoleLogLevel, shouldLogToConsole } = useAppSelector(selector);
const consoleLogLevel = useAppSelector((s) => s.system.consoleLogLevel);
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
// The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => {

View File

@@ -1,6 +1,6 @@
import { createAction } from '@reduxjs/toolkit';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { BatchConfig } from 'services/api/types';
import type { InvokeTabName } from 'features/ui/store/tabMap';
import type { BatchConfig } from 'services/api/types';
export const enqueueRequested = createAction<{
tabName: InvokeTabName;

View File

@@ -1,4 +1,9 @@
import { createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
import {
createDraftSafeSelectorCreator,
createSelectorCreator,
lruMemoize,
} from '@reduxjs/toolkit';
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
import { isEqual } from 'lodash-es';
/**
@@ -9,4 +14,22 @@ export const createMemoizedSelector = createSelectorCreator({
memoizeOptions: {
resultEqualityCheck: isEqual,
},
argsMemoize: lruMemoize,
});
/**
* A memoized selector creator that uses LRU cache default shallow equality check.
*/
export const createLruSelector = createSelectorCreator({
memoize: lruMemoize,
argsMemoize: lruMemoize,
});
export const createLruDraftSafeSelector = createDraftSafeSelectorCreator({
memoize: lruMemoize,
argsMemoize: lruMemoize,
});
export const getSelectorsOptions: GetSelectorsOptions = {
createSelector: createLruDraftSafeSelector,
};

View File

@@ -8,7 +8,7 @@ import { postprocessingPersistDenylist } from 'features/parameters/store/postpro
import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
import { omit } from 'lodash-es';
import { SerializeFunction } from 'redux-remember';
import type { SerializeFunction } from 'redux-remember';
const serializationDenylist: {
[key: string]: string[];

View File

@@ -8,10 +8,9 @@ import { initialPostprocessingState } from 'features/parameters/store/postproces
import { initialSDXLState } from 'features/sdxl/store/sdxlSlice';
import { initialConfigState } from 'features/system/store/configSlice';
import { initialSystemState } from 'features/system/store/systemSlice';
import { initialHotkeysState } from 'features/ui/store/hotkeysSlice';
import { initialUIState } from 'features/ui/store/uiSlice';
import { defaultsDeep } from 'lodash-es';
import { UnserializeFunction } from 'redux-remember';
import type { UnserializeFunction } from 'redux-remember';
const initialStates: {
[key: string]: object; // TODO: type this properly
@@ -24,7 +23,6 @@ const initialStates: {
system: initialSystemState,
config: initialConfigState,
ui: initialUIState,
hotkeys: initialHotkeysState,
controlAdapters: initialControlAdapterState,
dynamicPrompts: initialDynamicPromptsState,
sdxl: initialSDXLState,

View File

@@ -1,8 +1,8 @@
import { UnknownAction } from '@reduxjs/toolkit';
import type { UnknownAction } from '@reduxjs/toolkit';
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodeTemplatesSlice';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { Graph } from 'services/api/types';
import type { Graph } from 'services/api/types';
export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
if (isAnyGraphBuilt(action)) {

View File

@@ -3,19 +3,14 @@
*/
export const actionsDenylist = [
// very spammy canvas actions
'canvas/setCursorPosition',
'canvas/setStageCoordinates',
'canvas/setStageScale',
'canvas/setIsDrawing',
'canvas/setBoundingBoxCoordinates',
'canvas/setBoundingBoxDimensions',
'canvas/setIsDrawing',
'canvas/addPointToCurrentLine',
// bazillions during generation
'socket/socketGeneratorProgress',
'socket/appSocketGeneratorProgress',
// every time user presses shift
// 'hotkeys/shiftKeyPressed',
// this happens after every state change
'@@REMEMBER_PERSISTED',
];

View File

@@ -1,11 +1,12 @@
import type { TypedAddListener, TypedStartListening } from '@reduxjs/toolkit';
import {
UnknownAction,
import type {
ListenerEffect,
addListener,
createListenerMiddleware,
TypedAddListener,
TypedStartListening,
UnknownAction,
} from '@reduxjs/toolkit';
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
import type { AppDispatch, RootState } from 'app/store/store';
import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener';
import { addFirstListImagesListener } from './listeners/addFirstListImagesListener.ts';
import { addAnyEnqueuedListener } from './listeners/anyEnqueued';
@@ -42,13 +43,13 @@ import {
addImageRemovedFromBoardFulfilledListener,
addImageRemovedFromBoardRejectedListener,
} from './listeners/imageRemovedFromBoard';
import { addImagesStarredListener } from './listeners/imagesStarred';
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
import {
addImageUploadedFulfilledListener,
addImageUploadedRejectedListener,
} from './listeners/imageUploaded';
import { addImagesStarredListener } from './listeners/imagesStarred';
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
import { addModelSelectedListener } from './listeners/modelSelected';
import { addModelsLoadedListener } from './listeners/modelsLoaded';
@@ -69,9 +70,9 @@ import { addSocketSubscribedEventListener as addSocketSubscribedListener } from
import { addSocketUnsubscribedEventListener as addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed';
import { addStagingAreaImageSavedListener } from './listeners/stagingAreaImageSaved';
import { addTabChangedListener } from './listeners/tabChanged';
import { addUpdateAllNodesRequestedListener } from './listeners/updateAllNodesRequested';
import { addUpscaleRequestedListener } from './listeners/upscaleRequested';
import { addWorkflowLoadRequestedListener } from './listeners/workflowLoadRequested';
import { addUpdateAllNodesRequestedListener } from './listeners/updateAllNodesRequested';
export const listenerMiddleware = createListenerMiddleware();

View File

@@ -8,6 +8,7 @@ import {
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..';
const matcher = isAnyOf(commitStagingAreaImage, discardStagedImages);

View File

@@ -2,9 +2,10 @@ import { createAction } from '@reduxjs/toolkit';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageCache } from 'services/api/types';
import { getListImagesUrl, imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
import { getListImagesUrl, imagesAdapter } from 'services/api/util';
import { ImageCache } from 'services/api/types';
export const appStarted = createAction('app/appStarted');
@@ -32,7 +33,7 @@ export const addFirstListImagesListener = () => {
if (data.ids.length > 0) {
// Select the first image
const firstImage = imagesAdapter.getSelectors().selectAll(data)[0];
const firstImage = imagesSelectors.selectAll(data)[0];
dispatch(imageSelected(firstImage ?? null));
}
},

View File

@@ -1,4 +1,5 @@
import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..';
export const addAnyEnqueuedListener = () => {

View File

@@ -4,6 +4,7 @@ import {
shouldUseWatermarkerChanged,
} from 'features/system/store/systemSlice';
import { appInfoApi } from 'services/api/endpoints/appInfo';
import { startAppListening } from '..';
export const addAppConfigReceivedListener = () => {

View File

@@ -1,4 +1,5 @@
import { createAction } from '@reduxjs/toolkit';
import { startAppListening } from '..';
export const appStarted = createAction('app/appStarted');

View File

@@ -5,7 +5,8 @@ import { zPydanticValidationError } from 'features/system/store/zodSchemas';
import { t } from 'i18next';
import { truncate, upperFirst } from 'lodash-es';
import { queueApi } from 'services/api/endpoints/queue';
import { TOAST_OPTIONS, theme } from 'theme/theme';
import { theme, TOAST_OPTIONS } from 'theme/theme';
import { startAppListening } from '..';
const { toast } = createStandaloneToast({

View File

@@ -4,6 +4,7 @@ import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addDeleteBoardAndImagesFulfilledListener = () => {
@@ -19,9 +20,15 @@ export const addDeleteBoardAndImagesFulfilledListener = () => {
let wasNodeEditorReset = false;
let wereControlAdaptersReset = false;
const state = getState();
const { generation, canvas, nodes, controlAdapters } = getState();
deleted_images.forEach((image_name) => {
const imageUsage = getImageUsage(state, image_name);
const imageUsage = getImageUsage(
generation,
canvas,
nodes,
controlAdapters,
image_name
);
if (imageUsage.isInitialImage && !wasInitialImageReset) {
dispatch(clearInitialImage());

View File

@@ -9,9 +9,10 @@ import {
IMAGE_CATEGORIES,
} from 'features/gallery/store/types';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
export const addBoardIdSelectedListener = () => {
startAppListening({
matcher: isAnyOf(boardIdSelected, galleryViewChanged),

View File

@@ -1,11 +1,12 @@
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
import { startAppListening } from '..';
import { $logger } from 'app/logging/logger';
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
import { t } from 'i18next';
import { startAppListening } from '..';
export const addCanvasCopiedToClipboardListener = () => {
startAppListening({
actionCreator: canvasCopiedToClipboard,

View File

@@ -1,11 +1,12 @@
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
import { startAppListening } from '..';
import { $logger } from 'app/logging/logger';
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
import { downloadBlob } from 'features/canvas/util/downloadBlob';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { startAppListening } from '..';
export const addCanvasDownloadedAsImageListener = () => {
startAppListening({
actionCreator: canvasDownloadedAsImage,

View File

@@ -1,11 +1,12 @@
import { logger } from 'app/logging/logger';
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
export const addCanvasImageToControlNetListener = () => {
startAppListening({

View File

@@ -2,9 +2,10 @@ import { logger } from 'app/logging/logger';
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasMaskSavedToGalleryListener = () => {
startAppListening({

View File

@@ -5,6 +5,7 @@ import { controlAdapterImageChanged } from 'features/controlAdapters/store/contr
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasMaskToControlNetListener = () => {

View File

@@ -4,9 +4,10 @@ import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasMergedListener = () => {
startAppListening({

View File

@@ -2,9 +2,10 @@ import { logger } from 'app/logging/logger';
import { canvasSavedToGallery } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasSavedToGalleryListener = () => {
startAppListening({

View File

@@ -1,6 +1,6 @@
import { AnyListenerPredicate } from '@reduxjs/toolkit';
import type { AnyListenerPredicate } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
import { RootState } from 'app/store/store';
import type { RootState } from 'app/store/store';
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
import {
controlAdapterAutoConfigToggled,
@@ -10,9 +10,10 @@ import {
controlAdapterProcessortTypeChanged,
selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { startAppListening } from '..';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { startAppListening } from '..';
type AnyControlAdapterParamChangeAction =
| ReturnType<typeof controlAdapterProcessorParamsChanged>
| ReturnType<typeof controlAdapterModelChanged>
@@ -68,10 +69,12 @@ const predicate: AnyListenerPredicate<RootState> = (
return isProcessorSelected && hasControlImage;
};
const DEBOUNCE_MS = 300;
/**
* Listener that automatically processes a ControlNet image when its processor parameters are changed.
*
* The network request is debounced by 1 second.
* The network request is debounced.
*/
export const addControlNetAutoProcessListener = () => {
startAppListening({
@@ -84,7 +87,7 @@ export const addControlNetAutoProcessListener = () => {
cancelActiveListeners();
log.trace('ControlNet auto-process triggered');
// Delay before starting actual work
await delay(300);
await delay(DEBOUNCE_MS);
dispatch(controlAdapterImageProcessed({ id }));
},

View File

@@ -8,14 +8,15 @@ import {
selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { isImageOutput } from 'features/nodes/types/common';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue';
import { BatchConfig, ImageDTO } from 'services/api/types';
import type { BatchConfig, ImageDTO } from 'services/api/types';
import { socketInvocationComplete } from 'services/events/actions';
import { startAppListening } from '..';
import { isImageOutput } from 'features/nodes/types/common';
export const addControlNetImageProcessedListener = () => {
startAppListening({

View File

@@ -14,7 +14,8 @@ import { buildCanvasGraph } from 'features/nodes/util/graph/buildCanvasGraph';
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue';
import { ImageDTO } from 'services/api/types';
import type { ImageDTO } from 'services/api/types';
import { startAppListening } from '..';
/**

View File

@@ -5,6 +5,7 @@ import { buildLinearSDXLImageToImageGraph } from 'features/nodes/util/graph/buil
import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph';
import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph';
import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..';
export const addEnqueueRequestedLinear = () => {

View File

@@ -1,8 +1,9 @@
import { enqueueRequested } from 'app/store/actions';
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
import { buildWorkflow } from 'features/nodes/util/workflow/buildWorkflow';
import { buildWorkflowRight } from 'features/nodes/util/workflow/buildWorkflow';
import { queueApi } from 'services/api/endpoints/queue';
import { BatchConfig } from 'services/api/types';
import type { BatchConfig } from 'services/api/types';
import { startAppListening } from '..';
export const addEnqueueRequestedNodes = () => {
@@ -14,14 +15,16 @@ export const addEnqueueRequestedNodes = () => {
const { nodes, edges } = state.nodes;
const workflow = state.workflow;
const graph = buildNodesGraph(state.nodes);
const builtWorkflow = buildWorkflow({
const builtWorkflow = buildWorkflowRight({
nodes,
edges,
workflow,
});
// embedded workflows don't have an id
delete builtWorkflow.id;
if (builtWorkflow) {
// embedded workflows don't have an id
delete builtWorkflow.id;
}
const batchConfig: BatchConfig = {
batch: {

View File

@@ -1,5 +1,6 @@
import { logger } from 'app/logging/logger';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addImageAddedToBoardFulfilledListener = () => {

View File

@@ -17,7 +17,8 @@ import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { clamp, forEach } from 'lodash-es';
import { api } from 'services/api';
import { imagesApi } from 'services/api/endpoints/images';
import { imagesAdapter } from 'services/api/util';
import { imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
export const addRequestedSingleImageDeletionListener = () => {
@@ -52,9 +53,7 @@ export const addRequestedSingleImageDeletionListener = () => {
const { data } =
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
const cachedImageDTOs = data
? imagesAdapter.getSelectors().selectAll(data)
: [];
const cachedImageDTOs = data ? imagesSelectors.selectAll(data) : [];
const deletedImageIndex = cachedImageDTOs.findIndex(
(i) => i.image_name === image_name
@@ -186,7 +185,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
const newSelectedImageDTO = data
? imagesAdapter.getSelectors().selectAll(data)[0]
? imagesSelectors.selectAll(data)[0]
: undefined;
if (newSelectedImageDTO) {

View File

@@ -6,16 +6,20 @@ import {
controlAdapterImageChanged,
controlAdapterIsEnabledChanged,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import {
import type {
TypesafeDraggableData,
TypesafeDroppableData,
} from 'features/dnd/types';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '../';
import { workflowExposedFieldAdded } from 'features/nodes/store/workflowSlice';
import {
initialImageChanged,
selectOptimalDimension,
} from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '../';
export const dndDropped = createAction<{
overData: TypesafeDroppableData;
@@ -25,7 +29,7 @@ export const dndDropped = createAction<{
export const addImageDroppedListener = () => {
startAppListening({
actionCreator: dndDropped,
effect: async (action, { dispatch }) => {
effect: async (action, { dispatch, getState }) => {
const log = logger('dnd');
const { activeData, overData } = action.payload;
@@ -114,7 +118,12 @@ export const addImageDroppedListener = () => {
activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO
) {
dispatch(setInitialCanvasImage(activeData.payload.imageDTO));
dispatch(
setInitialCanvasImage(
activeData.payload.imageDTO,
selectOptimalDimension(getState())
)
);
return;
}

View File

@@ -1,5 +1,6 @@
import { logger } from 'app/logging/logger';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addImageRemovedFromBoardFulfilledListener = () => {

View File

@@ -4,6 +4,7 @@ import {
imagesToDeleteSelected,
isModalOpenChanged,
} from 'features/deleteImageModal/store/slice';
import { startAppListening } from '..';
export const addImageToDeleteSelectedListener = () => {

View File

@@ -1,4 +1,4 @@
import { UseToastOptions } from '@chakra-ui/react';
import type { UseToastOptions } from '@chakra-ui/react';
import { logger } from 'app/logging/logger';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import {
@@ -6,14 +6,18 @@ import {
controlAdapterIsEnabledChanged,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { initialImageChanged } from 'features/parameters/store/generationSlice';
import {
initialImageChanged,
selectOptimalDimension,
} from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
import { omit } from 'lodash-es';
import { boardsApi } from 'services/api/endpoints/boards';
import { startAppListening } from '..';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addImageUploadedFulfilledListener = () => {
startAppListening({
matcher: imagesApi.endpoints.uploadImage.matchFulfilled,
@@ -75,7 +79,9 @@ export const addImageUploadedFulfilledListener = () => {
}
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
dispatch(setInitialCanvasImage(imageDTO));
dispatch(
setInitialCanvasImage(imageDTO, selectOptimalDimension(state))
);
dispatch(
addToast({
...DEFAULT_UPLOADED_TOAST,

View File

@@ -1,7 +1,8 @@
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { selectionChanged } from 'features/gallery/store/gallerySlice';
import { ImageDTO } from 'services/api/types';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
import { startAppListening } from '..';
export const addImagesStarredListener = () => {
startAppListening({

View File

@@ -1,7 +1,8 @@
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { selectionChanged } from 'features/gallery/store/gallerySlice';
import { ImageDTO } from 'services/api/types';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
import { startAppListening } from '..';
export const addImagesUnstarredListener = () => {
startAppListening({

View File

@@ -3,6 +3,7 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next';
import { startAppListening } from '..';
export const addInitialImageSelectedListener = () => {

View File

@@ -1,5 +1,4 @@
import { logger } from 'app/logging/logger';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import {
controlAdapterIsEnabledChanged,
selectControlAdapterAll,
@@ -8,16 +7,15 @@ import { loraRemoved } from 'features/lora/store/loraSlice';
import { modelSelected } from 'features/parameters/store/actions';
import {
modelChanged,
setHeight,
setWidth,
vaeSelected,
} from 'features/parameters/store/generationSlice';
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next';
import { forEach } from 'lodash-es';
import { startAppListening } from '..';
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
export const addModelSelectedListener = () => {
startAppListening({
@@ -83,22 +81,6 @@ export const addModelSelectedListener = () => {
}
}
// Update Width / Height / Bounding Box Dimensions on Model Change
if (
state.generation.model?.base_model !== newModel.base_model &&
state.ui.shouldAutoChangeDimensions
) {
if (['sdxl', 'sdxl-refiner'].includes(newModel.base_model)) {
dispatch(setWidth(1024));
dispatch(setHeight(1024));
dispatch(setBoundingBoxDimensions({ width: 1024, height: 1024 }));
} else {
dispatch(setWidth(512));
dispatch(setHeight(512));
dispatch(setBoundingBoxDimensions({ width: 512, height: 512 }));
}
}
dispatch(modelChanged(newModel));
},
});

View File

@@ -12,20 +12,17 @@ import {
} from 'features/parameters/store/generationSlice';
import {
zParameterModel,
zParameterSDXLRefinerModel,
zParameterVAEModel,
} from 'features/parameters/types/parameterSchemas';
import {
refinerModelChanged,
setShouldUseSDXLRefiner,
} from 'features/sdxl/store/sdxlSlice';
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
import { forEach, some } from 'lodash-es';
import {
mainModelsAdapter,
mainModelsAdapterSelectors,
modelsApi,
vaeModelsAdapter,
vaeModelsAdapterSelectors,
} from 'services/api/endpoints/models';
import { TypeGuardFor } from 'services/api/types';
import type { TypeGuardFor } from 'services/api/types';
import { startAppListening } from '..';
export const addModelsLoadedListener = () => {
@@ -46,7 +43,7 @@ export const addModelsLoadedListener = () => {
);
const currentModel = getState().generation.model;
const models = mainModelsAdapter.getSelectors().selectAll(action.payload);
const models = mainModelsAdapterSelectors.selectAll(action.payload);
if (models.length === 0) {
// No models loaded at all
@@ -97,12 +94,11 @@ export const addModelsLoadedListener = () => {
);
const currentModel = getState().sdxl.refinerModel;
const models = mainModelsAdapter.getSelectors().selectAll(action.payload);
const models = mainModelsAdapterSelectors.selectAll(action.payload);
if (models.length === 0) {
// No models loaded at all
dispatch(refinerModelChanged(null));
dispatch(setShouldUseSDXLRefiner(false));
return;
}
@@ -115,21 +111,10 @@ export const addModelsLoadedListener = () => {
)
: false;
if (isCurrentModelAvailable) {
if (!isCurrentModelAvailable) {
dispatch(refinerModelChanged(null));
return;
}
const result = zParameterSDXLRefinerModel.safeParse(models[0]);
if (!result.success) {
log.error(
{ error: result.error.format() },
'Failed to parse SDXL Refiner Model'
);
return;
}
dispatch(refinerModelChanged(result.data));
},
});
startAppListening({
@@ -160,9 +145,7 @@ export const addModelsLoadedListener = () => {
return;
}
const firstModel = vaeModelsAdapter
.getSelectors()
.selectAll(action.payload)[0];
const firstModel = vaeModelsAdapterSelectors.selectAll(action.payload)[0];
if (!firstModel) {
// No custom VAEs loaded at all; use the default

View File

@@ -8,9 +8,11 @@ import {
parsingErrorChanged,
promptsChanged,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { setPositivePrompt } from 'features/parameters/store/generationSlice';
import { utilitiesApi } from 'services/api/endpoints/utilities';
import { appSocketConnected } from 'services/events/actions';
import { startAppListening } from '..';
const matcher = isAnyOf(
@@ -28,20 +30,39 @@ export const addDynamicPromptsListener = () => {
action,
{ dispatch, getState, cancelActiveListeners, delay }
) => {
// debounce request
cancelActiveListeners();
await delay(1000);
const state = getState();
const { positivePrompt } = state.generation;
const { maxPrompts } = state.dynamicPrompts;
if (state.config.disabledFeatures.includes('dynamicPrompting')) {
return;
}
const { positivePrompt } = state.generation;
const { maxPrompts } = state.dynamicPrompts;
const cachedPrompts = utilitiesApi.endpoints.dynamicPrompts.select({
prompt: positivePrompt,
max_prompts: maxPrompts,
})(getState()).data;
dispatch(isLoadingChanged(true));
if (cachedPrompts) {
dispatch(promptsChanged(cachedPrompts.prompts));
return;
}
if (!getShouldProcessPrompt(state.generation.positivePrompt)) {
if (state.dynamicPrompts.isLoading) {
dispatch(isLoadingChanged(false));
}
dispatch(promptsChanged([state.generation.positivePrompt]));
return;
}
if (!state.dynamicPrompts.isLoading) {
dispatch(isLoadingChanged(true));
}
// debounce request
await delay(1000);
try {
const req = dispatch(

View File

@@ -1,9 +1,10 @@
import { logger } from 'app/logging/logger';
import { parseify } from 'common/util/serialize';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodeTemplatesSlice';
import { parseSchema } from 'features/nodes/util/schema/parseSchema';
import { size } from 'lodash-es';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { startAppListening } from '..';
export const addReceivedOpenAPISchemaListener = () => {

View File

@@ -1,10 +1,11 @@
import { logger } from 'app/logging/logger';
import { isInitializedChanged } from 'features/system/store/systemSlice';
import { size } from 'lodash-es';
import { api } from 'services/api';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { appSocketConnected, socketConnected } from 'services/events/actions';
import { startAppListening } from '../..';
import { isInitializedChanged } from 'features/system/store/systemSlice';
export const addSocketConnectedEventListener = () => {
startAppListening({
@@ -14,11 +15,11 @@ export const addSocketConnectedEventListener = () => {
log.debug('Connected');
const { nodes, config, system } = getState();
const { nodeTemplates, config, system } = getState();
const { disabledTabs } = config;
if (!size(nodes.nodeTemplates) && !disabledTabs.includes('nodes')) {
if (!size(nodeTemplates.templates) && !disabledTabs.includes('nodes')) {
dispatch(receivedOpenAPISchema());
}

View File

@@ -3,6 +3,7 @@ import {
appSocketDisconnected,
socketDisconnected,
} from 'services/events/actions';
import { startAppListening } from '../..';
export const addSocketDisconnectedEventListener = () => {

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