mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
Compare commits
90 Commits
merged-cha
...
maryhipp/w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
981177e934 | ||
|
|
aad486e59e | ||
|
|
7fde19730e | ||
|
|
13575642d8 | ||
|
|
3f5370b284 | ||
|
|
d048eb5b20 | ||
|
|
dd7031a472 | ||
|
|
4160d5ef26 | ||
|
|
51bdf2fd19 | ||
|
|
6a44697911 | ||
|
|
7a1d0ec228 | ||
|
|
b5928fd411 | ||
|
|
2f345d1976 | ||
|
|
f5d0721fa8 | ||
|
|
c3b36cb61d | ||
|
|
189c430e46 | ||
|
|
b922ee566a | ||
|
|
89da69f647 | ||
|
|
138caa34de | ||
|
|
26c3378ede | ||
|
|
aa134a2db8 | ||
|
|
d0391cb430 | ||
|
|
c955ea9de0 | ||
|
|
fc29a5d439 | ||
|
|
7e9942dbab | ||
|
|
c003967eaa | ||
|
|
b28fcc6be5 | ||
|
|
418cdbabb7 | ||
|
|
18e61e92d9 | ||
|
|
de20711637 | ||
|
|
55e91b97be | ||
|
|
f79bbd2d6e | ||
|
|
e1c2c3905d | ||
|
|
03ac93bfc7 | ||
|
|
89da976949 | ||
|
|
57dafd294d | ||
|
|
e611baa4b4 | ||
|
|
fc448d5b6d | ||
|
|
e59954f956 | ||
|
|
e160cbb1e9 | ||
|
|
86c857b9c2 | ||
|
|
0a13d7d2c7 | ||
|
|
68da5c6d22 | ||
|
|
f82744b95e | ||
|
|
5a67bc68a1 | ||
|
|
61cf4d4c70 | ||
|
|
9d20a2d5a3 | ||
|
|
8b0ac451e3 | ||
|
|
470dbe75a2 | ||
|
|
b7d19b8130 | ||
|
|
3dc13221d8 | ||
|
|
35184dbd86 | ||
|
|
0868fc2558 | ||
|
|
92fb09c4df | ||
|
|
b4cf5496b6 | ||
|
|
a0e68705dd | ||
|
|
7cb49e65bd | ||
|
|
39fedb090b | ||
|
|
f36a691219 | ||
|
|
6a2eb1d2e4 | ||
|
|
13123daa3f | ||
|
|
c859eb865e | ||
|
|
8f5e2cbcc7 | ||
|
|
2aed6e2dba | ||
|
|
52b51a6088 | ||
|
|
52b24e01e2 | ||
|
|
1178fd8bd3 | ||
|
|
a0187cc9df | ||
|
|
2f656cc357 | ||
|
|
71f9ac9985 | ||
|
|
8bbdfc45fa | ||
|
|
3cbb1a7671 | ||
|
|
b74e0de74a | ||
|
|
e7e7793896 | ||
|
|
504bdac14a | ||
|
|
b76d2cd716 | ||
|
|
022b32c724 | ||
|
|
653b820da1 | ||
|
|
68232e642f | ||
|
|
4ba0bf4dcf | ||
|
|
5e4daf4bc6 | ||
|
|
7e0713c869 | ||
|
|
099d516ac0 | ||
|
|
b94f6a4a29 | ||
|
|
4caf63d53d | ||
|
|
6057229ceb | ||
|
|
6a2856e46f | ||
|
|
4dedd63b74 | ||
|
|
db74837eb1 | ||
|
|
892fe62264 |
98
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
98
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -6,10 +6,6 @@ title: '[bug]: '
|
||||
|
||||
labels: ['bug']
|
||||
|
||||
# assignees:
|
||||
# - moderator_bot
|
||||
# - lstein
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -18,10 +14,9 @@ body:
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
label: Is there an existing issue for this problem?
|
||||
description: |
|
||||
Please use the [search function](https://github.com/invoke-ai/InvokeAI/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
irst to see if an issue already exists for the bug you encountered.
|
||||
Please [search](https://github.com/invoke-ai/InvokeAI/issues) first to see if an issue already exists for the problem.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
@@ -33,80 +28,119 @@ body:
|
||||
- type: dropdown
|
||||
id: os_dropdown
|
||||
attributes:
|
||||
label: OS
|
||||
description: Which operating System did you use when the bug occured
|
||||
label: Operating system
|
||||
description: Your computer's operating system.
|
||||
multiple: false
|
||||
options:
|
||||
- 'Linux'
|
||||
- 'Windows'
|
||||
- 'macOS'
|
||||
- 'other'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: gpu_dropdown
|
||||
attributes:
|
||||
label: GPU
|
||||
description: Which kind of Graphic-Adapter is your System using
|
||||
label: GPU vendor
|
||||
description: Your GPU's vendor.
|
||||
multiple: false
|
||||
options:
|
||||
- 'cuda'
|
||||
- 'amd'
|
||||
- 'mps'
|
||||
- 'cpu'
|
||||
- 'Nvidia (CUDA)'
|
||||
- 'AMD (ROCm)'
|
||||
- 'Apple Silicon (MPS)'
|
||||
- 'None (CPU)'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: gpu_model
|
||||
attributes:
|
||||
label: GPU model
|
||||
description: Your GPU's model. If on Apple Silicon, this is your Mac's chip. Leave blank if on CPU.
|
||||
placeholder: ex. RTX 2080 Ti, Mac M1 Pro
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: vram
|
||||
attributes:
|
||||
label: VRAM
|
||||
description: Size of the VRAM if known
|
||||
label: GPU VRAM
|
||||
description: Your GPU's VRAM. If on Apple Silicon, this is your Mac's unified memory. Leave blank if on CPU.
|
||||
placeholder: 8GB
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
- type: input
|
||||
id: version-number
|
||||
attributes:
|
||||
label: What version did you experience this issue on?
|
||||
label: Version number
|
||||
description: |
|
||||
Please share the version of Invoke AI that you experienced the issue on. If this is not the latest version, please update first to confirm the issue still exists. If you are testing main, please include the commit hash instead.
|
||||
placeholder: X.X.X
|
||||
The version of Invoke you have installed. If it is not the latest version, please update and try again to confirm the issue still exists. If you are testing main, please include the commit hash instead.
|
||||
placeholder: ex. 3.6.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser
|
||||
description: Your web browser and version.
|
||||
placeholder: ex. Firefox 123.0b3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: python-deps
|
||||
attributes:
|
||||
label: Python dependencies
|
||||
description: |
|
||||
If the problem occurred during image generation, click the gear icon at the bottom left corner, click "About", click the copy button and then paste here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
label: What happened
|
||||
description: |
|
||||
Briefly describe what happened, what you expected to happen and how to reproduce this bug.
|
||||
placeholder: When using the webinterface and right-clicking on button X instead of the popup-menu there error Y appears
|
||||
Describe what happened. Include any relevant error messages, stack traces and screenshots here.
|
||||
placeholder: I clicked button X and then Y happened.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-you-expected
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem
|
||||
placeholder: this is what the result looked like <screenshot>
|
||||
label: What you expected to happen
|
||||
description: Describe what you expected to happen.
|
||||
placeholder: I expected Z to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: how-to-repro
|
||||
attributes:
|
||||
label: How to reproduce the problem
|
||||
description: List steps to reproduce the problem.
|
||||
placeholder: Start the app, generate an image with these settings, then click button X.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here
|
||||
description: Any other context that might help us to understand the problem.
|
||||
placeholder: Only happens when there is full moon and Friday the 13th on Christmas Eve 🎅🏻
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: contact
|
||||
id: discord-username
|
||||
attributes:
|
||||
label: Contact Details
|
||||
description: __OPTIONAL__ How can we get in touch with you if we need more info (besides this issue)?
|
||||
placeholder: ex. email@example.com, discordname, twitter, ...
|
||||
label: Discord username
|
||||
description: If you are on the Invoke discord and would prefer to be contacted there, please provide your username.
|
||||
placeholder: supercoolusername123
|
||||
validations:
|
||||
required: false
|
||||
|
||||
59
.github/pr_labels.yml
vendored
Normal file
59
.github/pr_labels.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
Root:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '*'
|
||||
|
||||
PythonDeps:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'pyproject.toml'
|
||||
|
||||
Python:
|
||||
- changed-files:
|
||||
- all-globs-to-any-file:
|
||||
- 'invokeai/**'
|
||||
- '!invokeai/frontend/web/**'
|
||||
|
||||
PythonTests:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'tests/**'
|
||||
|
||||
CICD:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: .github/**
|
||||
|
||||
Docker:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: docker/**
|
||||
|
||||
Installer:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: installer/**
|
||||
|
||||
Documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: docs/**
|
||||
|
||||
Invocations:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/app/invocations/**'
|
||||
|
||||
Backend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/backend/**'
|
||||
|
||||
Api:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/app/api/**'
|
||||
|
||||
Services:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/app/services/**'
|
||||
|
||||
FrontendDeps:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*/package.json'
|
||||
- '**/*/pnpm-lock.yaml'
|
||||
|
||||
Frontend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/frontend/web/**'
|
||||
16
.github/workflows/label-pr.yml
vendored
Normal file
16
.github/workflows/label-pr.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
configuration-path: .github/pr_labels.yml
|
||||
@@ -1,76 +0,0 @@
|
||||
# Contributing to the Frontend
|
||||
|
||||
# InvokeAI Web UI
|
||||
|
||||
- [InvokeAI Web UI](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#invokeai-web-ui)
|
||||
- [Stack](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#stack)
|
||||
- [Contributing](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#contributing)
|
||||
- [Dev Environment](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#dev-environment)
|
||||
- [Production builds](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#production-builds)
|
||||
|
||||
The UI is a fairly straightforward Typescript React app, with the Unified Canvas being more complex.
|
||||
|
||||
Code is located in `invokeai/frontend/web/` for review.
|
||||
|
||||
## Stack
|
||||
|
||||
State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). We lean heavily on RTK:
|
||||
|
||||
- `createAsyncThunk` for HTTP requests
|
||||
- `createEntityAdapter` for fetching images and models
|
||||
- `createListenerMiddleware` for workflows
|
||||
|
||||
The API client and associated types are generated from the OpenAPI schema. See API_CLIENT.md.
|
||||
|
||||
Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a simple socket.io redux middleware to help).
|
||||
|
||||
[Chakra-UI](https://github.com/chakra-ui/chakra-ui) & [Mantine](https://github.com/mantinedev/mantine) for components and styling.
|
||||
|
||||
[Konva](https://github.com/konvajs/react-konva) for the canvas, but we are pushing the limits of what is feasible with it (and HTML canvas in general). We plan to rebuild it with [PixiJS](https://github.com/pixijs/pixijs) to take advantage of WebGL's improved raster handling.
|
||||
|
||||
[Vite](https://vitejs.dev/) for bundling.
|
||||
|
||||
Localisation is via [i18next](https://github.com/i18next/react-i18next), but translation happens on our [Weblate](https://hosted.weblate.org/engage/invokeai/) project. Only the English source strings should be changed on this repo.
|
||||
|
||||
## Contributing
|
||||
|
||||
Thanks for your interest in contributing to the InvokeAI Web UI!
|
||||
|
||||
We encourage you to ping @psychedelicious and @blessedcoolant on [Discord](https://discord.gg/ZmtBAhwWhy) if you want to contribute, just to touch base and ensure your work doesn't conflict with anything else going on. The project is very active.
|
||||
|
||||
### Dev Environment
|
||||
|
||||
**Setup**
|
||||
|
||||
1. Install [node](https://nodejs.org/en/download/). You can confirm node is installed with:
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
2. Install [pnpm](https://pnpm.io/) and confirm it is installed by running this:
|
||||
```bash
|
||||
npm install --global pnpm
|
||||
pnpm --version
|
||||
```
|
||||
|
||||
From `invokeai/frontend/web/` run `pnpm install` to get everything set up.
|
||||
|
||||
Start everything in dev mode:
|
||||
1. Ensure your virtual environment is running
|
||||
2. Start the dev server: `pnpm dev`
|
||||
3. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
|
||||
4. Point your browser to the dev server address e.g. [http://localhost:5173/](http://localhost:5173/)
|
||||
|
||||
### VSCode Remote Dev
|
||||
|
||||
We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out. Suggest disabling the IDE's port forwarding feature and doing it manually via SSH:
|
||||
|
||||
`ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host`
|
||||
|
||||
### Production builds
|
||||
|
||||
For a number of technical and logistical reasons, we need to commit UI build artefacts to the repo.
|
||||
|
||||
If you submit a PR, there is a good chance we will ask you to include a separate commit with a build of the app.
|
||||
|
||||
To build for production, run `pnpm build`.
|
||||
@@ -12,7 +12,7 @@ To get started, take a look at our [new contributors checklist](newContributorCh
|
||||
Once you're setup, for more information, you can review the documentation specific to your area of interest:
|
||||
|
||||
* #### [InvokeAI Architecure](../ARCHITECTURE.md)
|
||||
* #### [Frontend Documentation](./contributingToFrontend.md)
|
||||
* #### [Frontend Documentation](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web)
|
||||
* #### [Node Documentation](../INVOCATIONS.md)
|
||||
* #### [Local Development](../LOCAL_DEVELOPMENT.md)
|
||||
|
||||
|
||||
BIN
docs/img/favicon.ico
Normal file
BIN
docs/img/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -117,6 +117,11 @@ Mac and Linux machines, and runs on GPU cards with as little as 4 GB of RAM.
|
||||
|
||||
## :octicons-gift-24: InvokeAI Features
|
||||
|
||||
### Installation
|
||||
- [Automated Installer](installation/010_INSTALL_AUTOMATED.md)
|
||||
- [Manual Installation](installation/020_INSTALL_MANUAL.md)
|
||||
- [Docker Installation](installation/040_INSTALL_DOCKER.md)
|
||||
|
||||
### The InvokeAI Web Interface
|
||||
- [WebUI overview](features/WEB.md)
|
||||
- [WebUI hotkey reference guide](features/WEBUIHOTKEYS.md)
|
||||
|
||||
@@ -18,13 +18,18 @@ either an Nvidia-based card (with CUDA support) or an AMD card (using the ROCm
|
||||
driver).
|
||||
|
||||
|
||||
## **[Automated Installer](010_INSTALL_AUTOMATED.md)**
|
||||
✅ This is the recommended installation method for first-time users.
|
||||
## **[Automated Installer (Recommended)](010_INSTALL_AUTOMATED.md)**
|
||||
✅ This is the recommended installation method for first-time users.
|
||||
|
||||
This is a script that will install all of InvokeAI's essential
|
||||
third party libraries and InvokeAI itself. It includes access to a
|
||||
"developer console" which will help us debug problems with you and
|
||||
give you to access experimental features.
|
||||
third party libraries and InvokeAI itself.
|
||||
|
||||
🖥️ **Download the latest installer .zip file here** : https://github.com/invoke-ai/InvokeAI/releases/latest
|
||||
|
||||
- *Look for the file labelled "InvokeAI-installer-v3.X.X.zip" at the bottom of the page*
|
||||
- If you experience issues, read through the full [installation instructions](010_INSTALL_AUTOMATED.md) to make sure you have met all of the installation requirements. If you need more help, join the [Discord](discord.gg/invoke-ai) or create an issue on [Github](https://github.com/invoke-ai/InvokeAI).
|
||||
|
||||
|
||||
|
||||
## **[Manual Installation](020_INSTALL_MANUAL.md)**
|
||||
This method is recommended for experienced users and developers.
|
||||
|
||||
@@ -25,7 +25,6 @@ To use a community workflow, download the the `.json` node graph file and load i
|
||||
+ [GPT2RandomPromptMaker](#gpt2randompromptmaker)
|
||||
+ [Grid to Gif](#grid-to-gif)
|
||||
+ [Halftone](#halftone)
|
||||
+ [Ideal Size](#ideal-size)
|
||||
+ [Image and Mask Composition Pack](#image-and-mask-composition-pack)
|
||||
+ [Image Dominant Color](#image-dominant-color)
|
||||
+ [Image to Character Art Image Nodes](#image-to-character-art-image-nodes)
|
||||
@@ -196,13 +195,6 @@ CMYK Halftone Output:
|
||||
|
||||
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/c59c578f-db8e-4d66-8c66-2851752d75ea" width="300" />
|
||||
|
||||
--------------------------------
|
||||
### Ideal Size
|
||||
|
||||
**Description:** This node calculates an ideal image size for a first pass of a multi-pass upscaling. The aim is to avoid duplication that results from choosing a size larger than the model is capable of.
|
||||
|
||||
**Node Link:** https://github.com/JPPhoto/ideal-size-node
|
||||
|
||||
--------------------------------
|
||||
### Image and Mask Composition Pack
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ their descriptions.
|
||||
| Integer Math | Perform basic math operations on two integers |
|
||||
| Convert Image Mode | Converts an image to a different mode. |
|
||||
| Crop Image | Crops an image to a specified box. The box can be outside of the image. |
|
||||
| Ideal Size | Calculates an ideal image size for latents for a first pass of a multi-pass upscaling to avoid duplication and other artifacts |
|
||||
| Image Hue Adjustment | Adjusts the Hue of an image. |
|
||||
| Inverse Lerp Image | Inverse linear interpolation of all pixels of an image |
|
||||
| Image Primitive | An image primitive value |
|
||||
|
||||
@@ -13,46 +13,69 @@ We thank them for all of their time and hard work.
|
||||
|
||||
- [Lincoln D. Stein](mailto:lincoln.stein@gmail.com)
|
||||
|
||||
## **Current core team**
|
||||
## **Current Core Team**
|
||||
|
||||
* @lstein (Lincoln Stein) - Co-maintainer
|
||||
* @blessedcoolant - Co-maintainer
|
||||
* @hipsterusername (Kent Keirsey) - Co-maintainer, CEO, Positive Vibes
|
||||
* @psychedelicious (Spencer Mabrito) - Web Team Leader
|
||||
* @Kyle0654 (Kyle Schouviller) - Node Architect and General Backend Wizard
|
||||
* @damian0815 - Attention Systems and Compel Maintainer
|
||||
* @ebr (Eugene Brodsky) - Cloud/DevOps/Sofware engineer; your friendly neighbourhood cluster-autoscaler
|
||||
* @genomancer (Gregg Helt) - Controlnet support
|
||||
* @StAlKeR7779 (Sergey Borisov) - Torch stack, ONNX, model management, optimization
|
||||
* @chainchompa (Jennifer Player) - Web Development & Chain-Chomping
|
||||
* @josh is toast (Josh Corbett) - Web Development
|
||||
* @cheerio (Mary Rogers) - Lead Engineer & Web App Development
|
||||
* @ebr (Eugene Brodsky) - Cloud/DevOps/Sofware engineer; your friendly neighbourhood cluster-autoscaler
|
||||
* @sunija - Standalone version
|
||||
* @genomancer (Gregg Helt) - Controlnet support
|
||||
* @brandon (Brandon Rising) - Platform, Infrastructure, Backend Systems
|
||||
* @ryanjdick (Ryan Dick) - Machine Learning & Training
|
||||
* @millu (Millun Atluri) - Community Manager, Documentation, Node-wrangler
|
||||
* @chainchompa (Jennifer Player) - Web Development & Chain-Chomping
|
||||
* @JPPhoto - Core image generation nodes
|
||||
* @dunkeroni - Image generation backend
|
||||
* @SkunkWorxDark - Image generation backend
|
||||
* @keturn (Kevin Turner) - Diffusers
|
||||
* @millu (Millun Atluri) - Community Wizard, Documentation, Node-wrangler,
|
||||
* @glimmerleaf (Devon Hopkins) - Community Wizard
|
||||
* @gogurt enjoyer - Discord moderator and end user support
|
||||
* @whosawhatsis - Discord moderator and end user support
|
||||
* @dwinrger - Discord moderator and end user support
|
||||
* @526christian - Discord moderator and end user support
|
||||
* @harvester62 - Discord moderator and end user support
|
||||
|
||||
|
||||
## **Honored Team Alumni**
|
||||
|
||||
* @StAlKeR7779 (Sergey Borisov) - Torch stack, ONNX, model management, optimization
|
||||
* @damian0815 - Attention Systems and Compel Maintainer
|
||||
* @netsvetaev (Artur) - Localization support
|
||||
* @Kyle0654 (Kyle Schouviller) - Node Architect and General Backend Wizard
|
||||
* @tildebyte - Installation and configuration
|
||||
* @mauwii (Matthias Wilde) - Installation, release, continuous integration
|
||||
|
||||
|
||||
## **Full List of Contributors by Commit Name**
|
||||
|
||||
- 이승석
|
||||
- AbdBarho
|
||||
- ablattmann
|
||||
- AdamOStark
|
||||
- Adam Rice
|
||||
- Airton Silva
|
||||
- Aldo Hoeben
|
||||
- Alexander Eichhorn
|
||||
- Alexandre D. Roberge
|
||||
- Alexandre Macabies
|
||||
- Alfie John
|
||||
- Andreas Rozek
|
||||
- Andre LaBranche
|
||||
- Andy Bearman
|
||||
- Andy Luhrs
|
||||
- Andy Pilate
|
||||
- Anonymous
|
||||
- Anthony Monthe
|
||||
- Any-Winter-4079
|
||||
- apolinario
|
||||
- Ar7ific1al
|
||||
- ArDiouscuros
|
||||
- Armando C. Santisbon
|
||||
- Arnold Cordewiner
|
||||
- Arthur Holstvoogd
|
||||
- artmen1516
|
||||
- Artur
|
||||
@@ -64,13 +87,16 @@ We thank them for all of their time and hard work.
|
||||
- blhook
|
||||
- BlueAmulet
|
||||
- Bouncyknighter
|
||||
- Brandon
|
||||
- Brandon Rising
|
||||
- Brent Ozar
|
||||
- Brian Racer
|
||||
- bsilvereagle
|
||||
- c67e708d
|
||||
- camenduru
|
||||
- CapableWeb
|
||||
- Carson Katri
|
||||
- chainchompa
|
||||
- Chloe
|
||||
- Chris Dawson
|
||||
- Chris Hayes
|
||||
@@ -86,30 +112,45 @@ We thank them for all of their time and hard work.
|
||||
- cpacker
|
||||
- Cragin Godley
|
||||
- creachec
|
||||
- CrypticWit
|
||||
- d8ahazard
|
||||
- damian
|
||||
- damian0815
|
||||
- Damian at mba
|
||||
- Damian Stewart
|
||||
- Daniel Manzke
|
||||
- Danny Beer
|
||||
- Dan Sully
|
||||
- Darren Ringer
|
||||
- David Burnett
|
||||
- David Ford
|
||||
- David Regla
|
||||
- David Sisco
|
||||
- David Wager
|
||||
- Daya Adianto
|
||||
- db3000
|
||||
- DekitaRPG
|
||||
- Denis Olshin
|
||||
- Dennis
|
||||
- dependabot[bot]
|
||||
- Dmitry Parnas
|
||||
- Dobrynia100
|
||||
- Dominic Letz
|
||||
- DrGunnarMallon
|
||||
- Drun555
|
||||
- dunkeroni
|
||||
- Edward Johan
|
||||
- elliotsayes
|
||||
- Elrik
|
||||
- ElrikUnderlake
|
||||
- Eric Khun
|
||||
- Eric Wolf
|
||||
- Eugene
|
||||
- Eugene Brodsky
|
||||
- ExperimentalCyborg
|
||||
- Fabian Bahl
|
||||
- Fabio 'MrWHO' Torchetti
|
||||
- Fattire
|
||||
- fattire
|
||||
- Felipe Nogueira
|
||||
- Félix Sanz
|
||||
@@ -118,8 +159,12 @@ We thank them for all of their time and hard work.
|
||||
- gabrielrotbart
|
||||
- gallegonovato
|
||||
- Gérald LONLAS
|
||||
- Gille
|
||||
- GitHub Actions Bot
|
||||
- glibesyck
|
||||
- gogurtenjoyer
|
||||
- Gohsuke Shimada
|
||||
- greatwolf
|
||||
- greentext2
|
||||
- Gregg Helt
|
||||
- H4rk
|
||||
@@ -131,6 +176,7 @@ We thank them for all of their time and hard work.
|
||||
- Hosted Weblate
|
||||
- Iman Karim
|
||||
- ismail ihsan bülbül
|
||||
- ItzAttila
|
||||
- Ivan Efimov
|
||||
- jakehl
|
||||
- Jakub Kolčář
|
||||
@@ -141,6 +187,7 @@ We thank them for all of their time and hard work.
|
||||
- Jason Toffaletti
|
||||
- Jaulustus
|
||||
- Jeff Mahoney
|
||||
- Jennifer Player
|
||||
- jeremy
|
||||
- Jeremy Clark
|
||||
- JigenD
|
||||
@@ -148,19 +195,26 @@ We thank them for all of their time and hard work.
|
||||
- Johan Roxendal
|
||||
- Johnathon Selstad
|
||||
- Jonathan
|
||||
- Jordan Hewitt
|
||||
- Joseph Dries III
|
||||
- Josh Corbett
|
||||
- JPPhoto
|
||||
- jspraul
|
||||
- junzi
|
||||
- Justin Wong
|
||||
- Juuso V
|
||||
- Kaspar Emanuel
|
||||
- Katsuyuki-Karasawa
|
||||
- Keerigan45
|
||||
- Kent Keirsey
|
||||
- Kevin Brack
|
||||
- Kevin Coakley
|
||||
- Kevin Gibbons
|
||||
- Kevin Schaul
|
||||
- Kevin Turner
|
||||
- Kieran Klaassen
|
||||
- krummrey
|
||||
- Kyle
|
||||
- Kyle Lacy
|
||||
- Kyle Schouviller
|
||||
- Lawrence Norton
|
||||
@@ -171,10 +225,15 @@ We thank them for all of their time and hard work.
|
||||
- Lynne Whitehorn
|
||||
- majick
|
||||
- Marco Labarile
|
||||
- Marta Nahorniuk
|
||||
- Martin Kristiansen
|
||||
- Mary Hipp
|
||||
- maryhipp
|
||||
- Mary Hipp Rogers
|
||||
- mastercaster
|
||||
- mastercaster9000
|
||||
- Matthias Wild
|
||||
- mauwii
|
||||
- michaelk71
|
||||
- mickr777
|
||||
- Mihai
|
||||
@@ -182,11 +241,15 @@ We thank them for all of their time and hard work.
|
||||
- Mikhail Tishin
|
||||
- Millun Atluri
|
||||
- Minjune Song
|
||||
- Mitchell Allain
|
||||
- mitien
|
||||
- mofuzz
|
||||
- Muhammad Usama
|
||||
- Name
|
||||
- _nderscore
|
||||
- Neil Wang
|
||||
- nekowaiz
|
||||
- nemuruibai
|
||||
- Netzer R
|
||||
- Nicholas Koh
|
||||
- Nicholas Körfer
|
||||
@@ -197,9 +260,11 @@ We thank them for all of their time and hard work.
|
||||
- ofirkris
|
||||
- Olivier Louvignes
|
||||
- owenvincent
|
||||
- pand4z31
|
||||
- Patrick Esser
|
||||
- Patrick Tien
|
||||
- Patrick von Platen
|
||||
- Paul Curry
|
||||
- Paul Sajna
|
||||
- pejotr
|
||||
- Peter Baylies
|
||||
@@ -207,6 +272,7 @@ We thank them for all of their time and hard work.
|
||||
- plucked
|
||||
- prixt
|
||||
- psychedelicious
|
||||
- psychedelicious@windows
|
||||
- Rainer Bernhardt
|
||||
- Riccardo Giovanetti
|
||||
- Rich Jones
|
||||
@@ -215,17 +281,22 @@ We thank them for all of their time and hard work.
|
||||
- Robert Bolender
|
||||
- Robin Rombach
|
||||
- Rohan Barar
|
||||
- rohinish404
|
||||
- Rohinish
|
||||
- rpagliuca
|
||||
- rromb
|
||||
- Rupesh Sreeraman
|
||||
- Ryan
|
||||
- Ryan Cao
|
||||
- Ryan Dick
|
||||
- Saifeddine
|
||||
- Saifeddine ALOUI
|
||||
- Sam
|
||||
- SammCheese
|
||||
- Sam McLeod
|
||||
- Sammy
|
||||
- sammyf
|
||||
- Samuel Husso
|
||||
- Saurav Maheshkar
|
||||
- Scott Lahteine
|
||||
- Sean McLellan
|
||||
- Sebastian Aigner
|
||||
@@ -233,16 +304,21 @@ We thank them for all of their time and hard work.
|
||||
- Sergey Krashevich
|
||||
- Shapor Naghibzadeh
|
||||
- Shawn Zhong
|
||||
- Simona Liliac
|
||||
- Simon Vans-Colina
|
||||
- skunkworxdark
|
||||
- slashtechno
|
||||
- SoheilRezaei
|
||||
- Song, Pengcheng
|
||||
- spezialspezial
|
||||
- ssantos
|
||||
- StAlKeR7779
|
||||
- Stefan Tobler
|
||||
- Stephan Koglin-Fischer
|
||||
- SteveCaruso
|
||||
- Steve Martinelli
|
||||
- Steven Frank
|
||||
- Surisen
|
||||
- System X - Files
|
||||
- Taylor Kems
|
||||
- techicode
|
||||
@@ -261,26 +337,34 @@ We thank them for all of their time and hard work.
|
||||
- tyler
|
||||
- unknown
|
||||
- user1
|
||||
- vedant-3010
|
||||
- Vedant Madane
|
||||
- veprogames
|
||||
- wa.code
|
||||
- wfng92
|
||||
- whjms
|
||||
- whosawhatsis
|
||||
- Will
|
||||
- William Becher
|
||||
- William Chong
|
||||
- Wilson E. Alvarez
|
||||
- woweenie
|
||||
- Wubbbi
|
||||
- xra
|
||||
- Yeung Yiu Hung
|
||||
- ymgenesis
|
||||
- Yorzaren
|
||||
- Yosuke Shinya
|
||||
- yun saki
|
||||
- ZachNagengast
|
||||
- Zadagu
|
||||
- zeptofine
|
||||
- Zerdoumi
|
||||
- Васянатор
|
||||
- 冯不游
|
||||
- 唐澤 克幸
|
||||
|
||||
## **Original CompVis Authors**
|
||||
## **Original CompVis (Stable Diffusion) Authors**
|
||||
|
||||
- [Robin Rombach](https://github.com/rromb)
|
||||
- [Patrick von Platen](https://github.com/patrickvonplaten)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@ 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
|
||||
from invokeai.backend.image_util.depth_anything import DepthAnythingDetector
|
||||
|
||||
from ...backend.model_management import BaseModelType
|
||||
from .baseinvocation import (
|
||||
@@ -602,3 +603,33 @@ class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
|
||||
color_map = cv2.resize(color_map, (width, height), interpolation=cv2.INTER_NEAREST)
|
||||
color_map = Image.fromarray(color_map)
|
||||
return color_map
|
||||
|
||||
|
||||
DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
|
||||
|
||||
|
||||
@invocation(
|
||||
"depth_anything_image_processor",
|
||||
title="Depth Anything Processor",
|
||||
tags=["controlnet", "depth", "depth anything"],
|
||||
category="controlnet",
|
||||
version="1.0.0",
|
||||
)
|
||||
class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Generates a depth map based on the Depth Anything algorithm"""
|
||||
|
||||
model_size: DEPTH_ANYTHING_MODEL_SIZES = InputField(
|
||||
default="small", description="The size of the depth model to use"
|
||||
)
|
||||
resolution: int = InputField(default=512, ge=64, multiple_of=64, description=FieldDescriptions.image_res)
|
||||
offload: bool = InputField(default=False)
|
||||
|
||||
def run_processor(self, image):
|
||||
depth_anything_detector = DepthAnythingDetector()
|
||||
depth_anything_detector.load_model(model_size=self.model_size)
|
||||
|
||||
if image.mode == "RGBA":
|
||||
image = image.convert("RGB")
|
||||
|
||||
processed_image = depth_anything_detector(image=image, resolution=self.resolution, offload=self.offload)
|
||||
return processed_image
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
|
||||
|
||||
import math
|
||||
from contextlib import ExitStack
|
||||
from functools import singledispatchmethod
|
||||
from typing import List, Literal, Optional, Union
|
||||
@@ -1228,3 +1229,57 @@ class CropLatentsCoreInvocation(BaseInvocation):
|
||||
context.services.latents.save(name, cropped_latents)
|
||||
|
||||
return build_latents_output(latents_name=name, latents=cropped_latents)
|
||||
|
||||
|
||||
@invocation_output("ideal_size_output")
|
||||
class IdealSizeOutput(BaseInvocationOutput):
|
||||
"""Base class for invocations that output an image"""
|
||||
|
||||
width: int = OutputField(description="The ideal width of the image (in pixels)")
|
||||
height: int = OutputField(description="The ideal height of the image (in pixels)")
|
||||
|
||||
|
||||
@invocation(
|
||||
"ideal_size",
|
||||
title="Ideal Size",
|
||||
tags=["latents", "math", "ideal_size"],
|
||||
version="1.0.2",
|
||||
)
|
||||
class IdealSizeInvocation(BaseInvocation):
|
||||
"""Calculates the ideal size for generation to avoid duplication"""
|
||||
|
||||
width: int = InputField(default=1024, description="Final image width")
|
||||
height: int = InputField(default=576, description="Final image height")
|
||||
unet: UNetField = InputField(default=None, description=FieldDescriptions.unet)
|
||||
multiplier: float = InputField(
|
||||
default=1.0,
|
||||
description="Amount to multiply the model's dimensions by when calculating the ideal size (may result in initial generation artifacts if too large)",
|
||||
)
|
||||
|
||||
def trim_to_multiple_of(self, *args, multiple_of=LATENT_SCALE_FACTOR):
|
||||
return tuple((x - x % multiple_of) for x in args)
|
||||
|
||||
def invoke(self, context: InvocationContext) -> IdealSizeOutput:
|
||||
aspect = self.width / self.height
|
||||
dimension = 512
|
||||
if self.unet.unet.base_model == BaseModelType.StableDiffusion2:
|
||||
dimension = 768
|
||||
elif self.unet.unet.base_model == BaseModelType.StableDiffusionXL:
|
||||
dimension = 1024
|
||||
dimension = dimension * self.multiplier
|
||||
min_dimension = math.floor(dimension * 0.5)
|
||||
model_area = dimension * dimension # hardcoded for now since all models are trained on square images
|
||||
|
||||
if aspect > 1.0:
|
||||
init_height = max(min_dimension, math.sqrt(model_area / aspect))
|
||||
init_width = init_height * aspect
|
||||
else:
|
||||
init_width = max(min_dimension, math.sqrt(model_area * aspect))
|
||||
init_height = init_width / aspect
|
||||
|
||||
scaled_width, scaled_height = self.trim_to_multiple_of(
|
||||
math.floor(init_width),
|
||||
math.floor(init_height),
|
||||
)
|
||||
|
||||
return IdealSizeOutput(width=scaled_width, height=scaled_height)
|
||||
|
||||
@@ -31,6 +31,7 @@ class WorkflowRecordOrderBy(str, Enum, metaclass=MetaEnum):
|
||||
class WorkflowCategory(str, Enum, metaclass=MetaEnum):
|
||||
User = "user"
|
||||
Default = "default"
|
||||
Project = "project"
|
||||
|
||||
|
||||
class WorkflowMeta(BaseModel):
|
||||
|
||||
109
invokeai/backend/image_util/depth_anything/__init__.py
Normal file
109
invokeai/backend/image_util/depth_anything/__init__.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import pathlib
|
||||
from typing import Literal, Union
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import repeat
|
||||
from PIL import Image
|
||||
from torchvision.transforms import Compose
|
||||
|
||||
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
||||
from invokeai.backend.image_util.depth_anything.model.dpt import DPT_DINOv2
|
||||
from invokeai.backend.image_util.depth_anything.utilities.util import NormalizeImage, PrepareForNet, Resize
|
||||
from invokeai.backend.util.devices import choose_torch_device
|
||||
from invokeai.backend.util.util import download_with_progress_bar
|
||||
|
||||
config = InvokeAIAppConfig.get_config()
|
||||
|
||||
DEPTH_ANYTHING_MODELS = {
|
||||
"large": {
|
||||
"url": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitl14.pth?download=true",
|
||||
"local": "any/annotators/depth_anything/depth_anything_vitl14.pth",
|
||||
},
|
||||
"base": {
|
||||
"url": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitb14.pth?download=true",
|
||||
"local": "any/annotators/depth_anything/depth_anything_vitb14.pth",
|
||||
},
|
||||
"small": {
|
||||
"url": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vits14.pth?download=true",
|
||||
"local": "any/annotators/depth_anything/depth_anything_vits14.pth",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
transform = Compose(
|
||||
[
|
||||
Resize(
|
||||
width=518,
|
||||
height=518,
|
||||
resize_target=False,
|
||||
keep_aspect_ratio=True,
|
||||
ensure_multiple_of=14,
|
||||
resize_method="lower_bound",
|
||||
image_interpolation_method=cv2.INTER_CUBIC,
|
||||
),
|
||||
NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
||||
PrepareForNet(),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class DepthAnythingDetector:
|
||||
def __init__(self) -> None:
|
||||
self.model = None
|
||||
self.model_size: Union[Literal["large", "base", "small"], None] = None
|
||||
|
||||
def load_model(self, model_size=Literal["large", "base", "small"]):
|
||||
DEPTH_ANYTHING_MODEL_PATH = pathlib.Path(config.models_path / DEPTH_ANYTHING_MODELS[model_size]["local"])
|
||||
if not DEPTH_ANYTHING_MODEL_PATH.exists():
|
||||
download_with_progress_bar(DEPTH_ANYTHING_MODELS[model_size]["url"], DEPTH_ANYTHING_MODEL_PATH)
|
||||
|
||||
if not self.model or model_size != self.model_size:
|
||||
del self.model
|
||||
self.model_size = model_size
|
||||
|
||||
match self.model_size:
|
||||
case "small":
|
||||
self.model = DPT_DINOv2(encoder="vits", features=64, out_channels=[48, 96, 192, 384])
|
||||
case "base":
|
||||
self.model = DPT_DINOv2(encoder="vitb", features=128, out_channels=[96, 192, 384, 768])
|
||||
case "large":
|
||||
self.model = DPT_DINOv2(encoder="vitl", features=256, out_channels=[256, 512, 1024, 1024])
|
||||
case _:
|
||||
raise TypeError("Not a supported model")
|
||||
|
||||
self.model.load_state_dict(torch.load(DEPTH_ANYTHING_MODEL_PATH.as_posix(), map_location="cpu"))
|
||||
self.model.eval()
|
||||
|
||||
self.model.to(choose_torch_device())
|
||||
return self.model
|
||||
|
||||
def to(self, device):
|
||||
self.model.to(device)
|
||||
return self
|
||||
|
||||
def __call__(self, image, resolution=512, offload=False):
|
||||
image = np.array(image, dtype=np.uint8)
|
||||
image = image[:, :, ::-1] / 255.0
|
||||
|
||||
image_height, image_width = image.shape[:2]
|
||||
image = transform({"image": image})["image"]
|
||||
image = torch.from_numpy(image).unsqueeze(0).to(choose_torch_device())
|
||||
|
||||
with torch.no_grad():
|
||||
depth = self.model(image)
|
||||
depth = F.interpolate(depth[None], (image_height, image_width), mode="bilinear", align_corners=False)[0, 0]
|
||||
depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
|
||||
|
||||
depth_map = repeat(depth, "h w -> h w 3").cpu().numpy().astype(np.uint8)
|
||||
depth_map = Image.fromarray(depth_map)
|
||||
|
||||
new_height = int(image_height * (resolution / image_width))
|
||||
depth_map = depth_map.resize((resolution, new_height))
|
||||
|
||||
if offload:
|
||||
del self.model
|
||||
|
||||
return depth_map
|
||||
145
invokeai/backend/image_util/depth_anything/model/blocks.py
Normal file
145
invokeai/backend/image_util/depth_anything/model/blocks.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def _make_scratch(in_shape, out_shape, groups=1, expand=False):
|
||||
scratch = nn.Module()
|
||||
|
||||
out_shape1 = out_shape
|
||||
out_shape2 = out_shape
|
||||
out_shape3 = out_shape
|
||||
if len(in_shape) >= 4:
|
||||
out_shape4 = out_shape
|
||||
|
||||
if expand:
|
||||
out_shape1 = out_shape
|
||||
out_shape2 = out_shape * 2
|
||||
out_shape3 = out_shape * 4
|
||||
if len(in_shape) >= 4:
|
||||
out_shape4 = out_shape * 8
|
||||
|
||||
scratch.layer1_rn = nn.Conv2d(
|
||||
in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
scratch.layer2_rn = nn.Conv2d(
|
||||
in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
scratch.layer3_rn = nn.Conv2d(
|
||||
in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
if len(in_shape) >= 4:
|
||||
scratch.layer4_rn = nn.Conv2d(
|
||||
in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
|
||||
return scratch
|
||||
|
||||
|
||||
class ResidualConvUnit(nn.Module):
|
||||
"""Residual convolution module."""
|
||||
|
||||
def __init__(self, features, activation, bn):
|
||||
"""Init.
|
||||
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.bn = bn
|
||||
|
||||
self.groups = 1
|
||||
|
||||
self.conv1 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
|
||||
|
||||
self.conv2 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
|
||||
|
||||
if self.bn:
|
||||
self.bn1 = nn.BatchNorm2d(features)
|
||||
self.bn2 = nn.BatchNorm2d(features)
|
||||
|
||||
self.activation = activation
|
||||
|
||||
self.skip_add = nn.quantized.FloatFunctional()
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward pass.
|
||||
|
||||
Args:
|
||||
x (tensor): input
|
||||
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
|
||||
out = self.activation(x)
|
||||
out = self.conv1(out)
|
||||
if self.bn:
|
||||
out = self.bn1(out)
|
||||
|
||||
out = self.activation(out)
|
||||
out = self.conv2(out)
|
||||
if self.bn:
|
||||
out = self.bn2(out)
|
||||
|
||||
if self.groups > 1:
|
||||
out = self.conv_merge(out)
|
||||
|
||||
return self.skip_add.add(out, x)
|
||||
|
||||
|
||||
class FeatureFusionBlock(nn.Module):
|
||||
"""Feature fusion block."""
|
||||
|
||||
def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True, size=None):
|
||||
"""Init.
|
||||
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super(FeatureFusionBlock, self).__init__()
|
||||
|
||||
self.deconv = deconv
|
||||
self.align_corners = align_corners
|
||||
|
||||
self.groups = 1
|
||||
|
||||
self.expand = expand
|
||||
out_features = features
|
||||
if self.expand:
|
||||
out_features = features // 2
|
||||
|
||||
self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1)
|
||||
|
||||
self.resConfUnit1 = ResidualConvUnit(features, activation, bn)
|
||||
self.resConfUnit2 = ResidualConvUnit(features, activation, bn)
|
||||
|
||||
self.skip_add = nn.quantized.FloatFunctional()
|
||||
|
||||
self.size = size
|
||||
|
||||
def forward(self, *xs, size=None):
|
||||
"""Forward pass.
|
||||
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
output = xs[0]
|
||||
|
||||
if len(xs) == 2:
|
||||
res = self.resConfUnit1(xs[1])
|
||||
output = self.skip_add.add(output, res)
|
||||
|
||||
output = self.resConfUnit2(output)
|
||||
|
||||
if (size is None) and (self.size is None):
|
||||
modifier = {"scale_factor": 2}
|
||||
elif size is None:
|
||||
modifier = {"size": self.size}
|
||||
else:
|
||||
modifier = {"size": size}
|
||||
|
||||
output = nn.functional.interpolate(output, **modifier, mode="bilinear", align_corners=self.align_corners)
|
||||
|
||||
output = self.out_conv(output)
|
||||
|
||||
return output
|
||||
183
invokeai/backend/image_util/depth_anything/model/dpt.py
Normal file
183
invokeai/backend/image_util/depth_anything/model/dpt.py
Normal file
@@ -0,0 +1,183 @@
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from .blocks import FeatureFusionBlock, _make_scratch
|
||||
|
||||
torchhub_path = Path(__file__).parent.parent / "torchhub"
|
||||
|
||||
|
||||
def _make_fusion_block(features, use_bn, size=None):
|
||||
return FeatureFusionBlock(
|
||||
features,
|
||||
nn.ReLU(False),
|
||||
deconv=False,
|
||||
bn=use_bn,
|
||||
expand=False,
|
||||
align_corners=True,
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
class DPTHead(nn.Module):
|
||||
def __init__(self, nclass, in_channels, features, out_channels, use_bn=False, use_clstoken=False):
|
||||
super(DPTHead, self).__init__()
|
||||
|
||||
self.nclass = nclass
|
||||
self.use_clstoken = use_clstoken
|
||||
|
||||
self.projects = nn.ModuleList(
|
||||
[
|
||||
nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channel,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
)
|
||||
for out_channel in out_channels
|
||||
]
|
||||
)
|
||||
|
||||
self.resize_layers = nn.ModuleList(
|
||||
[
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=out_channels[0], out_channels=out_channels[0], kernel_size=4, stride=4, padding=0
|
||||
),
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=out_channels[1], out_channels=out_channels[1], kernel_size=2, stride=2, padding=0
|
||||
),
|
||||
nn.Identity(),
|
||||
nn.Conv2d(
|
||||
in_channels=out_channels[3], out_channels=out_channels[3], kernel_size=3, stride=2, padding=1
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
if use_clstoken:
|
||||
self.readout_projects = nn.ModuleList()
|
||||
for _ in range(len(self.projects)):
|
||||
self.readout_projects.append(nn.Sequential(nn.Linear(2 * in_channels, in_channels), nn.GELU()))
|
||||
|
||||
self.scratch = _make_scratch(
|
||||
out_channels,
|
||||
features,
|
||||
groups=1,
|
||||
expand=False,
|
||||
)
|
||||
|
||||
self.scratch.stem_transpose = None
|
||||
|
||||
self.scratch.refinenet1 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet2 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet3 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet4 = _make_fusion_block(features, use_bn)
|
||||
|
||||
head_features_1 = features
|
||||
head_features_2 = 32
|
||||
|
||||
if nclass > 1:
|
||||
self.scratch.output_conv = nn.Sequential(
|
||||
nn.Conv2d(head_features_1, head_features_1, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU(True),
|
||||
nn.Conv2d(head_features_1, nclass, kernel_size=1, stride=1, padding=0),
|
||||
)
|
||||
else:
|
||||
self.scratch.output_conv1 = nn.Conv2d(
|
||||
head_features_1, head_features_1 // 2, kernel_size=3, stride=1, padding=1
|
||||
)
|
||||
|
||||
self.scratch.output_conv2 = nn.Sequential(
|
||||
nn.Conv2d(head_features_1 // 2, head_features_2, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU(True),
|
||||
nn.Conv2d(head_features_2, 1, kernel_size=1, stride=1, padding=0),
|
||||
nn.ReLU(True),
|
||||
nn.Identity(),
|
||||
)
|
||||
|
||||
def forward(self, out_features, patch_h, patch_w):
|
||||
out = []
|
||||
for i, x in enumerate(out_features):
|
||||
if self.use_clstoken:
|
||||
x, cls_token = x[0], x[1]
|
||||
readout = cls_token.unsqueeze(1).expand_as(x)
|
||||
x = self.readout_projects[i](torch.cat((x, readout), -1))
|
||||
else:
|
||||
x = x[0]
|
||||
|
||||
x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
|
||||
|
||||
x = self.projects[i](x)
|
||||
x = self.resize_layers[i](x)
|
||||
|
||||
out.append(x)
|
||||
|
||||
layer_1, layer_2, layer_3, layer_4 = out
|
||||
|
||||
layer_1_rn = self.scratch.layer1_rn(layer_1)
|
||||
layer_2_rn = self.scratch.layer2_rn(layer_2)
|
||||
layer_3_rn = self.scratch.layer3_rn(layer_3)
|
||||
layer_4_rn = self.scratch.layer4_rn(layer_4)
|
||||
|
||||
path_4 = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:])
|
||||
path_3 = self.scratch.refinenet3(path_4, layer_3_rn, size=layer_2_rn.shape[2:])
|
||||
path_2 = self.scratch.refinenet2(path_3, layer_2_rn, size=layer_1_rn.shape[2:])
|
||||
path_1 = self.scratch.refinenet1(path_2, layer_1_rn)
|
||||
|
||||
out = self.scratch.output_conv1(path_1)
|
||||
out = F.interpolate(out, (int(patch_h * 14), int(patch_w * 14)), mode="bilinear", align_corners=True)
|
||||
out = self.scratch.output_conv2(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class DPT_DINOv2(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
features,
|
||||
out_channels,
|
||||
encoder="vitl",
|
||||
use_bn=False,
|
||||
use_clstoken=False,
|
||||
):
|
||||
super(DPT_DINOv2, self).__init__()
|
||||
|
||||
assert encoder in ["vits", "vitb", "vitl"]
|
||||
|
||||
# # in case the Internet connection is not stable, please load the DINOv2 locally
|
||||
# if use_local:
|
||||
# self.pretrained = torch.hub.load(
|
||||
# torchhub_path / "facebookresearch_dinov2_main",
|
||||
# "dinov2_{:}14".format(encoder),
|
||||
# source="local",
|
||||
# pretrained=False,
|
||||
# )
|
||||
# else:
|
||||
# self.pretrained = torch.hub.load(
|
||||
# "facebookresearch/dinov2",
|
||||
# "dinov2_{:}14".format(encoder),
|
||||
# )
|
||||
|
||||
self.pretrained = torch.hub.load(
|
||||
"facebookresearch/dinov2",
|
||||
"dinov2_{:}14".format(encoder),
|
||||
)
|
||||
|
||||
dim = self.pretrained.blocks[0].attn.qkv.in_features
|
||||
|
||||
self.depth_head = DPTHead(1, dim, features, out_channels=out_channels, use_bn=use_bn, use_clstoken=use_clstoken)
|
||||
|
||||
def forward(self, x):
|
||||
h, w = x.shape[-2:]
|
||||
|
||||
features = self.pretrained.get_intermediate_layers(x, 4, return_class_token=True)
|
||||
|
||||
patch_h, patch_w = h // 14, w // 14
|
||||
|
||||
depth = self.depth_head(features, patch_h, patch_w)
|
||||
depth = F.interpolate(depth, size=(h, w), mode="bilinear", align_corners=True)
|
||||
depth = F.relu(depth)
|
||||
|
||||
return depth.squeeze(1)
|
||||
227
invokeai/backend/image_util/depth_anything/utilities/util.py
Normal file
227
invokeai/backend/image_util/depth_anything/utilities/util.py
Normal file
@@ -0,0 +1,227 @@
|
||||
import math
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA):
|
||||
"""Rezise the sample to ensure the given size. Keeps aspect ratio.
|
||||
|
||||
Args:
|
||||
sample (dict): sample
|
||||
size (tuple): image size
|
||||
|
||||
Returns:
|
||||
tuple: new size
|
||||
"""
|
||||
shape = list(sample["disparity"].shape)
|
||||
|
||||
if shape[0] >= size[0] and shape[1] >= size[1]:
|
||||
return sample
|
||||
|
||||
scale = [0, 0]
|
||||
scale[0] = size[0] / shape[0]
|
||||
scale[1] = size[1] / shape[1]
|
||||
|
||||
scale = max(scale)
|
||||
|
||||
shape[0] = math.ceil(scale * shape[0])
|
||||
shape[1] = math.ceil(scale * shape[1])
|
||||
|
||||
# resize
|
||||
sample["image"] = cv2.resize(sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method)
|
||||
|
||||
sample["disparity"] = cv2.resize(sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST)
|
||||
sample["mask"] = cv2.resize(
|
||||
sample["mask"].astype(np.float32),
|
||||
tuple(shape[::-1]),
|
||||
interpolation=cv2.INTER_NEAREST,
|
||||
)
|
||||
sample["mask"] = sample["mask"].astype(bool)
|
||||
|
||||
return tuple(shape)
|
||||
|
||||
|
||||
class Resize(object):
|
||||
"""Resize sample to given size (width, height)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width,
|
||||
height,
|
||||
resize_target=True,
|
||||
keep_aspect_ratio=False,
|
||||
ensure_multiple_of=1,
|
||||
resize_method="lower_bound",
|
||||
image_interpolation_method=cv2.INTER_AREA,
|
||||
):
|
||||
"""Init.
|
||||
|
||||
Args:
|
||||
width (int): desired output width
|
||||
height (int): desired output height
|
||||
resize_target (bool, optional):
|
||||
True: Resize the full sample (image, mask, target).
|
||||
False: Resize image only.
|
||||
Defaults to True.
|
||||
keep_aspect_ratio (bool, optional):
|
||||
True: Keep the aspect ratio of the input sample.
|
||||
Output sample might not have the given width and height, and
|
||||
resize behaviour depends on the parameter 'resize_method'.
|
||||
Defaults to False.
|
||||
ensure_multiple_of (int, optional):
|
||||
Output width and height is constrained to be multiple of this parameter.
|
||||
Defaults to 1.
|
||||
resize_method (str, optional):
|
||||
"lower_bound": Output will be at least as large as the given size.
|
||||
"upper_bound": Output will be at max as large as the given size. (Output size might be smaller
|
||||
than given size.)
|
||||
"minimal": Scale as least as possible. (Output size might be smaller than given size.)
|
||||
Defaults to "lower_bound".
|
||||
"""
|
||||
self.__width = width
|
||||
self.__height = height
|
||||
|
||||
self.__resize_target = resize_target
|
||||
self.__keep_aspect_ratio = keep_aspect_ratio
|
||||
self.__multiple_of = ensure_multiple_of
|
||||
self.__resize_method = resize_method
|
||||
self.__image_interpolation_method = image_interpolation_method
|
||||
|
||||
def constrain_to_multiple_of(self, x, min_val=0, max_val=None):
|
||||
y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
if max_val is not None and y > max_val:
|
||||
y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
if y < min_val:
|
||||
y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
return y
|
||||
|
||||
def get_size(self, width, height):
|
||||
# determine new height and width
|
||||
scale_height = self.__height / height
|
||||
scale_width = self.__width / width
|
||||
|
||||
if self.__keep_aspect_ratio:
|
||||
if self.__resize_method == "lower_bound":
|
||||
# scale such that output size is lower bound
|
||||
if scale_width > scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "upper_bound":
|
||||
# scale such that output size is upper bound
|
||||
if scale_width < scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "minimal":
|
||||
# scale as least as possbile
|
||||
if abs(1 - scale_width) < abs(1 - scale_height):
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
else:
|
||||
raise ValueError(f"resize_method {self.__resize_method} not implemented")
|
||||
|
||||
if self.__resize_method == "lower_bound":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height, min_val=self.__height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width, min_val=self.__width)
|
||||
elif self.__resize_method == "upper_bound":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height, max_val=self.__height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width, max_val=self.__width)
|
||||
elif self.__resize_method == "minimal":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width)
|
||||
else:
|
||||
raise ValueError(f"resize_method {self.__resize_method} not implemented")
|
||||
|
||||
return (new_width, new_height)
|
||||
|
||||
def __call__(self, sample):
|
||||
width, height = self.get_size(sample["image"].shape[1], sample["image"].shape[0])
|
||||
|
||||
# resize sample
|
||||
sample["image"] = cv2.resize(
|
||||
sample["image"],
|
||||
(width, height),
|
||||
interpolation=self.__image_interpolation_method,
|
||||
)
|
||||
|
||||
if self.__resize_target:
|
||||
if "disparity" in sample:
|
||||
sample["disparity"] = cv2.resize(
|
||||
sample["disparity"],
|
||||
(width, height),
|
||||
interpolation=cv2.INTER_NEAREST,
|
||||
)
|
||||
|
||||
if "depth" in sample:
|
||||
sample["depth"] = cv2.resize(sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST)
|
||||
|
||||
if "semseg_mask" in sample:
|
||||
# sample["semseg_mask"] = cv2.resize(
|
||||
# sample["semseg_mask"], (width, height), interpolation=cv2.INTER_NEAREST
|
||||
# )
|
||||
sample["semseg_mask"] = F.interpolate(
|
||||
torch.from_numpy(sample["semseg_mask"]).float()[None, None, ...], (height, width), mode="nearest"
|
||||
).numpy()[0, 0]
|
||||
|
||||
if "mask" in sample:
|
||||
sample["mask"] = cv2.resize(
|
||||
sample["mask"].astype(np.float32),
|
||||
(width, height),
|
||||
interpolation=cv2.INTER_NEAREST,
|
||||
)
|
||||
# sample["mask"] = sample["mask"].astype(bool)
|
||||
|
||||
# print(sample['image'].shape, sample['depth'].shape)
|
||||
return sample
|
||||
|
||||
|
||||
class NormalizeImage(object):
|
||||
"""Normlize image by given mean and std."""
|
||||
|
||||
def __init__(self, mean, std):
|
||||
self.__mean = mean
|
||||
self.__std = std
|
||||
|
||||
def __call__(self, sample):
|
||||
sample["image"] = (sample["image"] - self.__mean) / self.__std
|
||||
|
||||
return sample
|
||||
|
||||
|
||||
class PrepareForNet(object):
|
||||
"""Prepare sample for usage as network input."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self, sample):
|
||||
image = np.transpose(sample["image"], (2, 0, 1))
|
||||
sample["image"] = np.ascontiguousarray(image).astype(np.float32)
|
||||
|
||||
if "mask" in sample:
|
||||
sample["mask"] = sample["mask"].astype(np.float32)
|
||||
sample["mask"] = np.ascontiguousarray(sample["mask"])
|
||||
|
||||
if "depth" in sample:
|
||||
depth = sample["depth"].astype(np.float32)
|
||||
sample["depth"] = np.ascontiguousarray(depth)
|
||||
|
||||
if "semseg_mask" in sample:
|
||||
sample["semseg_mask"] = sample["semseg_mask"].astype(np.float32)
|
||||
sample["semseg_mask"] = np.ascontiguousarray(sample["semseg_mask"])
|
||||
|
||||
return sample
|
||||
@@ -1,131 +1,9 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'prettier',
|
||||
'plugin:storybook/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: [
|
||||
'react',
|
||||
'@typescript-eslint',
|
||||
'eslint-plugin-react-hooks',
|
||||
'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,
|
||||
extends: ['@invoke-ai/eslint-config-react'],
|
||||
rules: {
|
||||
'path/no-relative-imports': ['error', { maxDepth: 0 }],
|
||||
curly: 'error',
|
||||
'i18next/no-literal-string': 'warn',
|
||||
'react/jsx-no-bind': ['error', { allowBind: true }],
|
||||
'react/jsx-curly-brace-presence': [
|
||||
'error',
|
||||
{ props: 'never', children: 'never' },
|
||||
],
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'no-var': 'error',
|
||||
'brace-style': 'error',
|
||||
'prefer-template': 'error',
|
||||
'import/no-duplicates': 'error',
|
||||
radix: 'error',
|
||||
'space-before-blocks': 'error',
|
||||
'import/prefer-default-export': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
args: 'after-used',
|
||||
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': [
|
||||
'error',
|
||||
{
|
||||
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',
|
||||
// Prefer @invoke-ai/ui components over chakra
|
||||
'no-restricted-imports': 'off',
|
||||
'@typescript-eslint/no-restricted-imports': [
|
||||
'warn',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: '@chakra-ui/react',
|
||||
message: "Please import from '@invoke-ai/ui' instead.",
|
||||
},
|
||||
{
|
||||
name: '@chakra-ui/layout',
|
||||
message: "Please import from '@invoke-ai/ui' instead.",
|
||||
},
|
||||
{
|
||||
name: '@chakra-ui/portal',
|
||||
message: "Please import from '@invoke-ai/ui' instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.stories.tsx'],
|
||||
rules: {
|
||||
'i18next/no-literal-string': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
// TODO(psyche): Enable this rule. Requires no default exports in components - many changes.
|
||||
'react-refresh/only-export-components': 'off',
|
||||
// TODO(psyche): Enable this rule. Requires a lot of eslint-disable-next-line comments.
|
||||
'@typescript-eslint/consistent-type-assertions': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
endOfLine: 'auto',
|
||||
...require('@invoke-ai/prettier-config-react'),
|
||||
overrides: [
|
||||
{
|
||||
files: ['public/locales/*.json'],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren, memo, useEffect } from 'react';
|
||||
import { modelChanged } from '../src/features/parameters/store/generationSlice';
|
||||
import { useAppDispatch } from '../src/app/store/storeHooks';
|
||||
import { useGlobalModifiersInit } from '@invoke-ai/ui';
|
||||
import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
|
||||
/**
|
||||
* Initializes some state for storybook. Must be in a different component
|
||||
* so that it is run inside the redux context.
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Provider } from 'react-redux';
|
||||
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
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
{
|
||||
"entry": ["src/main.tsx"],
|
||||
"extensions": [".ts", ".tsx"],
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/**",
|
||||
"dist/**",
|
||||
"public/**",
|
||||
"**/*.stories.tsx",
|
||||
"config/**"
|
||||
],
|
||||
"ignorePatterns": ["**/node_modules/**", "dist/**", "public/**", "**/*.stories.tsx", "config/**"],
|
||||
"ignoreUnresolved": [],
|
||||
"ignoreUnimported": ["src/i18.d.ts", "vite.config.ts", "src/vite-env.d.ts"],
|
||||
"respectGitignore": true,
|
||||
|
||||
150
invokeai/frontend/web/README.md
Normal file
150
invokeai/frontend/web/README.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Invoke UI
|
||||
|
||||
<!-- @import "[TOC]" {cmd="toc" depthFrom=2 depthTo=3 orderedList=false} -->
|
||||
|
||||
<!-- code_chunk_output -->
|
||||
|
||||
- [Dev environment](#dev-environment)
|
||||
- [Setup](#setup)
|
||||
- [Package scripts](#package-scripts)
|
||||
- [Type generation](#type-generation)
|
||||
- [Localization](#localization)
|
||||
- [VSCode](#vscode)
|
||||
- [Contributing](#contributing)
|
||||
- [Check in before investing your time](#check-in-before-investing-your-time)
|
||||
- [Commit format](#commit-format)
|
||||
- [Submitting a PR](#submitting-a-pr)
|
||||
- [Other docs](#other-docs)
|
||||
|
||||
<!-- /code_chunk_output -->
|
||||
|
||||
Invoke's UI is made possible by many contributors and open-source libraries. Thank you!
|
||||
|
||||
## Dev environment
|
||||
|
||||
### Setup
|
||||
|
||||
1. Install [node] and [pnpm].
|
||||
1. Run `pnpm i` to install all packages.
|
||||
|
||||
#### Run in dev mode
|
||||
|
||||
1. From `invokeai/frontend/web/`, run `pnpm dev`.
|
||||
1. From repo root, run `python scripts/invokeai-web.py`.
|
||||
1. Point your browser to the dev server address, e.g. <http://localhost:5173/>
|
||||
|
||||
### Package scripts
|
||||
|
||||
- `dev`: run the frontend in dev mode, enabling hot reloading
|
||||
- `build`: run all checks (madge, eslint, prettier, tsc) and then build the frontend
|
||||
- `typegen`: generate types from the OpenAPI schema (see [Type generation])
|
||||
- `lint:madge`: check frontend for circular dependencies
|
||||
- `lint:eslint`: check frontend for code quality
|
||||
- `lint:prettier`: check frontend for code formatting
|
||||
- `lint:tsc`: check frontend for type issues
|
||||
- `lint`: run all checks concurrently
|
||||
- `fix`: run `eslint` and `prettier`, fixing fixable issues
|
||||
|
||||
### Type generation
|
||||
|
||||
We use [openapi-typescript] to generate types from the app's OpenAPI schema.
|
||||
|
||||
The generated types are committed to the repo in [schema.ts].
|
||||
|
||||
```sh
|
||||
# from the repo root, start the server
|
||||
python scripts/invokeai-web.py
|
||||
# from invokeai/frontend/web/, run the script
|
||||
pnpm typegen
|
||||
```
|
||||
|
||||
### Localization
|
||||
|
||||
We use [i18next] for localization, but translation to languages other than English happens on our [Weblate] project.
|
||||
|
||||
Only the English source strings should be changed on this repo.
|
||||
|
||||
### VSCode
|
||||
|
||||
#### Example debugger config
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Invoke UI",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}/invokeai/frontend/web",
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
#### Remote dev
|
||||
|
||||
We've noticed an intermittent timeout issue with the VSCode remote dev port forwarding.
|
||||
|
||||
We suggest disabling the editor's port forwarding feature and doing it manually via SSH:
|
||||
|
||||
```sh
|
||||
ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host
|
||||
```
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
Thanks for your interest in contributing to the Invoke Web UI!
|
||||
|
||||
Please follow these guidelines when contributing.
|
||||
|
||||
### Check in before investing your time
|
||||
|
||||
Please check in before you invest your time on anything besides a trivial fix, in case it conflicts with ongoing work or isn't aligned with the vision for the app.
|
||||
|
||||
If a feature request or issue doesn't already exist for the thing you want to work on, please create one.
|
||||
|
||||
Ping `@psychedelicious` on [discord] in the `#frontend-dev` channel or in the feature request / issue you want to work on - we're happy chat.
|
||||
|
||||
### Code conventions
|
||||
|
||||
- This is a fairly complex app with a deep component tree. Please use memoization (`useCallback`, `useMemo`, `memo`) with enthusiasm.
|
||||
- If you need to add some global, ephemeral state, please use [nanostores] if possible.
|
||||
- Be careful with your redux selectors. If they need to be parameterized, consider creating them inside a `useMemo`.
|
||||
- Feel free to use `lodash` (via `lodash-es`) to make the intent of your code clear.
|
||||
- Please add comments describing the "why", not the "how" (unless it is really arcane).
|
||||
|
||||
### Commit format
|
||||
|
||||
Please use the [conventional commits] spec for the web UI, with a scope of "ui":
|
||||
|
||||
- `chore(ui): bump deps`
|
||||
- `chore(ui): lint`
|
||||
- `feat(ui): add some cool new feature`
|
||||
- `fix(ui): fix some bug`
|
||||
|
||||
### Submitting a PR
|
||||
|
||||
- Ensure your branch is tidy. Use an interactive rebase to clean up the commit history and reword the commit messages if they are not descriptive.
|
||||
- Run `pnpm lint`. Some issues are auto-fixable with `pnpm fix`.
|
||||
- Fill out the PR form when creating the PR.
|
||||
- It doesn't need to be super detailed, but a screenshot or video is nice if you changed something visually.
|
||||
- If a section isn't relevant, delete it. There are no UI tests at this time.
|
||||
|
||||
## Other docs
|
||||
|
||||
- [Workflows - Design and Implementation]
|
||||
- [State Management]
|
||||
|
||||
[node]: https://nodejs.org/en/download/
|
||||
[pnpm]: https://github.com/pnpm/pnpm
|
||||
[discord]: https://discord.gg/ZmtBAhwWhy
|
||||
[i18next]: https://github.com/i18next/react-i18next
|
||||
[Weblate]: https://hosted.weblate.org/engage/invokeai/
|
||||
[openapi-typescript]: https://github.com/drwpow/openapi-typescript
|
||||
[Type generation]: #type-generation
|
||||
[schema.ts]: ../src/services/api/schema.ts
|
||||
[conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/
|
||||
[Workflows - Design and Implementation]: ./docs/WORKFLOWS_DESIGN_IMPLEMENTATION.md
|
||||
[State Management]: ./docs/STATE_MGMT.md
|
||||
@@ -1,154 +0,0 @@
|
||||
# InvokeAI Web UI
|
||||
|
||||
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
|
||||
|
||||
<!-- code_chunk_output -->
|
||||
|
||||
- [InvokeAI Web UI](#invokeai-web-ui)
|
||||
- [Core Libraries](#core-libraries)
|
||||
- [Redux Toolkit](#redux-toolkit)
|
||||
- [Socket\.IO](#socketio)
|
||||
- [Chakra UI](#chakra-ui)
|
||||
- [KonvaJS](#konvajs)
|
||||
- [Vite](#vite)
|
||||
- [i18next & Weblate](#i18next--weblate)
|
||||
- [openapi-typescript](#openapi-typescript)
|
||||
- [reactflow](#reactflow)
|
||||
- [zod](#zod)
|
||||
- [Client Types Generation](#client-types-generation)
|
||||
- [Package Scripts](#package-scripts)
|
||||
- [Contributing](#contributing)
|
||||
- [Dev Environment](#dev-environment)
|
||||
- [VSCode Remote Dev](#vscode-remote-dev)
|
||||
- [Production builds](#production-builds)
|
||||
|
||||
<!-- /code_chunk_output -->
|
||||
|
||||
The UI is a fairly straightforward Typescript React app.
|
||||
|
||||
## Core Libraries
|
||||
|
||||
InvokeAI's UI is made possible by a number of excellent open-source libraries. The most heavily-used are listed below, but there are many others.
|
||||
|
||||
### Redux Toolkit
|
||||
|
||||
[Redux Toolkit] is used for state management and fetching/caching:
|
||||
|
||||
- `RTK-Query` for data fetching and caching
|
||||
- `createAsyncThunk` for a couple other HTTP requests
|
||||
- `createEntityAdapter` to normalize things like images and models
|
||||
- `createListenerMiddleware` for async workflows
|
||||
|
||||
We use [redux-remember] for persistence.
|
||||
|
||||
### Socket\.IO
|
||||
|
||||
[Socket.IO] is used for server-to-client events, like generation process and queue state changes.
|
||||
|
||||
### Chakra UI
|
||||
|
||||
[Chakra UI] is our primary UI library, but we also use a few components from [Mantine v6].
|
||||
|
||||
### KonvaJS
|
||||
|
||||
[KonvaJS] powers the canvas. In the future, we'd like to explore [PixiJS] or WebGPU.
|
||||
|
||||
### Vite
|
||||
|
||||
[Vite] is our bundler.
|
||||
|
||||
### i18next & Weblate
|
||||
|
||||
We use [i18next] for localization, but translation to languages other than English happens on our [Weblate] project. **Only the English source strings should be changed on this repo.**
|
||||
|
||||
### openapi-typescript
|
||||
|
||||
[openapi-typescript] is used to generate types from the server's OpenAPI schema. See TYPES_CODEGEN.md.
|
||||
|
||||
### reactflow
|
||||
|
||||
[reactflow] powers the Workflow Editor.
|
||||
|
||||
### zod
|
||||
|
||||
[zod] schemas are used to model data structures and provide runtime validation.
|
||||
|
||||
## Client Types Generation
|
||||
|
||||
We use [openapi-typescript] to generate types from the app's OpenAPI schema.
|
||||
|
||||
The generated types are written to `invokeai/frontend/web/src/services/api/schema.d.ts`. This file is committed to the repo.
|
||||
|
||||
The server must be started and available at <http://127.0.0.1:9090>.
|
||||
|
||||
```sh
|
||||
# from the repo root, start the server
|
||||
python scripts/invokeai-web.py
|
||||
# from invokeai/frontend/web/, run the script
|
||||
pnpm typegen
|
||||
```
|
||||
|
||||
## Package Scripts
|
||||
|
||||
See `package.json` for all scripts.
|
||||
|
||||
Run with `pnpm <script name>`.
|
||||
|
||||
- `dev`: run the frontend in dev mode, enabling hot reloading
|
||||
- `build`: run all checks (madge, eslint, prettier, tsc) and then build the frontend
|
||||
- `typegen`: generate types from the OpenAPI schema (see [Client Types Generation](#client-types-generation))
|
||||
- `lint:madge`: check frontend for circular dependencies
|
||||
- `lint:eslint`: check frontend for code quality
|
||||
- `lint:prettier`: check frontend for code formatting
|
||||
- `lint:tsc`: check frontend for type issues
|
||||
- `lint`: run all checks concurrently
|
||||
- `fix`: run `eslint` and `prettier`, fixing fixable issues
|
||||
|
||||
## Contributing
|
||||
|
||||
Thanks for your interest in contributing to the InvokeAI Web UI!
|
||||
|
||||
We encourage you to ping @psychedelicious and @blessedcoolant on [discord] if you want to contribute, just to touch base and ensure your work doesn't conflict with anything else going on. The project is very active.
|
||||
|
||||
### Dev Environment
|
||||
|
||||
Install [node] and [pnpm].
|
||||
|
||||
From `invokeai/frontend/web/` run `pnpm i` to get everything set up.
|
||||
|
||||
Start everything in dev mode:
|
||||
|
||||
1. Start the dev server: `pnpm dev`
|
||||
2. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
|
||||
3. Point your browser to the dev server address e.g. <http://localhost:5173/>
|
||||
|
||||
#### VSCode Remote Dev
|
||||
|
||||
We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out. Suggest disabling the IDE's port forwarding feature and doing it manually via SSH:
|
||||
|
||||
`ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host`
|
||||
|
||||
### Production builds
|
||||
|
||||
For a number of technical and logistical reasons, we need to commit UI build artefacts to the repo.
|
||||
|
||||
If you submit a PR, there is a good chance we will ask you to include a separate commit with a build of the app.
|
||||
|
||||
To build for production, run `pnpm build`.
|
||||
|
||||
[node]: https://nodejs.org/en/download/
|
||||
[pnpm]: https://github.com/pnpm/pnpm
|
||||
[discord]: https://discord.gg/ZmtBAhwWhy
|
||||
[Redux Toolkit]: https://github.com/reduxjs/redux-toolkit
|
||||
[redux-remember]: https://github.com/zewish/redux-remember
|
||||
[Socket.IO]: https://github.com/socketio/socket.io
|
||||
[Chakra UI]: https://github.com/chakra-ui/chakra-ui
|
||||
[Mantine v6]: https://v6.mantine.dev/
|
||||
[KonvaJS]: https://github.com/konvajs/react-konva
|
||||
[PixiJS]: https://github.com/pixijs/pixijs
|
||||
[Vite]: https://github.com/vitejs/vite
|
||||
[i18next]: https://github.com/i18next/react-i18next
|
||||
[Weblate]: https://hosted.weblate.org/engage/invokeai/
|
||||
[openapi-typescript]: https://github.com/drwpow/openapi-typescript
|
||||
[reactflow]: https://github.com/xyflow/xyflow
|
||||
[zod]: https://github.com/colinhacks/zod
|
||||
38
invokeai/frontend/web/docs/STATE_MGMT.md
Normal file
38
invokeai/frontend/web/docs/STATE_MGMT.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# State Management
|
||||
|
||||
The app makes heavy use of Redux Toolkit, its Query library, and `nanostores`.
|
||||
|
||||
## Redux
|
||||
|
||||
TODO
|
||||
|
||||
## `nanostores`
|
||||
|
||||
[nanostores] is a tiny state management library. It provides both imperative and declarative APIs.
|
||||
|
||||
### Example
|
||||
|
||||
```ts
|
||||
export const $myStringOption = atom<string | null>(null);
|
||||
|
||||
// Outside a component, or within a callback for performance-critical logic
|
||||
$myStringOption.get();
|
||||
$myStringOption.set('new value');
|
||||
|
||||
// Inside a component
|
||||
const myStringOption = useStore($myStringOption);
|
||||
```
|
||||
|
||||
### Where to put nanostores
|
||||
|
||||
- For global application state, export your stores from `invokeai/frontend/web/src/app/store/nanostores/`.
|
||||
- For feature state, create a file for the stores next to the redux slice definition (e.g. `invokeai/frontend/web/src/features/myFeature/myFeatureNanostores.ts`).
|
||||
- For hooks with global state, export the store from the same file the hook is in, or put it next to the hook.
|
||||
|
||||
### When to use nanostores
|
||||
|
||||
- For non-serializable data that needs to be available throughout the app, use `nanostores` instead of a global.
|
||||
- For ephemeral global state (i.e. state that does not need to be persisted), use `nanostores` instead of redux.
|
||||
- For performance-critical code and in callbacks, redux selectors can be problematic due to the declarative reactivity system. Consider refactoring to use `nanostores` if there's a **measurable** performance issue.
|
||||
|
||||
[nanostores]: https://github.com/nanostores/nanostores/
|
||||
@@ -23,7 +23,7 @@
|
||||
- [Primitive Types](#primitive-types)
|
||||
- [Complex Types](#complex-types)
|
||||
- [Collection Types](#collection-types)
|
||||
- [Polymorphic Types](#polymorphic-types)
|
||||
- [Collection or Scalar Types](#collection-or-scalar-types)
|
||||
- [Optional Fields](#optional-fields)
|
||||
- [Building Field Input Templates](#building-field-input-templates)
|
||||
- [Building Field Output Templates](#building-field-output-templates)
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "concurrently \"vite dev\" \"pnpm run theme:watch\"",
|
||||
"dev:host": "concurrently \"vite dev --host\" \"pnpm run theme:watch\"",
|
||||
"dev": "vite dev",
|
||||
"dev:host": "vite dev --host",
|
||||
"build": "pnpm run lint && vite build",
|
||||
"typegen": "node scripts/typegen.js",
|
||||
"preview": "vite preview",
|
||||
@@ -31,9 +31,6 @@
|
||||
"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": "pnpm run theme",
|
||||
"theme": "chakra-cli tokens node_modules/@invoke-ai/ui",
|
||||
"theme:watch": "chakra-cli tokens node_modules/@invoke-ai/ui --watch",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"unimported": "npx unimported"
|
||||
@@ -52,21 +49,12 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/anatomy": "^2.2.2",
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
"@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.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fontsource-variable/inter": "^5.0.16",
|
||||
"@invoke-ai/ui": "0.0.10",
|
||||
"@invoke-ai/ui-library": "^0.0.18",
|
||||
"@mantine/form": "6.0.21",
|
||||
"@nanostores/react": "^0.7.1",
|
||||
"@reduxjs/toolkit": "2.0.1",
|
||||
@@ -116,7 +104,6 @@
|
||||
"zod-validation-error": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -124,7 +111,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arthurgeron/eslint-plugin-react-usememo": "^2.2.3",
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"@invoke-ai/eslint-config-react": "^0.0.12",
|
||||
"@invoke-ai/prettier-config-react": "^0.0.6",
|
||||
"@storybook/addon-docs": "^7.6.10",
|
||||
"@storybook/addon-essentials": "^7.6.10",
|
||||
"@storybook/addon-interactions": "^7.6.10",
|
||||
|
||||
1120
invokeai/frontend/web/pnpm-lock.yaml
generated
1120
invokeai/frontend/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -110,7 +110,28 @@
|
||||
"somethingWentWrong": "Etwas ist schief gelaufen",
|
||||
"copyError": "$t(gallery.copy) Fehler",
|
||||
"input": "Eingabe",
|
||||
"notInstalled": "Nicht $t(common.installed)"
|
||||
"notInstalled": "Nicht $t(common.installed)",
|
||||
"advancedOptions": "Erweiterte Einstellungen",
|
||||
"alpha": "Alpha",
|
||||
"red": "Rot",
|
||||
"green": "Grün",
|
||||
"blue": "Blau",
|
||||
"delete": "Löschen",
|
||||
"or": "oder",
|
||||
"direction": "Richtung",
|
||||
"free": "Frei",
|
||||
"save": "Speichern",
|
||||
"preferencesLabel": "Präferenzen",
|
||||
"created": "Erstellt",
|
||||
"prevPage": "Vorherige Seite",
|
||||
"nextPage": "Nächste Seite",
|
||||
"unknownError": "Unbekannter Fehler",
|
||||
"unsaved": "Nicht gespeichert",
|
||||
"aboutDesc": "Verwenden Sie Invoke für die Arbeit? Dann siehe hier:",
|
||||
"localSystem": "Lokales System",
|
||||
"orderBy": "Ordnen nach",
|
||||
"saveAs": "Speicher als",
|
||||
"updated": "Aktualisiert"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Erzeugungen",
|
||||
@@ -701,7 +722,8 @@
|
||||
"invokeProgressBar": "Invoke Fortschrittsanzeige",
|
||||
"mode": "Modus",
|
||||
"resetUI": "$t(accessibility.reset) von UI",
|
||||
"createIssue": "Ticket erstellen"
|
||||
"createIssue": "Ticket erstellen",
|
||||
"about": "Über"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Automatisches Hinzufügen zum Ordner",
|
||||
@@ -809,7 +831,8 @@
|
||||
"canny": "Canny",
|
||||
"hedDescription": "Ganzheitlich verschachtelte Kantenerkennung",
|
||||
"scribble": "Scribble",
|
||||
"maxFaces": "Maximal Anzahl Gesichter"
|
||||
"maxFaces": "Maximal Anzahl Gesichter",
|
||||
"resizeSimple": "Größe ändern (einfach)"
|
||||
},
|
||||
"queue": {
|
||||
"status": "Status",
|
||||
@@ -999,5 +1022,27 @@
|
||||
"selectLoRA": "Wählen ein LoRA aus",
|
||||
"esrganModel": "ESRGAN Modell",
|
||||
"addLora": "LoRA hinzufügen"
|
||||
},
|
||||
"accordions": {
|
||||
"generation": {
|
||||
"title": "Erstellung",
|
||||
"modelTab": "Modell",
|
||||
"conceptsTab": "Konzepte"
|
||||
},
|
||||
"image": {
|
||||
"title": "Bild"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Erweitert"
|
||||
},
|
||||
"control": {
|
||||
"title": "Kontrolle",
|
||||
"controlAdaptersTab": "Kontroll Adapter",
|
||||
"ipTab": "Bild Beschreibung"
|
||||
},
|
||||
"compositing": {
|
||||
"coherenceTab": "Kohärenzpass",
|
||||
"infillTab": "Füllung"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"back": "Back",
|
||||
"batch": "Batch Manager",
|
||||
"cancel": "Cancel",
|
||||
"copy": "Copy",
|
||||
"copyError": "$t(gallery.copy) Error",
|
||||
"close": "Close",
|
||||
"on": "On",
|
||||
@@ -224,6 +225,7 @@
|
||||
"amult": "a_mult",
|
||||
"autoConfigure": "Auto configure processor",
|
||||
"balanced": "Balanced",
|
||||
"base": "Base",
|
||||
"beginEndStepPercent": "Begin / End Step Percentage",
|
||||
"bgth": "bg_th",
|
||||
"canny": "Canny",
|
||||
@@ -237,6 +239,8 @@
|
||||
"controlMode": "Control Mode",
|
||||
"crop": "Crop",
|
||||
"delete": "Delete",
|
||||
"depthAnything": "Depth Anything",
|
||||
"depthAnythingDescription": "Depth map generation using the Depth Anything technique",
|
||||
"depthMidas": "Depth (Midas)",
|
||||
"depthMidasDescription": "Depth map generation using Midas",
|
||||
"depthZoe": "Depth (Zoe)",
|
||||
@@ -256,6 +260,7 @@
|
||||
"colorMapTileSize": "Tile Size",
|
||||
"importImageFromCanvas": "Import Image From Canvas",
|
||||
"importMaskFromCanvas": "Import Mask From Canvas",
|
||||
"large": "Large",
|
||||
"lineart": "Lineart",
|
||||
"lineartAnime": "Lineart Anime",
|
||||
"lineartAnimeDescription": "Anime-style lineart processing",
|
||||
@@ -268,6 +273,7 @@
|
||||
"minConfidence": "Min Confidence",
|
||||
"mlsd": "M-LSD",
|
||||
"mlsdDescription": "Minimalist Line Segment Detector",
|
||||
"modelSize": "Model Size",
|
||||
"none": "None",
|
||||
"noneDescription": "No processing applied",
|
||||
"normalBae": "Normal BAE",
|
||||
@@ -288,6 +294,7 @@
|
||||
"selectModel": "Select a model",
|
||||
"setControlImageDimensions": "Set Control Image Dimensions To W/H",
|
||||
"showAdvanced": "Show Advanced",
|
||||
"small": "Small",
|
||||
"toggleControlNet": "Toggle this ControlNet",
|
||||
"w": "W",
|
||||
"weight": "Weight",
|
||||
@@ -600,6 +607,10 @@
|
||||
"desc": "Send current image to Image to Image",
|
||||
"title": "Send To Image To Image"
|
||||
},
|
||||
"remixImage": {
|
||||
"desc": "Use all parameters except seed from the current image",
|
||||
"title": "Remix image"
|
||||
},
|
||||
"setParameters": {
|
||||
"desc": "Use all parameters of the current image",
|
||||
"title": "Set Parameters"
|
||||
@@ -1216,6 +1227,7 @@
|
||||
"useCpuNoise": "Use CPU Noise",
|
||||
"cpuNoise": "CPU Noise",
|
||||
"gpuNoise": "GPU Noise",
|
||||
"remixImage": "Remix Image",
|
||||
"useInitImg": "Use Initial Image",
|
||||
"usePrompt": "Use Prompt",
|
||||
"useSeed": "Use Seed",
|
||||
@@ -1452,9 +1464,7 @@
|
||||
},
|
||||
"compositingCoherencePass": {
|
||||
"heading": "Coherence Pass",
|
||||
"paragraphs": [
|
||||
"A second round of denoising helps to composite the Inpainted/Outpainted image."
|
||||
]
|
||||
"paragraphs": ["A second round of denoising helps to composite the Inpainted/Outpainted image."]
|
||||
},
|
||||
"compositingCoherenceMode": {
|
||||
"heading": "Mode",
|
||||
@@ -1462,10 +1472,7 @@
|
||||
},
|
||||
"compositingCoherenceSteps": {
|
||||
"heading": "Steps",
|
||||
"paragraphs": [
|
||||
"Number of denoising steps used in the Coherence Pass.",
|
||||
"Same as the main Steps parameter."
|
||||
]
|
||||
"paragraphs": ["Number of denoising steps used in the Coherence Pass.", "Same as the main Steps parameter."]
|
||||
},
|
||||
"compositingStrength": {
|
||||
"heading": "Strength",
|
||||
@@ -1487,15 +1494,11 @@
|
||||
},
|
||||
"controlNetControlMode": {
|
||||
"heading": "Control Mode",
|
||||
"paragraphs": [
|
||||
"Lends more weight to either the prompt or ControlNet."
|
||||
]
|
||||
"paragraphs": ["Lends more weight to either the prompt or ControlNet."]
|
||||
},
|
||||
"controlNetResizeMode": {
|
||||
"heading": "Resize Mode",
|
||||
"paragraphs": [
|
||||
"How the ControlNet image will be fit to the image output size."
|
||||
]
|
||||
"paragraphs": ["How the ControlNet image will be fit to the image output size."]
|
||||
},
|
||||
"controlNet": {
|
||||
"heading": "ControlNet",
|
||||
@@ -1505,9 +1508,7 @@
|
||||
},
|
||||
"controlNetWeight": {
|
||||
"heading": "Weight",
|
||||
"paragraphs": [
|
||||
"How strongly the ControlNet will impact the generated image."
|
||||
]
|
||||
"paragraphs": ["How strongly the ControlNet will impact the generated image."]
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"heading": "Dynamic Prompts",
|
||||
@@ -1519,9 +1520,7 @@
|
||||
},
|
||||
"dynamicPromptsMaxPrompts": {
|
||||
"heading": "Max Prompts",
|
||||
"paragraphs": [
|
||||
"Limits the number of prompts that can be generated by Dynamic Prompts."
|
||||
]
|
||||
"paragraphs": ["Limits the number of prompts that can be generated by Dynamic Prompts."]
|
||||
},
|
||||
"dynamicPromptsSeedBehaviour": {
|
||||
"heading": "Seed Behaviour",
|
||||
@@ -1538,9 +1537,7 @@
|
||||
},
|
||||
"lora": {
|
||||
"heading": "LoRA Weight",
|
||||
"paragraphs": [
|
||||
"Higher LoRA weight will lead to larger impacts on the final image."
|
||||
]
|
||||
"paragraphs": ["Higher LoRA weight will lead to larger impacts on the final image."]
|
||||
},
|
||||
"noiseUseCPU": {
|
||||
"heading": "Use CPU Noise",
|
||||
@@ -1552,9 +1549,7 @@
|
||||
},
|
||||
"paramCFGScale": {
|
||||
"heading": "CFG Scale",
|
||||
"paragraphs": [
|
||||
"Controls how much your prompt influences the generation process."
|
||||
]
|
||||
"paragraphs": ["Controls how much your prompt influences the generation process."]
|
||||
},
|
||||
"paramCFGRescaleMultiplier": {
|
||||
"heading": "CFG Rescale Multiplier",
|
||||
@@ -1606,9 +1601,7 @@
|
||||
},
|
||||
"paramVAE": {
|
||||
"heading": "VAE",
|
||||
"paragraphs": [
|
||||
"Model used for translating AI output into the final image."
|
||||
]
|
||||
"paragraphs": ["Model used for translating AI output into the final image."]
|
||||
},
|
||||
"paramVAEPrecision": {
|
||||
"heading": "VAE Precision",
|
||||
@@ -1697,6 +1690,7 @@
|
||||
"workflowLibrary": "Library",
|
||||
"userWorkflows": "My Workflows",
|
||||
"defaultWorkflows": "Default Workflows",
|
||||
"projectWorkflows": "Project Workflows",
|
||||
"openWorkflow": "Open Workflow",
|
||||
"uploadWorkflow": "Load from File",
|
||||
"deleteWorkflow": "Delete Workflow",
|
||||
@@ -1709,6 +1703,7 @@
|
||||
"workflowSaved": "Workflow Saved",
|
||||
"noRecentWorkflows": "No Recent Workflows",
|
||||
"noUserWorkflows": "No User Workflows",
|
||||
"noWorkflows": "No Workflows",
|
||||
"noSystemWorkflows": "No System Workflows",
|
||||
"problemLoading": "Problem Loading Workflows",
|
||||
"loading": "Loading Workflows",
|
||||
|
||||
@@ -118,7 +118,14 @@
|
||||
"advancedOptions": "Opzioni avanzate",
|
||||
"free": "Libero",
|
||||
"or": "o",
|
||||
"preferencesLabel": "Preferenze"
|
||||
"preferencesLabel": "Preferenze",
|
||||
"red": "Rosso",
|
||||
"aboutHeading": "Possiedi il tuo potere creativo",
|
||||
"aboutDesc": "Utilizzi Invoke per lavoro? Guarda qui:",
|
||||
"localSystem": "Sistema locale",
|
||||
"green": "Verde",
|
||||
"blue": "Blu",
|
||||
"alpha": "Alfa"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generazioni",
|
||||
@@ -377,7 +384,11 @@
|
||||
"desc": "Apre e chiude le opzioni e i pannelli della galleria",
|
||||
"title": "Attiva/disattiva le Opzioni e la Galleria"
|
||||
},
|
||||
"clearSearch": "Cancella ricerca"
|
||||
"clearSearch": "Cancella ricerca",
|
||||
"remixImage": {
|
||||
"desc": "Utilizza tutti i parametri tranne il seme dell'immagine corrente",
|
||||
"title": "Remixa l'immagine"
|
||||
}
|
||||
},
|
||||
"modelManager": {
|
||||
"modelManager": "Gestione Modelli",
|
||||
@@ -521,7 +532,8 @@
|
||||
"customConfigFileLocation": "Posizione del file di configurazione personalizzato",
|
||||
"vaePrecision": "Precisione VAE",
|
||||
"noModelSelected": "Nessun modello selezionato",
|
||||
"conversionNotSupported": "Conversione non supportata"
|
||||
"conversionNotSupported": "Conversione non supportata",
|
||||
"configFile": "File di configurazione"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Immagini",
|
||||
@@ -660,7 +672,10 @@
|
||||
"lockAspectRatio": "Blocca proporzioni",
|
||||
"swapDimensions": "Scambia dimensioni",
|
||||
"aspect": "Aspetto",
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (potrebbe essere troppo grande)"
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (potrebbe essere troppo grande)",
|
||||
"boxBlur": "Box",
|
||||
"gaussianBlur": "Gaussian",
|
||||
"remixImage": "Remixa l'immagine"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Modelli",
|
||||
@@ -794,7 +809,9 @@
|
||||
"invalidUpload": "Caricamento non valido",
|
||||
"problemDeletingWorkflow": "Problema durante l'eliminazione del flusso di lavoro",
|
||||
"workflowDeleted": "Flusso di lavoro eliminato",
|
||||
"problemRetrievingWorkflow": "Problema nel recupero del flusso di lavoro"
|
||||
"problemRetrievingWorkflow": "Problema nel recupero del flusso di lavoro",
|
||||
"resetInitialImage": "Reimposta l'immagine iniziale",
|
||||
"uploadInitialImage": "Carica l'immagine iniziale"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
@@ -899,7 +916,8 @@
|
||||
"loadMore": "Carica altro",
|
||||
"mode": "Modalità",
|
||||
"resetUI": "$t(accessibility.reset) l'Interfaccia Utente",
|
||||
"createIssue": "Segnala un problema"
|
||||
"createIssue": "Segnala un problema",
|
||||
"about": "Informazioni"
|
||||
},
|
||||
"ui": {
|
||||
"hideProgressImages": "Nascondi avanzamento immagini",
|
||||
@@ -1232,7 +1250,11 @@
|
||||
"scribble": "Scarabocchio",
|
||||
"amult": "Angolo di illuminazione",
|
||||
"coarse": "Approssimativo",
|
||||
"resizeSimple": "Ridimensiona (semplice)"
|
||||
"resizeSimple": "Ridimensiona (semplice)",
|
||||
"large": "Grande",
|
||||
"small": "Piccolo",
|
||||
"depthAnythingDescription": "Generazione di mappe di profondità utilizzando la tecnica Depth Anything",
|
||||
"modelSize": "Dimensioni del modello"
|
||||
},
|
||||
"queue": {
|
||||
"queueFront": "Aggiungi all'inizio della coda",
|
||||
@@ -1664,7 +1686,9 @@
|
||||
"userWorkflows": "I miei flussi di lavoro",
|
||||
"newWorkflowCreated": "Nuovo flusso di lavoro creato",
|
||||
"downloadWorkflow": "Salva su file",
|
||||
"uploadWorkflow": "Carica da file"
|
||||
"uploadWorkflow": "Carica da file",
|
||||
"projectWorkflows": "Flussi di lavoro del progetto",
|
||||
"noWorkflows": "Nessun flusso di lavoro"
|
||||
},
|
||||
"app": {
|
||||
"storeNotInitialized": "Il negozio non è inizializzato"
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
{
|
||||
"accessibility": {
|
||||
"invokeProgressBar": "Invoke ilerleme durumu",
|
||||
"nextImage": "Sonraki Resim",
|
||||
"useThisParameter": "Kullanıcı parametreleri",
|
||||
"nextImage": "Sonraki İmaj",
|
||||
"useThisParameter": "Bu ayarları kullan",
|
||||
"copyMetadataJson": "Metadata verilerini kopyala (JSON)",
|
||||
"exitViewer": "Görüntüleme Modundan Çık",
|
||||
"zoomIn": "Yakınlaştır",
|
||||
"zoomOut": "Uzaklaştır",
|
||||
"rotateCounterClockwise": "Döndür (Saat yönünün tersine)",
|
||||
"rotateClockwise": "Döndür (Saat yönünde)",
|
||||
"rotateCounterClockwise": "Saat yönünün tersine döndür",
|
||||
"rotateClockwise": "Saat yönüne döndür",
|
||||
"flipHorizontally": "Yatay Çevir",
|
||||
"flipVertically": "Dikey Çevir",
|
||||
"modifyConfig": "Ayarları Değiştir",
|
||||
"toggleAutoscroll": "Otomatik kaydırmayı aç/kapat",
|
||||
"toggleLogViewer": "Günlük Görüntüleyici Aç/Kapa",
|
||||
"showOptionsPanel": "Ayarlar Panelini Göster",
|
||||
"modelSelect": "Model Seçin",
|
||||
"reset": "Sıfırla",
|
||||
"uploadImage": "Resim Yükle",
|
||||
"previousImage": "Önceki Resim",
|
||||
"menu": "Menü"
|
||||
"toggleAutoscroll": "Otomatik kaydırmayı Aç-Kapat",
|
||||
"toggleLogViewer": "Günlüğü Aç-Kapat",
|
||||
"showOptionsPanel": "Yan Paneli Göster",
|
||||
"modelSelect": "Model Seçimi",
|
||||
"reset": "Resetle",
|
||||
"uploadImage": "İmaj Yükle",
|
||||
"previousImage": "Önceki İmaj",
|
||||
"menu": "Menü",
|
||||
"about": "Hakkında",
|
||||
"mode": "Kip",
|
||||
"resetUI": "$t(accessibility.reset)Arayüz",
|
||||
"showGalleryPanel": "Galeri Panelini Göster",
|
||||
"loadMore": "Daha Getir",
|
||||
"createIssue": "Sorun Bildir"
|
||||
},
|
||||
"common": {
|
||||
"hotkeysLabel": "Kısayol Tuşları",
|
||||
"languagePickerLabel": "Dil Seçimi",
|
||||
"reportBugLabel": "Hata Bildir",
|
||||
"languagePickerLabel": "Dil",
|
||||
"reportBugLabel": "Sorun Bildir",
|
||||
"githubLabel": "Github",
|
||||
"discordLabel": "Discord",
|
||||
"settingsLabel": "Ayarlar",
|
||||
@@ -37,22 +43,324 @@
|
||||
"langJapanese": "Japonca",
|
||||
"langPolish": "Lehçe",
|
||||
"langPortuguese": "Portekizce",
|
||||
"langBrPortuguese": "Portekizcr (Brezilya)",
|
||||
"langBrPortuguese": "Portekizce (Brezilya)",
|
||||
"langRussian": "Rusça",
|
||||
"langSimplifiedChinese": "Çince (Basit)",
|
||||
"langUkranian": "Ukraynaca",
|
||||
"langSpanish": "İspanyolca",
|
||||
"txt2img": "Metinden Resime",
|
||||
"img2img": "Resimden Metine",
|
||||
"linear": "Çizgisel",
|
||||
"nodes": "Düğümler",
|
||||
"postprocessing": "İşlem Sonrası",
|
||||
"postProcessing": "İşlem Sonrası",
|
||||
"postProcessDesc2": "Daha gelişmiş özellikler için ve iş akışını kolaylaştırmak için özel bir kullanıcı arayüzü çok yakında yayınlanacaktır.",
|
||||
"postProcessDesc3": "Invoke AI komut satırı arayüzü, bir çok yeni özellik sunmaktadır.",
|
||||
"txt2img": "Yazıdan İmaj",
|
||||
"img2img": "İmajdan İmaj",
|
||||
"linear": "Doğrusal",
|
||||
"nodes": "İş Akış Düzenleyici",
|
||||
"postprocessing": "Rötuş",
|
||||
"postProcessing": "Rötuş",
|
||||
"postProcessDesc2": "Daha gelişmiş iş akışlarına olanak sağlayacak özel bir arayüz yakında yayınlanacaktır.",
|
||||
"postProcessDesc3": "Invoke AI Komut Satırı Arayüzü, Embiggen dahil birçok yeni özellik sunmaktadır.",
|
||||
"langKorean": "Korece",
|
||||
"unifiedCanvas": "Akıllı Tuval",
|
||||
"nodesDesc": "Görüntülerin oluşturulmasında hazırladığımız yeni bir sistem geliştirme aşamasındadır. Bu harika özellikler ve çok daha fazlası için bizi takip etmeye devam edin.",
|
||||
"postProcessDesc1": "Invoke AI son kullanıcıya yönelik bir çok özellik sunar. Görüntü kalitesi yükseltme, yüz restorasyonu WebUI üzerinden kullanılabilir. Metinden resime ve resimden metne araçlarına gelişmiş seçenekler menüsünden ulaşabilirsiniz. İsterseniz mevcut görüntü ekranının üzerindeki veya görüntüleyicideki görüntüyü doğrudan düzenleyebilirsiniz."
|
||||
"nodesDesc": "İmaj oluşturmak için hazırladığımız çizge tabanlı sistem şu an geliştirme aşamasındadır. Bu harika özellik hakkındaki gelişmeler için bizi takip etmeye devam edin.",
|
||||
"postProcessDesc1": "Invoke AI birçok rötuş (post-process) aracı sağlar. İmaj büyütme ve yüz iyileştirme halihazırda WebUI üzerinden kullanılabilir. Bunlara Yazıdan İmaj ve İmajdan İmaj sekmelerindeki Gelişmiş Ayarlar menüsünden ulaşabilirsiniz. İsterseniz mevcut görüntü ekranının üzerindeki veya görüntüleyicideki imajı doğrudan üstteki tuşlar yardımıyla düzenleyebilirsiniz.",
|
||||
"batch": "Toplu İş Yöneticisi",
|
||||
"accept": "Onayla",
|
||||
"cancel": "Vazgeç",
|
||||
"advanced": "Gelişmiş",
|
||||
"copyError": "$t(gallery.copy) Hata",
|
||||
"on": "Açık",
|
||||
"or": "ya da",
|
||||
"aboutDesc": "Invoke'u iş için mi kullanıyorsunuz? Şuna bir göz atın:",
|
||||
"advancedOptions": "Gelişmiş Ayarlar",
|
||||
"ai": "yapay zeka",
|
||||
"close": "Kapat",
|
||||
"auto": "Otomatik",
|
||||
"communityLabel": "Topluluk",
|
||||
"back": "Geri",
|
||||
"areYouSure": "Emin misiniz?",
|
||||
"notInstalled": "$t(common.installed) Değil",
|
||||
"openInNewTab": "Yeni Sekmede Aç",
|
||||
"aboutHeading": "Yaratıcı Gücünüzün Sahibi Olun",
|
||||
"lightMode": "Açık Tema",
|
||||
"load": "Yükle",
|
||||
"loading": "Yükleniyor",
|
||||
"loadingInvokeAI": "Invoke AI Yükleniyor",
|
||||
"localSystem": "Yerel Sistem",
|
||||
"inpaint": "içboyama",
|
||||
"modelManager": "Model Yöneticisi",
|
||||
"orderBy": "Sırala",
|
||||
"outpaint": "dışboyama",
|
||||
"outputs": "Çıktılar",
|
||||
"langHebrew": "İbranice",
|
||||
"learnMore": "Bilgi Edin",
|
||||
"nodeEditor": "Çizge Düzenleyici",
|
||||
"save": "Kaydet",
|
||||
"statusMergingModels": "Modeller Birleştiriliyor",
|
||||
"statusGenerating": "Oluşturuluyor",
|
||||
"statusGenerationComplete": "Oluşturma Tamamlandı",
|
||||
"statusGeneratingOutpainting": "Dışboyama Oluşturuluyor",
|
||||
"statusLoadingModel": "Model Yükleniyor",
|
||||
"random": "Rastgele",
|
||||
"simple": "Basit",
|
||||
"preferencesLabel": "Seçenekler",
|
||||
"statusConnected": "Bağlandı",
|
||||
"statusMergedModels": "Modeller Birleştirildi",
|
||||
"statusModelChanged": "Model Değişti",
|
||||
"statusModelConverted": "Model Dönüştürüldü",
|
||||
"statusPreparing": "Hazırlanıyor",
|
||||
"statusProcessing": "İşleniyor",
|
||||
"statusProcessingCanceled": "İşlemden Vazgeçildi",
|
||||
"statusRestoringFacesCodeFormer": "Yüzler İyileştiriliyor (CodeFormer)",
|
||||
"statusRestoringFacesGFPGAN": "Yüzler İyileştiriliyor (GFPGAN)",
|
||||
"template": "Şablon",
|
||||
"saveAs": "Farklı Kaydet",
|
||||
"statusProcessingComplete": "İşlem Tamamlandı",
|
||||
"statusSavingImage": "İmaj Kaydediliyor",
|
||||
"somethingWentWrong": "Bir sorun oluştu",
|
||||
"statusConvertingModel": "Model Dönüştürülüyor",
|
||||
"statusDisconnected": "Bağlantı Kesildi",
|
||||
"statusError": "Hata",
|
||||
"statusGeneratingImageToImage": "İmajdan İmaj Oluşturuluyor",
|
||||
"statusGeneratingInpainting": "İçboyama Oluşturuluyor",
|
||||
"statusRestoringFaces": "Yüzler İyileştiriliyor",
|
||||
"statusUpscaling": "Büyütme",
|
||||
"statusUpscalingESRGAN": "Büyütme (ESRGAN)",
|
||||
"training": "Eğitim",
|
||||
"statusGeneratingTextToImage": "Yazıdan İmaj Oluşturuluyor",
|
||||
"imagePrompt": "Resim İstemi",
|
||||
"unknown": "Bilinmeyen",
|
||||
"green": "Yeşil",
|
||||
"red": "Kırmızı",
|
||||
"blue": "Mavi",
|
||||
"alpha": "Alfa",
|
||||
"file": "Dosya",
|
||||
"folder": "Klasör",
|
||||
"format": "biçim",
|
||||
"details": "Ayrıntılar",
|
||||
"error": "Hata",
|
||||
"generate": "Oluştur",
|
||||
"free": "Serbest",
|
||||
"imageFailedToLoad": "İmaj Yüklenemedi",
|
||||
"safetensors": "Safetensor",
|
||||
"upload": "Yükle",
|
||||
"nextPage": "Sonraki Sayfa",
|
||||
"prevPage": "Önceki Sayfa",
|
||||
"dontAskMeAgain": "Bir daha sorma",
|
||||
"delete": "Kaldır",
|
||||
"direction": "Yön",
|
||||
"darkMode": "Koyu Tema",
|
||||
"unsaved": "Kaydedilmemiş",
|
||||
"unknownError": "Bilinmeyen Hata"
|
||||
},
|
||||
"accordions": {
|
||||
"generation": {
|
||||
"title": "Oluşturma",
|
||||
"modelTab": "Model",
|
||||
"conceptsTab": "Konseptler"
|
||||
},
|
||||
"image": {
|
||||
"title": "İmaj"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Gelişmiş"
|
||||
},
|
||||
"compositing": {
|
||||
"title": "Birleştirme",
|
||||
"coherenceTab": "Uyum Geçişi",
|
||||
"infillTab": "Doldurma"
|
||||
},
|
||||
"control": {
|
||||
"ipTab": "Resim İstemleri"
|
||||
}
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Panoya Otomatik Ekleme",
|
||||
"cancel": "Vazgeç",
|
||||
"clearSearch": "Aramayı Sil",
|
||||
"deleteBoard": "Panoyu Sil",
|
||||
"loading": "Yükleniyor...",
|
||||
"myBoard": "Panom",
|
||||
"selectBoard": "Bir Pano Seç",
|
||||
"addBoard": "Pano Ekle",
|
||||
"deleteBoardAndImages": "Panoyu ve İmajları Sil",
|
||||
"deleteBoardOnly": "Sadece Panoyu Sil",
|
||||
"deletedBoardsCannotbeRestored": "Silinen panolar geri getirilemez",
|
||||
"menuItemAutoAdd": "Bu panoya otomatik olarak ekle",
|
||||
"move": "Taşı",
|
||||
"movingImagesToBoard_one": "{{count}} imajı şu panoya taşı:",
|
||||
"movingImagesToBoard_other": "{{count}} imajı şu panoya taşı:",
|
||||
"noMatching": "Eşleşen pano yok",
|
||||
"searchBoard": "Pano Ara...",
|
||||
"topMessage": "Bu pano, şu özelliklerde kullanılan imajlar içeriyor:",
|
||||
"downloadBoard": "Panoyu İndir",
|
||||
"uncategorized": "Kategorisiz",
|
||||
"changeBoard": "Panoyu Değiştir",
|
||||
"bottomMessage": "Bu panoyu ve imajları silmek, bunları kullanan özelliklerin resetlemesine neden olacaktır."
|
||||
},
|
||||
"controlnet": {
|
||||
"balanced": "Dengeli",
|
||||
"contentShuffle": "İçerik Karma",
|
||||
"contentShuffleDescription": "İmajın içeriğini karıştırır",
|
||||
"depthZoe": "Derinlik (Zoe)",
|
||||
"depthZoeDescription": "Zoe kullanarak derinlik haritası oluşturma",
|
||||
"resizeMode": "Boyutlandırma Kipi",
|
||||
"addControlNet": "$t(common.controlNet) Ekle",
|
||||
"addIPAdapter": "$t(common.ipAdapter) Ekle",
|
||||
"addT2IAdapter": "$t(common.t2iAdapter) Ekle",
|
||||
"controlNetEnabledT2IDisabled": "$t(common.controlNet) etkin, $t(common.t2iAdapter)s etkin değil",
|
||||
"t2iEnabledControlNetDisabled": "$t(common.t2iAdapter) etkin, $t(common.controlNet)s etkin değil",
|
||||
"colorMap": "Renk",
|
||||
"crop": "Kırpma",
|
||||
"delete": "Kaldır",
|
||||
"depthMidas": "Derinlik (Midas)",
|
||||
"depthMidasDescription": "Midas kullanarak derinlik haritası oluşturma",
|
||||
"detectResolution": "Çözünürlüğü Bul",
|
||||
"none": "Hiçbiri",
|
||||
"noneDescription": "Hiçbir işlem uygulanmamış",
|
||||
"selectModel": "Model seçin",
|
||||
"showAdvanced": "Gelişmiş Ayarları Göster",
|
||||
"controlNetT2IMutexDesc": "$t(common.controlNet) ve $t(common.t2iAdapter)'nün beraber kullanımı henüz desteklenmiyor.",
|
||||
"canny": "Canny",
|
||||
"colorMapDescription": "İmajdan bir renk haritası oluşturur",
|
||||
"handAndFace": "El ve Yüz",
|
||||
"processor": "İşlemci",
|
||||
"prompt": "İstem",
|
||||
"duplicate": "Kopyala",
|
||||
"large": "Büyük",
|
||||
"modelSize": "Model Boyutu",
|
||||
"resize": "Boyutlandır",
|
||||
"resizeSimple": "Boyutlandır (Basit)",
|
||||
"safe": "Güvenli",
|
||||
"small": "Küçük",
|
||||
"weight": "Etki",
|
||||
"cannyDescription": "Canny kenar algılama",
|
||||
"fill": "Doldur",
|
||||
"highThreshold": "Üst Eşik",
|
||||
"imageResolution": "İmaj Çözünürlüğü",
|
||||
"colorMapTileSize": "Karo Boyutu",
|
||||
"importImageFromCanvas": "Tuvalden İmajı içe Aktar",
|
||||
"importMaskFromCanvas": "Tuvalden Maskeyi İçe Aktar",
|
||||
"lowThreshold": "Alt Eşik",
|
||||
"base": "Taban",
|
||||
"depthAnythingDescription": "Depth Anything tekniği ile derinlik haritası oluşturma"
|
||||
},
|
||||
"queue": {
|
||||
"queuedCount": "{{pending}} Sırada",
|
||||
"resumeSucceeded": "İşlem Sürdürüldü",
|
||||
"openQueue": "Sırayı Göster",
|
||||
"cancelSucceeded": "Öğeden Vazgeçildi",
|
||||
"cancelFailed": "Öğeden Vazgeçmede Sorun",
|
||||
"prune": "Arındır",
|
||||
"pruneTooltip": "{{item_count}} Bitmiş İşi Sil",
|
||||
"resumeFailed": "İşlemi Sürdürmede Sorun",
|
||||
"pauseFailed": "İşlemi Duraklatmada Sorun",
|
||||
"cancelBatchSucceeded": "Toplu İşten Vazgeçildi",
|
||||
"pruneSucceeded": "{{item_count}} Bitmiş İş Sıradan Kaldırıldı",
|
||||
"in_progress": "İşleniyor",
|
||||
"completed": "Bitti",
|
||||
"canceled": "Vazgeçildi",
|
||||
"back": "arka",
|
||||
"queueFront": "Sıranın Başına Ekle",
|
||||
"queueBack": "Sıraya Ekle",
|
||||
"resumeTooltip": "İşlemi Sürdür",
|
||||
"clearQueueAlertDialog2": "Sırayı boşaltmak istediğinizden emin misiniz?",
|
||||
"batchQueuedDesc_one": "{{count}} iş sıranın {{direction}} eklendi",
|
||||
"batchQueuedDesc_other": "{{count}} iş sıranın {{direction}} eklendi",
|
||||
"batchFailedToQueue": "Toplu İş Sıraya Alınamadı",
|
||||
"front": "ön",
|
||||
"queue": "Sıra",
|
||||
"resume": "Sürdür",
|
||||
"queueTotal": "Toplam {{total}}",
|
||||
"queueEmpty": "Sıra Boş",
|
||||
"clearQueueAlertDialog": "Sırayı boşaltma tuşu halihazırdaki işlemi durdurur ve sırayı tamamen boşaltır.",
|
||||
"current": "Şu Anki",
|
||||
"time": "Süre",
|
||||
"pause": "Duraklat",
|
||||
"pauseTooltip": "İşlemi Duraklat",
|
||||
"pruneFailed": "Sırayı Arındırmada Sorun",
|
||||
"clearTooltip": "Vazgeç ve Tüm Öğeleri Sil",
|
||||
"clear": "Boşalt",
|
||||
"cancelBatchFailed": "Toplu İşten Vazgeçmede Sorun",
|
||||
"next": "Sonraki",
|
||||
"status": "Durum",
|
||||
"failed": "Başarısız",
|
||||
"item": "Öğe",
|
||||
"enqueueing": "Toplu İş Sıraya Alınıyor",
|
||||
"pauseSucceeded": "İşlem Duraklatıldı",
|
||||
"cancel": "Vazgeç",
|
||||
"cancelTooltip": "Şu Anki Öğeden Vazgeç",
|
||||
"clearSucceeded": "Sıra Boşaltıldı",
|
||||
"clearFailed": "Sırayı Boşaltmada Sorun",
|
||||
"cancelBatch": "Toplu İşten Vazgeç",
|
||||
"cancelItem": "Öğeden Vazgeç",
|
||||
"total": "Toplam",
|
||||
"pending": "Sırada",
|
||||
"completedIn": "'de bitirildi",
|
||||
"batch": "Toplu İş",
|
||||
"session": "Oturum",
|
||||
"batchQueued": "Toplu İş Sıraya Alındı",
|
||||
"notReady": "Sıraya Alınamadı"
|
||||
},
|
||||
"invocationCache": {
|
||||
"cacheSize": "Önbellek Boyutu",
|
||||
"disable": "Kapat",
|
||||
"clear": "Boşalt",
|
||||
"maxCacheSize": "Maksimum Önbellek Boyutu",
|
||||
"useCache": "Önbellek Kullan",
|
||||
"enable": "Aç"
|
||||
},
|
||||
"gallery": {
|
||||
"deleteImageBin": "Silinen imajlar işletim sisteminin çöp kutusuna gönderilir.",
|
||||
"deleteImagePermanent": "Silinen imajlar geri getirilemez.",
|
||||
"assets": "Özkaynaklar",
|
||||
"autoAssignBoardOnClick": "Tıklanan Panoya Otomatik Atama",
|
||||
"loading": "Yükleniyor",
|
||||
"starImage": "Yıldız Koy",
|
||||
"download": "İndir",
|
||||
"deleteSelection": "Seçileni Sil",
|
||||
"preparingDownloadFailed": "İndirme Hazırlanırken Sorun",
|
||||
"problemDeletingImages": "İmaj Silmede Sorun",
|
||||
"featuresWillReset": "Bu imajı silerseniz, o özellikler resetlenecektir.",
|
||||
"galleryImageResetSize": "Boyutu Resetle",
|
||||
"noImageSelected": "İmaj Seçili Değil",
|
||||
"unstarImage": "Yıldızı Kaldır",
|
||||
"uploads": "Yüklemeler",
|
||||
"problemDeletingImagesDesc": "Bir ya da daha çok imaj silinemedi",
|
||||
"gallerySettings": "Galeri Ayarları",
|
||||
"image": "imaj",
|
||||
"galleryImageSize": "İmaj Boyutu",
|
||||
"allImagesLoaded": "Bütün İmajlar Yüklendi",
|
||||
"copy": "Kopyala",
|
||||
"noImagesInGallery": "Gösterilecek İmaj Yok",
|
||||
"autoSwitchNewImages": "Yeni İmajı Biter Bitmez Gör",
|
||||
"maintainAspectRatio": "En-Boy Oranını Koru",
|
||||
"currentlyInUse": "Bu imaj şu an bu bölümlerde kullanımda:",
|
||||
"deleteImage": "İmajı Sil",
|
||||
"loadMore": "Daha Getir",
|
||||
"setCurrentImage": "Çalışma İmajı Yap",
|
||||
"unableToLoad": "Galeri Yüklenemedi",
|
||||
"downloadSelection": "Seçileni İndir",
|
||||
"preparingDownload": "İndirmeye Hazırlanıyor"
|
||||
},
|
||||
"hrf": {
|
||||
"hrf": "Yüksek Çözünürlük Kürü",
|
||||
"enableHrf": "Yüksek Çözünürlük Kürünü Aç",
|
||||
"hrfStrength": "Yüksek Çözünürlük Kürü Etkisi",
|
||||
"strengthTooltip": "Düşük değerler daha az detaya neden olsa da olası bozuklukları önleyebilir.",
|
||||
"metadata": {
|
||||
"enabled": "Yüksek Çözünürlük Kürü Açık",
|
||||
"strength": "Yüksek Çözünürlük Kürü Etkisi",
|
||||
"method": "Yüksek Çözünürlük Kürü Yöntemi"
|
||||
},
|
||||
"upscaleMethod": "Büyütme Yöntemi",
|
||||
"enableHrfTooltip": "Daha düşük bir başlangıç çözünürlüğüyle oluşturup ana çözünürlüğe büyütür ve İmajdan İmaj yapar."
|
||||
},
|
||||
"hotkeys": {
|
||||
"noHotkeysFound": "Kısayol Tuşu Bulanamadı",
|
||||
"searchHotkeys": "Kısayol Tuşlarında Ara",
|
||||
"clearSearch": "Aramayı Sil"
|
||||
},
|
||||
"embedding": {
|
||||
"incompatibleModel": "Uyumsuz ana model:"
|
||||
},
|
||||
"unifiedCanvas": {
|
||||
"accept": "Onayla"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
|
||||
const OUTPUT_FILE = 'src/services/api/schema.ts';
|
||||
|
||||
async function main() {
|
||||
process.stdout.write(
|
||||
`Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...`
|
||||
);
|
||||
process.stdout.write(`Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...`);
|
||||
const types = await openapiTS(OPENAPI_URL, {
|
||||
exportType: true,
|
||||
transform: (schemaObject) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, useGlobalModifiersInit } from '@invoke-ai/ui';
|
||||
import { Box, useGlobalModifiersInit } from '@invoke-ai/ui-library';
|
||||
import { useSocketIO } from 'app/hooks/useSocketIO';
|
||||
import { useLogger } from 'app/logging/useLogger';
|
||||
import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
|
||||
@@ -45,8 +45,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||
useGlobalModifiersInit();
|
||||
useGlobalHotkeys();
|
||||
|
||||
const { dropzone, isHandlingUpload, setIsHandlingUpload } =
|
||||
useFullscreenDropzone();
|
||||
const { dropzone, isHandlingUpload, setIsHandlingUpload } = useFullscreenDropzone();
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
clearStorage();
|
||||
@@ -70,10 +69,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
onReset={handleReset}
|
||||
FallbackComponent={AppErrorBoundaryFallback}
|
||||
>
|
||||
<ErrorBoundary onReset={handleReset} FallbackComponent={AppErrorBoundaryFallback}>
|
||||
<Box
|
||||
id="invoke-app-wrapper"
|
||||
w="100vw"
|
||||
@@ -86,10 +82,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||
<InvokeTabs />
|
||||
<AnimatePresence>
|
||||
{dropzone.isDragActive && isHandlingUpload && (
|
||||
<ImageUploadOverlay
|
||||
dropzone={dropzone}
|
||||
setIsHandlingUpload={setIsHandlingUpload}
|
||||
/>
|
||||
<ImageUploadOverlay dropzone={dropzone} setIsHandlingUpload={setIsHandlingUpload} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Box>
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Button, Flex, Heading, Link, Text, useToast } from '@invoke-ai/ui';
|
||||
import { Button, Flex, Heading, Link, Text, useToast } from '@invoke-ai/ui-library';
|
||||
import newGithubIssueUrl from 'new-github-issue-url';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
PiArrowCounterClockwiseBold,
|
||||
PiArrowSquareOutBold,
|
||||
PiCopyBold,
|
||||
} from 'react-icons/pi';
|
||||
import { PiArrowCounterClockwiseBold, PiArrowSquareOutBold, PiCopyBold } from 'react-icons/pi';
|
||||
import { serializeError } from 'serialize-error';
|
||||
|
||||
type Props = {
|
||||
@@ -37,22 +33,8 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
[error.message, error.name]
|
||||
);
|
||||
return (
|
||||
<Flex
|
||||
layerStyle="body"
|
||||
w="100vw"
|
||||
h="100vh"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
p={4}
|
||||
>
|
||||
<Flex
|
||||
layerStyle="first"
|
||||
flexDir="column"
|
||||
borderRadius="base"
|
||||
justifyContent="center"
|
||||
gap={8}
|
||||
p={16}
|
||||
>
|
||||
<Flex layerStyle="body" w="100vw" h="100vh" alignItems="center" justifyContent="center" p={4}>
|
||||
<Flex layerStyle="first" flexDir="column" borderRadius="base" justifyContent="center" gap={8} p={16}>
|
||||
<Heading>{t('common.somethingWentWrong')}</Heading>
|
||||
<Flex
|
||||
layerStyle="second"
|
||||
@@ -68,19 +50,14 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap={4}>
|
||||
<Button
|
||||
leftIcon={<PiArrowCounterClockwiseBold />}
|
||||
onClick={resetErrorBoundary}
|
||||
>
|
||||
<Button leftIcon={<PiArrowCounterClockwiseBold />} onClick={resetErrorBoundary}>
|
||||
{t('accessibility.resetUI')}
|
||||
</Button>
|
||||
<Button leftIcon={<PiCopyBold />} onClick={handleCopy}>
|
||||
{t('common.copyError')}
|
||||
</Button>
|
||||
<Link href={url} isExternal>
|
||||
<Button leftIcon={<PiArrowSquareOutBold />}>
|
||||
{t('accessibility.createIssue')}
|
||||
</Button>
|
||||
<Button leftIcon={<PiArrowSquareOutBold />}>{t('accessibility.createIssue')}</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -10,14 +10,17 @@ 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 { $openAPISchemaUrl } from 'app/store/nanostores/openAPISchemaUrl';
|
||||
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 { $workflowCategories } from 'app/store/nanostores/workflowCategories';
|
||||
import { createStore } from 'app/store/store';
|
||||
import type { PartialAppConfig } from 'app/types/invokeai';
|
||||
import Loading from 'common/components/Loading/Loading';
|
||||
import AppDndContext from 'features/dnd/components/AppDndContext';
|
||||
import type { PropsWithChildren, ReactNode } from 'react';
|
||||
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { lazy, memo, useEffect, useMemo } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
|
||||
@@ -28,12 +31,13 @@ const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||
|
||||
interface Props extends PropsWithChildren {
|
||||
apiUrl?: string;
|
||||
openAPISchemaUrl?: string;
|
||||
token?: string;
|
||||
config?: PartialAppConfig;
|
||||
customNavComponent?: ReactNode;
|
||||
customNavComponent?: () => JSX.Element;
|
||||
middleware?: Middleware[];
|
||||
projectId?: string;
|
||||
galleryHeader?: ReactNode;
|
||||
galleryHeader?: () => JSX.Element;
|
||||
queueId?: string;
|
||||
selectedImage?: {
|
||||
imageName: string;
|
||||
@@ -42,11 +46,13 @@ interface Props extends PropsWithChildren {
|
||||
customStarUi?: CustomStarUi;
|
||||
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
||||
isDebugging?: boolean;
|
||||
logo?: ReactNode;
|
||||
logo?: () => JSX.Element;
|
||||
workflowCategories?: WorkflowCategory[];
|
||||
}
|
||||
|
||||
const InvokeAIUI = ({
|
||||
apiUrl,
|
||||
openAPISchemaUrl,
|
||||
token,
|
||||
config,
|
||||
customNavComponent,
|
||||
@@ -59,6 +65,7 @@ const InvokeAIUI = ({
|
||||
socketOptions,
|
||||
isDebugging = false,
|
||||
logo,
|
||||
workflowCategories,
|
||||
}: Props) => {
|
||||
useEffect(() => {
|
||||
// configure API client token
|
||||
@@ -123,6 +130,16 @@ const InvokeAIUI = ({
|
||||
};
|
||||
}, [customNavComponent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (openAPISchemaUrl) {
|
||||
$openAPISchemaUrl.set(openAPISchemaUrl);
|
||||
}
|
||||
|
||||
return () => {
|
||||
$openAPISchemaUrl.set(undefined);
|
||||
};
|
||||
}, [openAPISchemaUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (galleryHeader) {
|
||||
$galleryHeader.set(galleryHeader);
|
||||
@@ -143,6 +160,16 @@ const InvokeAIUI = ({
|
||||
};
|
||||
}, [logo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (workflowCategories) {
|
||||
$workflowCategories.set(workflowCategories);
|
||||
}
|
||||
|
||||
return () => {
|
||||
$workflowCategories.set([]);
|
||||
};
|
||||
}, [workflowCategories]);
|
||||
|
||||
useEffect(() => {
|
||||
if (socketOptions) {
|
||||
$socketOptions.set(socketOptions);
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import '@fontsource-variable/inter';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
import {
|
||||
ChakraProvider,
|
||||
DarkMode,
|
||||
extendTheme,
|
||||
theme as _theme,
|
||||
TOAST_OPTIONS,
|
||||
} from '@invoke-ai/ui';
|
||||
import { ChakraProvider, DarkMode, extendTheme, theme as _theme, TOAST_OPTIONS } from '@invoke-ai/ui-library';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useToast } from '@invoke-ai/ui';
|
||||
import { useToast } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
|
||||
import type { MakeToastArg } from 'features/system/util/makeToast';
|
||||
@@ -36,10 +36,7 @@ const Toaster = () => {
|
||||
*/
|
||||
export const useAppToaster = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const toaster = useCallback(
|
||||
(arg: MakeToastArg) => dispatch(addToast(makeToast(arg))),
|
||||
[dispatch]
|
||||
);
|
||||
const toaster = useCallback((arg: MakeToastArg) => dispatch(addToast(makeToast(arg))), [dispatch]);
|
||||
|
||||
return toaster;
|
||||
};
|
||||
|
||||
@@ -6,10 +6,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import type { MapStore } from 'nanostores';
|
||||
import { atom, map } from 'nanostores';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import type {
|
||||
ClientToServerEvents,
|
||||
ServerToClientEvents,
|
||||
} from 'services/events/types';
|
||||
import type { ClientToServerEvents, ServerToClientEvents } from 'services/events/types';
|
||||
import { setEventListeners } from 'services/events/util/setEventListeners';
|
||||
import type { ManagerOptions, Socket, SocketOptions } from 'socket.io-client';
|
||||
import { io } from 'socket.io-client';
|
||||
@@ -45,7 +42,7 @@ export const useSocketIO = () => {
|
||||
const socketOptions = useMemo(() => {
|
||||
const options: Partial<ManagerOptions & SocketOptions> = {
|
||||
timeout: 60000,
|
||||
path: '/ws/socket.io',
|
||||
path: baseUrl ? '/ws/socket.io' : `${window.location.pathname}ws/socket.io`,
|
||||
autoConnect: false, // achtung! removing this breaks the dynamic middleware
|
||||
forceNew: true,
|
||||
};
|
||||
@@ -56,7 +53,7 @@ export const useSocketIO = () => {
|
||||
}
|
||||
|
||||
return { ...options, ...addlSocketOptions };
|
||||
}, [authToken, addlSocketOptions]);
|
||||
}, [authToken, addlSocketOptions, baseUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if ($isSocketInitialized.get()) {
|
||||
@@ -64,10 +61,7 @@ export const useSocketIO = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
|
||||
socketUrl,
|
||||
socketOptions
|
||||
);
|
||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(socketUrl, socketOptions);
|
||||
setEventListeners({ dispatch, socket });
|
||||
socket.connect();
|
||||
|
||||
|
||||
@@ -30,20 +30,11 @@ export type LoggerNamespace =
|
||||
| 'queue'
|
||||
| 'dnd';
|
||||
|
||||
export const logger = (namespace: LoggerNamespace) =>
|
||||
$logger.get().child({ namespace });
|
||||
export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace });
|
||||
|
||||
export const zLogLevel = z.enum([
|
||||
'trace',
|
||||
'debug',
|
||||
'info',
|
||||
'warn',
|
||||
'error',
|
||||
'fatal',
|
||||
]);
|
||||
export const zLogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
|
||||
export type LogLevel = z.infer<typeof zLogLevel>;
|
||||
export const isLogLevel = (v: unknown): v is LogLevel =>
|
||||
zLogLevel.safeParse(v).success;
|
||||
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<LogLevel, number> = {
|
||||
|
||||
@@ -17,10 +17,7 @@ export const useLogger = (namespace: LoggerNamespace) => {
|
||||
localStorage.setItem('ROARR_LOG', 'true');
|
||||
|
||||
// Use a filter to show only logs of the given level
|
||||
localStorage.setItem(
|
||||
'ROARR_FILTER',
|
||||
`context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`
|
||||
);
|
||||
localStorage.setItem('ROARR_FILTER', `context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`);
|
||||
} else {
|
||||
// Disable console log output
|
||||
localStorage.setItem('ROARR_LOG', 'false');
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
createDraftSafeSelectorCreator,
|
||||
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';
|
||||
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { StorageError } from 'app/store/enhancers/reduxRemember/errors';
|
||||
import { $projectId } from 'app/store/nanostores/projectId';
|
||||
import type { UseStore } from 'idb-keyval';
|
||||
import {
|
||||
clear,
|
||||
createStore as createIDBKeyValStore,
|
||||
get,
|
||||
set,
|
||||
} from 'idb-keyval';
|
||||
import { clear, createStore as createIDBKeyValStore, get, set } from 'idb-keyval';
|
||||
import { action, atom } from 'nanostores';
|
||||
import type { Driver } from 'redux-remember';
|
||||
|
||||
// Create a custom idb-keyval store (just needed to customize the name)
|
||||
export const $idbKeyValStore = atom<UseStore>(
|
||||
createIDBKeyValStore('invoke', 'invoke-store')
|
||||
);
|
||||
export const $idbKeyValStore = atom<UseStore>(createIDBKeyValStore('invoke', 'invoke-store'));
|
||||
|
||||
export const clearIdbKeyValStore = action($idbKeyValStore, 'clear', (store) => {
|
||||
clear(store.get());
|
||||
|
||||
@@ -4,13 +4,12 @@ import { diff } from 'jsondiffpatch';
|
||||
/**
|
||||
* Super simple logger middleware. Useful for debugging when the redux devtools are awkward.
|
||||
*/
|
||||
export const debugLoggerMiddleware: Middleware =
|
||||
(api: MiddlewareAPI) => (next) => (action) => {
|
||||
const originalState = api.getState();
|
||||
console.log('REDUX: dispatching', action);
|
||||
const result = next(action);
|
||||
const nextState = api.getState();
|
||||
console.log('REDUX: next state', nextState);
|
||||
console.log('REDUX: diff', diff(originalState, nextState));
|
||||
return result;
|
||||
};
|
||||
export const debugLoggerMiddleware: Middleware = (api: MiddlewareAPI) => (next) => (action) => {
|
||||
const originalState = api.getState();
|
||||
console.log('REDUX: dispatching', action);
|
||||
const result = next(action);
|
||||
const nextState = api.getState();
|
||||
console.log('REDUX: next state', nextState);
|
||||
console.log('REDUX: diff', diff(originalState, nextState));
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -35,8 +35,7 @@ export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
||||
if (socketGeneratorProgress.match(action)) {
|
||||
const sanitized = cloneDeep(action);
|
||||
if (sanitized.payload.data.progress_image) {
|
||||
sanitized.payload.data.progress_image.dataURL =
|
||||
'<Progress image omitted>';
|
||||
sanitized.payload.data.progress_image.dataURL = '<Progress image omitted>';
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import type {
|
||||
ListenerEffect,
|
||||
TypedAddListener,
|
||||
TypedStartListening,
|
||||
UnknownAction,
|
||||
} from '@reduxjs/toolkit';
|
||||
import type { ListenerEffect, TypedAddListener, TypedStartListening, UnknownAction } from '@reduxjs/toolkit';
|
||||
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
@@ -47,10 +42,7 @@ import {
|
||||
import { addImagesStarredListener } from './listeners/imagesStarred';
|
||||
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
|
||||
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
|
||||
import {
|
||||
addImageUploadedFulfilledListener,
|
||||
addImageUploadedRejectedListener,
|
||||
} from './listeners/imageUploaded';
|
||||
import { addImageUploadedFulfilledListener, addImageUploadedRejectedListener } from './listeners/imageUploaded';
|
||||
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
|
||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||
import { addModelsLoadedListener } from './listeners/modelsLoaded';
|
||||
@@ -78,19 +70,11 @@ export const listenerMiddleware = createListenerMiddleware();
|
||||
|
||||
export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
|
||||
|
||||
export const startAppListening =
|
||||
listenerMiddleware.startListening as AppStartListening;
|
||||
export const startAppListening = listenerMiddleware.startListening as AppStartListening;
|
||||
|
||||
export const addAppListener = addListener as TypedAddListener<
|
||||
RootState,
|
||||
AppDispatch
|
||||
>;
|
||||
export const addAppListener = addListener as TypedAddListener<RootState, AppDispatch>;
|
||||
|
||||
export type AppListenerEffect = ListenerEffect<
|
||||
UnknownAction,
|
||||
RootState,
|
||||
AppDispatch
|
||||
>;
|
||||
export type AppListenerEffect = ListenerEffect<UnknownAction, RootState, AppDispatch>;
|
||||
|
||||
/**
|
||||
* The RTK listener middleware is a lightweight alternative sagas/observables.
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { isAnyOf } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import {
|
||||
canvasBatchIdsReset,
|
||||
commitStagingAreaImage,
|
||||
discardStagedImages,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { canvasBatchIdsReset, commitStagingAreaImage, discardStagedImages } from 'features/canvas/store/canvasSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { t } from 'i18next';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
@@ -23,10 +19,7 @@ export const addCommitStagingAreaImageListener = () => {
|
||||
|
||||
try {
|
||||
const req = dispatch(
|
||||
queueApi.endpoints.cancelByBatchIds.initiate(
|
||||
{ batch_ids: batchIds },
|
||||
{ fixedCacheKey: 'cancelByBatchIds' }
|
||||
)
|
||||
queueApi.endpoints.cancelByBatchIds.initiate({ batch_ids: batchIds }, { fixedCacheKey: 'cancelByBatchIds' })
|
||||
);
|
||||
const { canceled } = await req.unwrap();
|
||||
req.reset();
|
||||
|
||||
@@ -12,15 +12,9 @@ export const appStarted = createAction('app/appStarted');
|
||||
export const addFirstListImagesListener = () => {
|
||||
startAppListening({
|
||||
matcher: imagesApi.endpoints.listImages.matchFulfilled,
|
||||
effect: async (
|
||||
action,
|
||||
{ dispatch, unsubscribe, cancelActiveListeners }
|
||||
) => {
|
||||
effect: async (action, { dispatch, unsubscribe, cancelActiveListeners }) => {
|
||||
// Only run this listener on the first listImages request for no-board images
|
||||
if (
|
||||
action.meta.arg.queryCacheKey !==
|
||||
getListImagesUrl({ board_id: 'none', categories: IMAGE_CATEGORIES })
|
||||
) {
|
||||
if (action.meta.arg.queryCacheKey !== getListImagesUrl({ board_id: 'none', categories: IMAGE_CATEGORIES })) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { setInfillMethod } from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
shouldUseNSFWCheckerChanged,
|
||||
shouldUseWatermarkerChanged,
|
||||
} from 'features/system/store/systemSlice';
|
||||
import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
|
||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||
|
||||
import { startAppListening } from '..';
|
||||
@@ -11,11 +8,7 @@ export const addAppConfigReceivedListener = () => {
|
||||
startAppListening({
|
||||
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const {
|
||||
infill_methods = [],
|
||||
nsfw_methods = [],
|
||||
watermarking_methods = [],
|
||||
} = action.payload;
|
||||
const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
|
||||
const infillMethod = getState().generation.infillMethod;
|
||||
|
||||
if (!infill_methods.includes(infillMethod)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createStandaloneToast, theme, TOAST_OPTIONS } from '@invoke-ai/ui';
|
||||
import { createStandaloneToast, theme, TOAST_OPTIONS } from '@invoke-ai/ui-library';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
||||
@@ -20,10 +20,7 @@ export const addBatchEnqueuedListener = () => {
|
||||
effect: async (action) => {
|
||||
const response = action.payload;
|
||||
const arg = action.meta.arg.originalArgs;
|
||||
logger('queue').debug(
|
||||
{ enqueueResult: parseify(response) },
|
||||
'Batch enqueued'
|
||||
);
|
||||
logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
|
||||
|
||||
if (!toast.isActive('batch-queued')) {
|
||||
toast({
|
||||
@@ -53,10 +50,7 @@ export const addBatchEnqueuedListener = () => {
|
||||
status: 'error',
|
||||
description: 'Unknown Error',
|
||||
});
|
||||
logger('queue').error(
|
||||
{ batchConfig: parseify(arg), error: parseify(response) },
|
||||
t('queue.batchFailedToQueue')
|
||||
);
|
||||
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,10 +75,7 @@ export const addBatchEnqueuedListener = () => {
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
logger('queue').error(
|
||||
{ batchConfig: parseify(arg), error: parseify(response) },
|
||||
t('queue.batchFailedToQueue')
|
||||
);
|
||||
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -22,13 +22,7 @@ export const addDeleteBoardAndImagesFulfilledListener = () => {
|
||||
|
||||
const { generation, canvas, nodes, controlAdapters } = getState();
|
||||
deleted_images.forEach((image_name) => {
|
||||
const imageUsage = getImageUsage(
|
||||
generation,
|
||||
canvas,
|
||||
nodes,
|
||||
controlAdapters,
|
||||
image_name
|
||||
);
|
||||
const imageUsage = getImageUsage(generation, canvas, nodes, controlAdapters, image_name);
|
||||
|
||||
if (imageUsage.isInitialImage && !wasInitialImageReset) {
|
||||
dispatch(clearInitialImage());
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { isAnyOf } from '@reduxjs/toolkit';
|
||||
import {
|
||||
boardIdSelected,
|
||||
galleryViewChanged,
|
||||
imageSelected,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import {
|
||||
ASSETS_CATEGORIES,
|
||||
IMAGE_CATEGORIES,
|
||||
} from 'features/gallery/store/types';
|
||||
import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import { imagesSelectors } from 'services/api/util';
|
||||
|
||||
@@ -16,53 +9,35 @@ import { startAppListening } from '..';
|
||||
export const addBoardIdSelectedListener = () => {
|
||||
startAppListening({
|
||||
matcher: isAnyOf(boardIdSelected, galleryViewChanged),
|
||||
effect: async (
|
||||
action,
|
||||
{ getState, dispatch, condition, cancelActiveListeners }
|
||||
) => {
|
||||
effect: async (action, { getState, dispatch, condition, cancelActiveListeners }) => {
|
||||
// Cancel any in-progress instances of this listener, we don't want to select an image from a previous board
|
||||
cancelActiveListeners();
|
||||
|
||||
const state = getState();
|
||||
|
||||
const board_id = boardIdSelected.match(action)
|
||||
? action.payload.boardId
|
||||
: state.gallery.selectedBoardId;
|
||||
const board_id = boardIdSelected.match(action) ? action.payload.boardId : state.gallery.selectedBoardId;
|
||||
|
||||
const galleryView = galleryViewChanged.match(action)
|
||||
? action.payload
|
||||
: state.gallery.galleryView;
|
||||
const galleryView = galleryViewChanged.match(action) ? action.payload : state.gallery.galleryView;
|
||||
|
||||
// when a board is selected, we need to wait until the board has loaded *some* images, then select the first one
|
||||
const categories =
|
||||
galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES;
|
||||
const categories = galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES;
|
||||
|
||||
const queryArgs = { board_id: board_id ?? 'none', categories };
|
||||
|
||||
// wait until the board has some images - maybe it already has some from a previous fetch
|
||||
// must use getState() to ensure we do not have stale state
|
||||
const isSuccess = await condition(
|
||||
() =>
|
||||
imagesApi.endpoints.listImages.select(queryArgs)(getState())
|
||||
.isSuccess,
|
||||
() => imagesApi.endpoints.listImages.select(queryArgs)(getState()).isSuccess,
|
||||
5000
|
||||
);
|
||||
|
||||
if (isSuccess) {
|
||||
// the board was just changed - we can select the first image
|
||||
const { data: boardImagesData } =
|
||||
imagesApi.endpoints.listImages.select(queryArgs)(getState());
|
||||
const { data: boardImagesData } = imagesApi.endpoints.listImages.select(queryArgs)(getState());
|
||||
|
||||
if (
|
||||
boardImagesData &&
|
||||
boardIdSelected.match(action) &&
|
||||
action.payload.selectedImageName
|
||||
) {
|
||||
if (boardImagesData && boardIdSelected.match(action) && action.payload.selectedImageName) {
|
||||
const firstImage = imagesSelectors.selectAll(boardImagesData)[0];
|
||||
const selectedImage = imagesSelectors.selectById(
|
||||
boardImagesData,
|
||||
action.payload.selectedImageName
|
||||
);
|
||||
const selectedImage = imagesSelectors.selectById(boardImagesData, action.payload.selectedImageName);
|
||||
|
||||
dispatch(imageSelected(selectedImage || firstImage || null));
|
||||
} else {
|
||||
|
||||
@@ -11,9 +11,7 @@ export const addCanvasCopiedToClipboardListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: canvasCopiedToClipboard,
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
const moduleLog = $logger
|
||||
.get()
|
||||
.child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||
const state = getState();
|
||||
|
||||
try {
|
||||
|
||||
@@ -11,9 +11,7 @@ export const addCanvasDownloadedAsImageListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: canvasDownloadedAsImage,
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
const moduleLog = $logger
|
||||
.get()
|
||||
.child({ namespace: 'canvasSavedToGalleryListener' });
|
||||
const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' });
|
||||
const state = getState();
|
||||
|
||||
let blob;
|
||||
@@ -32,9 +30,7 @@ export const addCanvasDownloadedAsImageListener = () => {
|
||||
}
|
||||
|
||||
downloadBlob(blob, 'canvas.png');
|
||||
dispatch(
|
||||
addToast({ title: t('toast.canvasDownloaded'), status: 'success' })
|
||||
);
|
||||
dispatch(addToast({ title: t('toast.canvasDownloaded'), status: 'success' }));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,9 +13,7 @@ export const addCanvasMergedListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: canvasMerged,
|
||||
effect: async (action, { dispatch }) => {
|
||||
const moduleLog = $logger
|
||||
.get()
|
||||
.child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||
const blob = await getFullBaseLayerBlob();
|
||||
|
||||
if (!blob) {
|
||||
|
||||
@@ -21,11 +21,7 @@ type AnyControlAdapterParamChangeAction =
|
||||
| ReturnType<typeof controlAdapterProcessortTypeChanged>
|
||||
| ReturnType<typeof controlAdapterAutoConfigToggled>;
|
||||
|
||||
const predicate: AnyListenerPredicate<RootState> = (
|
||||
action,
|
||||
state,
|
||||
prevState
|
||||
) => {
|
||||
const predicate: AnyListenerPredicate<RootState> = (action, state, prevState) => {
|
||||
const isActionMatched =
|
||||
controlAdapterProcessorParamsChanged.match(action) ||
|
||||
controlAdapterModelChanged.match(action) ||
|
||||
@@ -40,12 +36,7 @@ const predicate: AnyListenerPredicate<RootState> = (
|
||||
const { id } = action.payload;
|
||||
const prevCA = selectControlAdapterById(prevState.controlAdapters, id);
|
||||
const ca = selectControlAdapterById(state.controlAdapters, id);
|
||||
if (
|
||||
!prevCA ||
|
||||
!isControlNetOrT2IAdapter(prevCA) ||
|
||||
!ca ||
|
||||
!isControlNetOrT2IAdapter(ca)
|
||||
) {
|
||||
if (!prevCA || !isControlNetOrT2IAdapter(prevCA) || !ca || !isControlNetOrT2IAdapter(ca)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,37 +64,28 @@ export const addControlNetImageProcessedListener = () => {
|
||||
);
|
||||
const enqueueResult = await req.unwrap();
|
||||
req.reset();
|
||||
log.debug(
|
||||
{ enqueueResult: parseify(enqueueResult) },
|
||||
t('queue.graphQueued')
|
||||
);
|
||||
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||
|
||||
const [invocationCompleteAction] = await take(
|
||||
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
||||
socketInvocationComplete.match(action) &&
|
||||
action.payload.data.queue_batch_id ===
|
||||
enqueueResult.batch.batch_id &&
|
||||
action.payload.data.queue_batch_id === enqueueResult.batch.batch_id &&
|
||||
action.payload.data.source_node_id === nodeId
|
||||
);
|
||||
|
||||
// We still have to check the output type
|
||||
if (isImageOutput(invocationCompleteAction.payload.data.result)) {
|
||||
const { image_name } =
|
||||
invocationCompleteAction.payload.data.result.image;
|
||||
const { image_name } = invocationCompleteAction.payload.data.result.image;
|
||||
|
||||
// Wait for the ImageDTO to be received
|
||||
const [{ payload }] = await take(
|
||||
(action) =>
|
||||
imagesApi.endpoints.getImageDTO.matchFulfilled(action) &&
|
||||
action.payload.image_name === image_name
|
||||
imagesApi.endpoints.getImageDTO.matchFulfilled(action) && action.payload.image_name === image_name
|
||||
);
|
||||
|
||||
const processedControlImage = payload as ImageDTO;
|
||||
|
||||
log.debug(
|
||||
{ controlNetId: action.payload, processedControlImage },
|
||||
'ControlNet image processed'
|
||||
);
|
||||
log.debug({ controlNetId: action.payload, processedControlImage }, 'ControlNet image processed');
|
||||
|
||||
// Update the processed image in the store
|
||||
dispatch(
|
||||
@@ -105,10 +96,7 @@ export const addControlNetImageProcessedListener = () => {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
{ enqueueBatchArg: parseify(enqueueBatchArg) },
|
||||
t('queue.graphFailedToQueue')
|
||||
);
|
||||
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||
|
||||
if (error instanceof Object) {
|
||||
if ('data' in error && 'status' in error) {
|
||||
|
||||
@@ -2,10 +2,7 @@ import { logger } from 'app/logging/logger';
|
||||
import { enqueueRequested } from 'app/store/actions';
|
||||
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
canvasBatchIdAdded,
|
||||
stagingAreaInitialized,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { canvasBatchIdAdded, stagingAreaInitialized } from 'features/canvas/store/canvasSlice';
|
||||
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
||||
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
||||
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
|
||||
@@ -34,20 +31,14 @@ import { startAppListening } from '..';
|
||||
export const addEnqueueRequestedCanvasListener = () => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
enqueueRequested.match(action) &&
|
||||
action.payload.tabName === 'unifiedCanvas',
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'unifiedCanvas',
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const log = logger('queue');
|
||||
const { prepend } = action.payload;
|
||||
const state = getState();
|
||||
|
||||
const {
|
||||
layerState,
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
isMaskEnabled,
|
||||
shouldPreserveMaskedArea,
|
||||
} = state.canvas;
|
||||
const { layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea } =
|
||||
state.canvas;
|
||||
|
||||
// Build canvas blobs
|
||||
const canvasBlobsAndImageData = await getCanvasData(
|
||||
@@ -63,14 +54,10 @@ export const addEnqueueRequestedCanvasListener = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { baseBlob, baseImageData, maskBlob, maskImageData } =
|
||||
canvasBlobsAndImageData;
|
||||
const { baseBlob, baseImageData, maskBlob, maskImageData } = canvasBlobsAndImageData;
|
||||
|
||||
// Determine the generation mode
|
||||
const generationMode = getCanvasGenerationMode(
|
||||
baseImageData,
|
||||
maskImageData
|
||||
);
|
||||
const generationMode = getCanvasGenerationMode(baseImageData, maskImageData);
|
||||
|
||||
if (state.system.enableImageDebugging) {
|
||||
const baseDataURL = await blobToDataURL(baseBlob);
|
||||
@@ -115,12 +102,7 @@ export const addEnqueueRequestedCanvasListener = () => {
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
const graph = buildCanvasGraph(
|
||||
state,
|
||||
generationMode,
|
||||
canvasInitImage,
|
||||
canvasMaskImage
|
||||
);
|
||||
const graph = buildCanvasGraph(state, generationMode, canvasInitImage, canvasMaskImage);
|
||||
|
||||
log.debug({ graph: parseify(graph) }, `Canvas graph built`);
|
||||
|
||||
|
||||
@@ -11,9 +11,7 @@ import { startAppListening } from '..';
|
||||
export const addEnqueueRequestedLinear = () => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
enqueueRequested.match(action) &&
|
||||
(action.payload.tabName === 'txt2img' ||
|
||||
action.payload.tabName === 'img2img'),
|
||||
enqueueRequested.match(action) && (action.payload.tabName === 'txt2img' || action.payload.tabName === 'img2img'),
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const state = getState();
|
||||
const model = state.generation.model;
|
||||
|
||||
@@ -32,8 +32,7 @@ export const addGalleryImageClickedListener = () => {
|
||||
const { imageDTO, shiftKey, ctrlKey, metaKey } = action.payload;
|
||||
const state = getState();
|
||||
const queryArgs = selectListImagesQueryArgs(state);
|
||||
const { data: listImagesData } =
|
||||
imagesApi.endpoints.listImages.select(queryArgs)(state);
|
||||
const { data: listImagesData } = imagesApi.endpoints.listImages.select(queryArgs)(state);
|
||||
|
||||
if (!listImagesData) {
|
||||
// Should never happen if we have clicked a gallery image
|
||||
@@ -46,12 +45,8 @@ export const addGalleryImageClickedListener = () => {
|
||||
if (shiftKey) {
|
||||
const rangeEndImageName = imageDTO.image_name;
|
||||
const lastSelectedImage = selection[selection.length - 1]?.image_name;
|
||||
const lastClickedIndex = imageDTOs.findIndex(
|
||||
(n) => n.image_name === lastSelectedImage
|
||||
);
|
||||
const currentClickedIndex = imageDTOs.findIndex(
|
||||
(n) => n.image_name === rangeEndImageName
|
||||
);
|
||||
const lastClickedIndex = imageDTOs.findIndex((n) => n.image_name === lastSelectedImage);
|
||||
const currentClickedIndex = imageDTOs.findIndex((n) => n.image_name === rangeEndImageName);
|
||||
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
||||
// We have a valid range!
|
||||
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
||||
@@ -60,15 +55,8 @@ export const addGalleryImageClickedListener = () => {
|
||||
dispatch(selectionChanged(selection.concat(imagesToSelect)));
|
||||
}
|
||||
} else if (ctrlKey || metaKey) {
|
||||
if (
|
||||
selection.some((i) => i.image_name === imageDTO.image_name) &&
|
||||
selection.length > 1
|
||||
) {
|
||||
dispatch(
|
||||
selectionChanged(
|
||||
selection.filter((n) => n.image_name !== imageDTO.image_name)
|
||||
)
|
||||
);
|
||||
if (selection.some((i) => i.image_name === imageDTO.image_name) && selection.length > 1) {
|
||||
dispatch(selectionChanged(selection.filter((n) => n.image_name !== imageDTO.image_name)));
|
||||
} else {
|
||||
dispatch(selectionChanged(selection.concat(imageDTO)));
|
||||
}
|
||||
|
||||
@@ -43,31 +43,21 @@ export const addRequestedSingleImageDeletionListener = () => {
|
||||
dispatch(isModalOpenChanged(false));
|
||||
|
||||
const state = getState();
|
||||
const lastSelectedImage =
|
||||
state.gallery.selection[state.gallery.selection.length - 1]?.image_name;
|
||||
const lastSelectedImage = state.gallery.selection[state.gallery.selection.length - 1]?.image_name;
|
||||
|
||||
if (imageDTO && imageDTO?.image_name === lastSelectedImage) {
|
||||
const { image_name } = imageDTO;
|
||||
|
||||
const baseQueryArgs = selectListImagesQueryArgs(state);
|
||||
const { data } =
|
||||
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
|
||||
const { data } = imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
|
||||
|
||||
const cachedImageDTOs = data ? imagesSelectors.selectAll(data) : [];
|
||||
|
||||
const deletedImageIndex = cachedImageDTOs.findIndex(
|
||||
(i) => i.image_name === image_name
|
||||
);
|
||||
const deletedImageIndex = cachedImageDTOs.findIndex((i) => i.image_name === image_name);
|
||||
|
||||
const filteredImageDTOs = cachedImageDTOs.filter(
|
||||
(i) => i.image_name !== image_name
|
||||
);
|
||||
const filteredImageDTOs = cachedImageDTOs.filter((i) => i.image_name !== image_name);
|
||||
|
||||
const newSelectedImageIndex = clamp(
|
||||
deletedImageIndex,
|
||||
0,
|
||||
filteredImageDTOs.length - 1
|
||||
);
|
||||
const newSelectedImageIndex = clamp(deletedImageIndex, 0, filteredImageDTOs.length - 1);
|
||||
|
||||
const newSelectedImageDTO = filteredImageDTOs[newSelectedImageIndex];
|
||||
|
||||
@@ -85,9 +75,7 @@ export const addRequestedSingleImageDeletionListener = () => {
|
||||
|
||||
imageDTOs.forEach((imageDTO) => {
|
||||
// reset init image if we deleted it
|
||||
if (
|
||||
getState().generation.initialImage?.imageName === imageDTO.image_name
|
||||
) {
|
||||
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
||||
dispatch(clearInitialImage());
|
||||
}
|
||||
|
||||
@@ -95,8 +83,7 @@ export const addRequestedSingleImageDeletionListener = () => {
|
||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
||||
if (
|
||||
ca.controlImage === imageDTO.image_name ||
|
||||
(isControlNetOrT2IAdapter(ca) &&
|
||||
ca.processedControlImage === imageDTO.image_name)
|
||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||
) {
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
@@ -120,10 +107,7 @@ export const addRequestedSingleImageDeletionListener = () => {
|
||||
}
|
||||
|
||||
forEach(node.data.inputs, (input) => {
|
||||
if (
|
||||
isImageFieldInputInstance(input) &&
|
||||
input.value?.image_name === imageDTO.image_name
|
||||
) {
|
||||
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||
dispatch(
|
||||
fieldImageValueChanged({
|
||||
nodeId: node.data.id,
|
||||
@@ -137,24 +121,16 @@ export const addRequestedSingleImageDeletionListener = () => {
|
||||
});
|
||||
|
||||
// Delete from server
|
||||
const { requestId } = dispatch(
|
||||
imagesApi.endpoints.deleteImage.initiate(imageDTO)
|
||||
);
|
||||
const { requestId } = dispatch(imagesApi.endpoints.deleteImage.initiate(imageDTO));
|
||||
|
||||
// Wait for successful deletion, then trigger boards to re-fetch
|
||||
const wasImageDeleted = await condition(
|
||||
(action) =>
|
||||
imagesApi.endpoints.deleteImage.matchFulfilled(action) &&
|
||||
action.meta.requestId === requestId,
|
||||
(action) => imagesApi.endpoints.deleteImage.matchFulfilled(action) && action.meta.requestId === requestId,
|
||||
30000
|
||||
);
|
||||
|
||||
if (wasImageDeleted) {
|
||||
dispatch(
|
||||
api.util.invalidateTags([
|
||||
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
||||
])
|
||||
);
|
||||
dispatch(api.util.invalidateTags([{ type: 'Board', id: imageDTO.board_id ?? 'none' }]));
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -176,17 +152,12 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
||||
|
||||
try {
|
||||
// Delete from server
|
||||
await dispatch(
|
||||
imagesApi.endpoints.deleteImages.initiate({ imageDTOs })
|
||||
).unwrap();
|
||||
await dispatch(imagesApi.endpoints.deleteImages.initiate({ imageDTOs })).unwrap();
|
||||
const state = getState();
|
||||
const queryArgs = selectListImagesQueryArgs(state);
|
||||
const { data } =
|
||||
imagesApi.endpoints.listImages.select(queryArgs)(state);
|
||||
const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state);
|
||||
|
||||
const newSelectedImageDTO = data
|
||||
? imagesSelectors.selectAll(data)[0]
|
||||
: undefined;
|
||||
const newSelectedImageDTO = data ? imagesSelectors.selectAll(data)[0] : undefined;
|
||||
|
||||
if (newSelectedImageDTO) {
|
||||
dispatch(imageSelected(newSelectedImageDTO));
|
||||
@@ -204,10 +175,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
||||
|
||||
imageDTOs.forEach((imageDTO) => {
|
||||
// reset init image if we deleted it
|
||||
if (
|
||||
getState().generation.initialImage?.imageName ===
|
||||
imageDTO.image_name
|
||||
) {
|
||||
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
||||
dispatch(clearInitialImage());
|
||||
}
|
||||
|
||||
@@ -215,8 +183,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
||||
if (
|
||||
ca.controlImage === imageDTO.image_name ||
|
||||
(isControlNetOrT2IAdapter(ca) &&
|
||||
ca.processedControlImage === imageDTO.image_name)
|
||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||
) {
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
@@ -240,10 +207,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
||||
}
|
||||
|
||||
forEach(node.data.inputs, (input) => {
|
||||
if (
|
||||
isImageFieldInputInstance(input) &&
|
||||
input.value?.image_name === imageDTO.image_name
|
||||
) {
|
||||
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||
dispatch(
|
||||
fieldImageValueChanged({
|
||||
nodeId: node.data.id,
|
||||
@@ -295,10 +259,7 @@ export const addImageDeletedRejectedListener = () => {
|
||||
matcher: imagesApi.endpoints.deleteImage.matchRejected,
|
||||
effect: (action) => {
|
||||
const log = logger('images');
|
||||
log.debug(
|
||||
{ imageDTO: action.meta.arg.originalArgs },
|
||||
'Unable to delete image'
|
||||
);
|
||||
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Unable to delete image');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -6,16 +6,10 @@ import {
|
||||
controlAdapterImageChanged,
|
||||
controlAdapterIsEnabledChanged,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import type {
|
||||
TypesafeDraggableData,
|
||||
TypesafeDroppableData,
|
||||
} from 'features/dnd/types';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
initialImageChanged,
|
||||
selectOptimalDimension,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
import { startAppListening } from '../';
|
||||
@@ -35,15 +29,9 @@ export const addImageDroppedListener = () => {
|
||||
if (activeData.payloadType === 'IMAGE_DTO') {
|
||||
log.debug({ activeData, overData }, 'Image dropped');
|
||||
} else if (activeData.payloadType === 'GALLERY_SELECTION') {
|
||||
log.debug(
|
||||
{ activeData, overData },
|
||||
`Images (${getState().gallery.selection.length}) dropped`
|
||||
);
|
||||
log.debug({ activeData, overData }, `Images (${getState().gallery.selection.length}) dropped`);
|
||||
} else if (activeData.payloadType === 'NODE_FIELD') {
|
||||
log.debug(
|
||||
{ activeData: parseify(activeData), overData: parseify(overData) },
|
||||
'Node field dropped'
|
||||
);
|
||||
log.debug({ activeData: parseify(activeData), overData: parseify(overData) }, 'Node field dropped');
|
||||
} else {
|
||||
log.debug({ activeData, overData }, `Unknown payload dropped`);
|
||||
}
|
||||
@@ -104,12 +92,7 @@ export const addImageDroppedListener = () => {
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(
|
||||
setInitialCanvasImage(
|
||||
activeData.payload.imageDTO,
|
||||
selectOptimalDimension(getState())
|
||||
)
|
||||
);
|
||||
dispatch(setInitialCanvasImage(activeData.payload.imageDTO, selectOptimalDimension(getState())));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -191,10 +174,7 @@ export const addImageDroppedListener = () => {
|
||||
/**
|
||||
* Multiple images dropped on user board
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'ADD_TO_BOARD' &&
|
||||
activeData.payloadType === 'GALLERY_SELECTION'
|
||||
) {
|
||||
if (overData.actionType === 'ADD_TO_BOARD' && activeData.payloadType === 'GALLERY_SELECTION') {
|
||||
const imageDTOs = getState().gallery.selection;
|
||||
const { boardId } = overData.context;
|
||||
dispatch(
|
||||
@@ -209,10 +189,7 @@ export const addImageDroppedListener = () => {
|
||||
/**
|
||||
* Multiple images dropped on 'none' board
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'REMOVE_FROM_BOARD' &&
|
||||
activeData.payloadType === 'GALLERY_SELECTION'
|
||||
) {
|
||||
if (overData.actionType === 'REMOVE_FROM_BOARD' && activeData.payloadType === 'GALLERY_SELECTION') {
|
||||
const imageDTOs = getState().gallery.selection;
|
||||
dispatch(
|
||||
imagesApi.endpoints.removeImagesFromBoard.initiate({
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||
import { selectImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||
import {
|
||||
imagesToDeleteSelected,
|
||||
isModalOpenChanged,
|
||||
} from 'features/deleteImageModal/store/slice';
|
||||
import { imagesToDeleteSelected, isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||
|
||||
import { startAppListening } from '..';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UseToastOptions } from '@invoke-ai/ui';
|
||||
import type { UseToastOptions } from '@invoke-ai/ui-library';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
@@ -6,10 +6,7 @@ import {
|
||||
controlAdapterIsEnabledChanged,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
initialImageChanged,
|
||||
selectOptimalDimension,
|
||||
} 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';
|
||||
@@ -79,9 +76,7 @@ export const addImageUploadedFulfilledListener = () => {
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
|
||||
dispatch(
|
||||
setInitialCanvasImage(imageDTO, selectOptimalDimension(state))
|
||||
);
|
||||
dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state)));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
@@ -127,9 +122,7 @@ export const addImageUploadedFulfilledListener = () => {
|
||||
|
||||
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
||||
const { nodeId, fieldName } = postUploadAction;
|
||||
dispatch(
|
||||
fieldImageValueChanged({ nodeId, fieldName, value: imageDTO })
|
||||
);
|
||||
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
|
||||
@@ -11,11 +11,7 @@ export const addInitialImageSelectedListener = () => {
|
||||
actionCreator: initialImageSelected,
|
||||
effect: (action, { dispatch }) => {
|
||||
if (!action.payload) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })
|
||||
)
|
||||
);
|
||||
dispatch(addToast(makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ import {
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import {
|
||||
modelChanged,
|
||||
vaeSelected,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { modelChanged, 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';
|
||||
@@ -27,18 +24,14 @@ export const addModelSelectedListener = () => {
|
||||
const result = zParameterModel.safeParse(action.payload);
|
||||
|
||||
if (!result.success) {
|
||||
log.error(
|
||||
{ error: result.error.format() },
|
||||
'Failed to parse main model'
|
||||
);
|
||||
log.error({ error: result.error.format() }, 'Failed to parse main model');
|
||||
return;
|
||||
}
|
||||
|
||||
const newModel = result.data;
|
||||
|
||||
const { base_model } = newModel;
|
||||
const didBaseModelChange =
|
||||
state.generation.model?.base_model !== base_model;
|
||||
const newBaseModel = newModel.base_model;
|
||||
const didBaseModelChange = state.generation.model?.base_model !== newBaseModel;
|
||||
|
||||
if (didBaseModelChange) {
|
||||
// we may need to reset some incompatible submodels
|
||||
@@ -46,7 +39,7 @@ export const addModelSelectedListener = () => {
|
||||
|
||||
// handle incompatible loras
|
||||
forEach(state.lora.loras, (lora, id) => {
|
||||
if (lora.base_model !== base_model) {
|
||||
if (lora.base_model !== newBaseModel) {
|
||||
dispatch(loraRemoved(id));
|
||||
modelsCleared += 1;
|
||||
}
|
||||
@@ -54,17 +47,15 @@ export const addModelSelectedListener = () => {
|
||||
|
||||
// handle incompatible vae
|
||||
const { vae } = state.generation;
|
||||
if (vae && vae.base_model !== base_model) {
|
||||
if (vae && vae.base_model !== newBaseModel) {
|
||||
dispatch(vaeSelected(null));
|
||||
modelsCleared += 1;
|
||||
}
|
||||
|
||||
// handle incompatible controlnets
|
||||
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
||||
if (ca.model?.base_model !== base_model) {
|
||||
dispatch(
|
||||
controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false })
|
||||
);
|
||||
if (ca.model?.base_model !== newBaseModel) {
|
||||
dispatch(controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false }));
|
||||
modelsCleared += 1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,41 +6,24 @@ import {
|
||||
selectAllT2IAdapters,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||
import {
|
||||
modelChanged,
|
||||
vaeSelected,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
zParameterModel,
|
||||
zParameterVAEModel,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
|
||||
import { forEach, some } from 'lodash-es';
|
||||
import {
|
||||
mainModelsAdapterSelectors,
|
||||
modelsApi,
|
||||
vaeModelsAdapterSelectors,
|
||||
} from 'services/api/endpoints/models';
|
||||
import { mainModelsAdapterSelectors, modelsApi, vaeModelsAdapterSelectors } from 'services/api/endpoints/models';
|
||||
import type { TypeGuardFor } from 'services/api/types';
|
||||
|
||||
import { startAppListening } from '..';
|
||||
|
||||
export const addModelsLoadedListener = () => {
|
||||
startAppListening({
|
||||
predicate: (
|
||||
action
|
||||
): action is TypeGuardFor<
|
||||
typeof modelsApi.endpoints.getMainModels.matchFulfilled
|
||||
> =>
|
||||
predicate: (action): action is TypeGuardFor<typeof modelsApi.endpoints.getMainModels.matchFulfilled> =>
|
||||
modelsApi.endpoints.getMainModels.matchFulfilled(action) &&
|
||||
!action.meta.arg.originalArgs.includes('sdxl-refiner'),
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`Main models loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `Main models loaded (${action.payload.ids.length})`);
|
||||
|
||||
const currentModel = getState().generation.model;
|
||||
const models = mainModelsAdapterSelectors.selectAll(action.payload);
|
||||
@@ -67,10 +50,7 @@ export const addModelsLoadedListener = () => {
|
||||
const result = zParameterModel.safeParse(models[0]);
|
||||
|
||||
if (!result.success) {
|
||||
log.error(
|
||||
{ error: result.error.format() },
|
||||
'Failed to parse main model'
|
||||
);
|
||||
log.error({ error: result.error.format() }, 'Failed to parse main model');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,20 +58,12 @@ export const addModelsLoadedListener = () => {
|
||||
},
|
||||
});
|
||||
startAppListening({
|
||||
predicate: (
|
||||
action
|
||||
): action is TypeGuardFor<
|
||||
typeof modelsApi.endpoints.getMainModels.matchFulfilled
|
||||
> =>
|
||||
modelsApi.endpoints.getMainModels.matchFulfilled(action) &&
|
||||
action.meta.arg.originalArgs.includes('sdxl-refiner'),
|
||||
predicate: (action): action is TypeGuardFor<typeof modelsApi.endpoints.getMainModels.matchFulfilled> =>
|
||||
modelsApi.endpoints.getMainModels.matchFulfilled(action) && action.meta.arg.originalArgs.includes('sdxl-refiner'),
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`SDXL Refiner models loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `SDXL Refiner models loaded (${action.payload.ids.length})`);
|
||||
|
||||
const currentModel = getState().sdxl.refinerModel;
|
||||
const models = mainModelsAdapterSelectors.selectAll(action.payload);
|
||||
@@ -122,10 +94,7 @@ export const addModelsLoadedListener = () => {
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// VAEs loaded, need to reset the VAE is it's no longer available
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`VAEs loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `VAEs loaded (${action.payload.ids.length})`);
|
||||
|
||||
const currentVae = getState().generation.vae;
|
||||
|
||||
@@ -136,9 +105,7 @@ export const addModelsLoadedListener = () => {
|
||||
|
||||
const isCurrentVAEAvailable = some(
|
||||
action.payload.entities,
|
||||
(m) =>
|
||||
m?.model_name === currentVae?.model_name &&
|
||||
m?.base_model === currentVae?.base_model
|
||||
(m) => m?.model_name === currentVae?.model_name && m?.base_model === currentVae?.base_model
|
||||
);
|
||||
|
||||
if (isCurrentVAEAvailable) {
|
||||
@@ -156,10 +123,7 @@ export const addModelsLoadedListener = () => {
|
||||
const result = zParameterVAEModel.safeParse(firstModel);
|
||||
|
||||
if (!result.success) {
|
||||
log.error(
|
||||
{ error: result.error.format() },
|
||||
'Failed to parse VAE model'
|
||||
);
|
||||
log.error({ error: result.error.format() }, 'Failed to parse VAE model');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,19 +135,14 @@ export const addModelsLoadedListener = () => {
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// LoRA models loaded - need to remove missing LoRAs from state
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`LoRAs loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `LoRAs loaded (${action.payload.ids.length})`);
|
||||
|
||||
const loras = getState().lora.loras;
|
||||
|
||||
forEach(loras, (lora, id) => {
|
||||
const isLoRAAvailable = some(
|
||||
action.payload.entities,
|
||||
(m) =>
|
||||
m?.model_name === lora?.model_name &&
|
||||
m?.base_model === lora?.base_model
|
||||
(m) => m?.model_name === lora?.model_name && m?.base_model === lora?.base_model
|
||||
);
|
||||
|
||||
if (isLoRAAvailable) {
|
||||
@@ -199,17 +158,12 @@ export const addModelsLoadedListener = () => {
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// ControlNet models loaded - need to remove missing ControlNets from state
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`ControlNet models loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `ControlNet models loaded (${action.payload.ids.length})`);
|
||||
|
||||
selectAllControlNets(getState().controlAdapters).forEach((ca) => {
|
||||
const isModelAvailable = some(
|
||||
action.payload.entities,
|
||||
(m) =>
|
||||
m?.model_name === ca?.model?.model_name &&
|
||||
m?.base_model === ca?.model?.base_model
|
||||
(m) => m?.model_name === ca?.model?.model_name && m?.base_model === ca?.model?.base_model
|
||||
);
|
||||
|
||||
if (isModelAvailable) {
|
||||
@@ -225,17 +179,12 @@ export const addModelsLoadedListener = () => {
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// ControlNet models loaded - need to remove missing ControlNets from state
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`T2I Adapter models loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `T2I Adapter models loaded (${action.payload.ids.length})`);
|
||||
|
||||
selectAllT2IAdapters(getState().controlAdapters).forEach((ca) => {
|
||||
const isModelAvailable = some(
|
||||
action.payload.entities,
|
||||
(m) =>
|
||||
m?.model_name === ca?.model?.model_name &&
|
||||
m?.base_model === ca?.model?.base_model
|
||||
(m) => m?.model_name === ca?.model?.model_name && m?.base_model === ca?.model?.base_model
|
||||
);
|
||||
|
||||
if (isModelAvailable) {
|
||||
@@ -251,17 +200,12 @@ export const addModelsLoadedListener = () => {
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
// ControlNet models loaded - need to remove missing ControlNets from state
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`IP Adapter models loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `IP Adapter models loaded (${action.payload.ids.length})`);
|
||||
|
||||
selectAllIPAdapters(getState().controlAdapters).forEach((ca) => {
|
||||
const isModelAvailable = some(
|
||||
action.payload.entities,
|
||||
(m) =>
|
||||
m?.model_name === ca?.model?.model_name &&
|
||||
m?.base_model === ca?.model?.base_model
|
||||
(m) => m?.model_name === ca?.model?.model_name && m?.base_model === ca?.model?.base_model
|
||||
);
|
||||
|
||||
if (isModelAvailable) {
|
||||
@@ -276,10 +220,7 @@ export const addModelsLoadedListener = () => {
|
||||
matcher: modelsApi.endpoints.getTextualInversionModels.matchFulfilled,
|
||||
effect: async (action) => {
|
||||
const log = logger('models');
|
||||
log.info(
|
||||
{ models: action.payload.entities },
|
||||
`Embeddings loaded (${action.payload.ids.length})`
|
||||
);
|
||||
log.info({ models: action.payload.entities }, `Embeddings loaded (${action.payload.ids.length})`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,21 +15,12 @@ import { socketConnected } from 'services/events/actions';
|
||||
|
||||
import { startAppListening } from '..';
|
||||
|
||||
const matcher = isAnyOf(
|
||||
setPositivePrompt,
|
||||
combinatorialToggled,
|
||||
maxPromptsChanged,
|
||||
maxPromptsReset,
|
||||
socketConnected
|
||||
);
|
||||
const matcher = isAnyOf(setPositivePrompt, combinatorialToggled, maxPromptsChanged, maxPromptsReset, socketConnected);
|
||||
|
||||
export const addDynamicPromptsListener = () => {
|
||||
startAppListening({
|
||||
matcher,
|
||||
effect: async (
|
||||
action,
|
||||
{ dispatch, getState, cancelActiveListeners, delay }
|
||||
) => {
|
||||
effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => {
|
||||
cancelActiveListeners();
|
||||
const state = getState();
|
||||
const { positivePrompt } = state.generation;
|
||||
@@ -46,14 +37,14 @@ export const addDynamicPromptsListener = () => {
|
||||
|
||||
if (cachedPrompts) {
|
||||
dispatch(promptsChanged(cachedPrompts.prompts));
|
||||
dispatch(parsingErrorChanged(cachedPrompts.error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getShouldProcessPrompt(state.generation.positivePrompt)) {
|
||||
if (state.dynamicPrompts.isLoading) {
|
||||
dispatch(isLoadingChanged(false));
|
||||
}
|
||||
dispatch(promptsChanged([state.generation.positivePrompt]));
|
||||
dispatch(parsingErrorChanged(undefined));
|
||||
dispatch(isErrorChanged(false));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,7 +69,6 @@ export const addDynamicPromptsListener = () => {
|
||||
dispatch(promptsChanged(res.prompts));
|
||||
dispatch(parsingErrorChanged(res.error));
|
||||
dispatch(isErrorChanged(false));
|
||||
dispatch(isLoadingChanged(false));
|
||||
} catch {
|
||||
dispatch(isErrorChanged(true));
|
||||
dispatch(isLoadingChanged(false));
|
||||
|
||||
@@ -17,16 +17,9 @@ export const addReceivedOpenAPISchemaListener = () => {
|
||||
log.debug({ schemaJSON }, 'Received OpenAPI schema');
|
||||
const { nodesAllowlist, nodesDenylist } = getState().config;
|
||||
|
||||
const nodeTemplates = parseSchema(
|
||||
schemaJSON,
|
||||
nodesAllowlist,
|
||||
nodesDenylist
|
||||
);
|
||||
const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist);
|
||||
|
||||
log.debug(
|
||||
{ nodeTemplates: parseify(nodeTemplates) },
|
||||
`Built ${size(nodeTemplates)} node templates`
|
||||
);
|
||||
log.debug({ nodeTemplates: parseify(nodeTemplates) }, `Built ${size(nodeTemplates)} node templates`);
|
||||
|
||||
dispatch(nodeTemplatesBuilt(nodeTemplates));
|
||||
},
|
||||
@@ -36,10 +29,7 @@ export const addReceivedOpenAPISchemaListener = () => {
|
||||
actionCreator: receivedOpenAPISchema.rejected,
|
||||
effect: (action) => {
|
||||
const log = logger('system');
|
||||
log.error(
|
||||
{ error: parseify(action.error) },
|
||||
'Problem retrieving OpenAPI Schema'
|
||||
);
|
||||
log.error({ error: parseify(action.error) }, 'Problem retrieving OpenAPI Schema');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -16,10 +16,7 @@ const $isFirstConnection = atom(true);
|
||||
export const addSocketConnectedEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketConnected,
|
||||
effect: async (
|
||||
action,
|
||||
{ dispatch, getState, cancelActiveListeners, delay }
|
||||
) => {
|
||||
effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => {
|
||||
log.debug('Connected');
|
||||
|
||||
/**
|
||||
@@ -86,10 +83,7 @@ export const addSocketConnectedEventListener = () => {
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
const { nodeTemplates, config } = getState();
|
||||
// We only want to re-fetch the schema if we don't have any node templates
|
||||
if (
|
||||
!size(nodeTemplates.templates) &&
|
||||
!config.disabledTabs.includes('nodes')
|
||||
) {
|
||||
if (!size(nodeTemplates.templates) && !config.disabledTabs.includes('nodes')) {
|
||||
// This request is a createAsyncThunk - resetting API state as in the above listener
|
||||
// will not trigger this request, so we need to manually do it.
|
||||
dispatch(receivedOpenAPISchema());
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
boardIdSelected,
|
||||
galleryViewChanged,
|
||||
imageSelected,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||
import { isImageOutput } from 'features/nodes/types/common';
|
||||
import {
|
||||
LINEAR_UI_OUTPUT,
|
||||
nodeIDDenyList,
|
||||
} from 'features/nodes/util/graph/constants';
|
||||
import { LINEAR_UI_OUTPUT, nodeIDDenyList } from 'features/nodes/util/graph/constants';
|
||||
import { boardsApi } from 'services/api/endpoints/boards';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import { imagesAdapter } from 'services/api/util';
|
||||
@@ -29,19 +22,12 @@ export const addInvocationCompleteEventListener = () => {
|
||||
actionCreator: socketInvocationComplete,
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
const { data } = action.payload;
|
||||
log.debug(
|
||||
{ data: parseify(data) },
|
||||
`Invocation complete (${action.payload.data.node.type})`
|
||||
);
|
||||
log.debug({ data: parseify(data) }, `Invocation complete (${action.payload.data.node.type})`);
|
||||
|
||||
const { result, node, queue_batch_id, source_node_id } = data;
|
||||
|
||||
// This complete event has an associated image output
|
||||
if (
|
||||
isImageOutput(result) &&
|
||||
!nodeTypeDenylist.includes(node.type) &&
|
||||
!nodeIDDenyList.includes(source_node_id)
|
||||
) {
|
||||
if (isImageOutput(result) && !nodeTypeDenylist.includes(node.type) && !nodeIDDenyList.includes(source_node_id)) {
|
||||
const { image_name } = result.image;
|
||||
const { canvas, gallery } = getState();
|
||||
|
||||
@@ -56,10 +42,7 @@ export const addInvocationCompleteEventListener = () => {
|
||||
imageDTORequest.unsubscribe();
|
||||
|
||||
// Add canvas images to the staging area
|
||||
if (
|
||||
canvas.batchIds.includes(queue_batch_id) &&
|
||||
[LINEAR_UI_OUTPUT].includes(data.source_node_id)
|
||||
) {
|
||||
if (canvas.batchIds.includes(queue_batch_id) && [LINEAR_UI_OUTPUT].includes(data.source_node_id)) {
|
||||
dispatch(addImageToStagingArea(imageDTO));
|
||||
}
|
||||
|
||||
@@ -84,21 +67,13 @@ export const addInvocationCompleteEventListener = () => {
|
||||
|
||||
// update the total images for the board
|
||||
dispatch(
|
||||
boardsApi.util.updateQueryData(
|
||||
'getBoardImagesTotal',
|
||||
imageDTO.board_id ?? 'none',
|
||||
(draft) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
draft.total += 1;
|
||||
}
|
||||
)
|
||||
boardsApi.util.updateQueryData('getBoardImagesTotal', imageDTO.board_id ?? 'none', (draft) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
draft.total += 1;
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(
|
||||
imagesApi.util.invalidateTags([
|
||||
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
||||
])
|
||||
);
|
||||
dispatch(imagesApi.util.invalidateTags([{ type: 'Board', id: imageDTO.board_id ?? 'none' }]));
|
||||
|
||||
const { shouldAutoSwitch } = gallery;
|
||||
|
||||
@@ -109,10 +84,7 @@ export const addInvocationCompleteEventListener = () => {
|
||||
dispatch(galleryViewChanged('images'));
|
||||
}
|
||||
|
||||
if (
|
||||
imageDTO.board_id &&
|
||||
imageDTO.board_id !== gallery.selectedBoardId
|
||||
) {
|
||||
if (imageDTO.board_id && imageDTO.board_id !== gallery.selectedBoardId) {
|
||||
dispatch(
|
||||
boardIdSelected({
|
||||
boardId: imageDTO.board_id,
|
||||
|
||||
@@ -9,10 +9,7 @@ export const addInvocationErrorEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketInvocationError,
|
||||
effect: (action) => {
|
||||
log.error(
|
||||
action.payload,
|
||||
`Invocation error (${action.payload.data.node.type})`
|
||||
);
|
||||
log.error(action.payload, `Invocation error (${action.payload.data.node.type})`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,10 +9,7 @@ export const addInvocationRetrievalErrorEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketInvocationRetrievalError,
|
||||
effect: (action) => {
|
||||
log.error(
|
||||
action.payload,
|
||||
`Invocation retrieval error (${action.payload.data.graph_execution_state_id})`
|
||||
);
|
||||
log.error(action.payload, `Invocation retrieval error (${action.payload.data.graph_execution_state_id})`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,10 +9,7 @@ export const addInvocationStartedEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketInvocationStarted,
|
||||
effect: (action) => {
|
||||
log.debug(
|
||||
action.payload,
|
||||
`Invocation started (${action.payload.data.node.type})`
|
||||
);
|
||||
log.debug(action.payload, `Invocation started (${action.payload.data.node.type})`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import {
|
||||
socketModelLoadCompleted,
|
||||
socketModelLoadStarted,
|
||||
} from 'services/events/actions';
|
||||
import { socketModelLoadCompleted, socketModelLoadStarted } from 'services/events/actions';
|
||||
|
||||
import { startAppListening } from '../..';
|
||||
|
||||
@@ -12,8 +9,7 @@ export const addModelLoadEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketModelLoadStarted,
|
||||
effect: (action) => {
|
||||
const { base_model, model_name, model_type, submodel } =
|
||||
action.payload.data;
|
||||
const { base_model, model_name, model_type, submodel } = action.payload.data;
|
||||
|
||||
let message = `Model load started: ${base_model}/${model_type}/${model_name}`;
|
||||
|
||||
@@ -28,8 +24,7 @@ export const addModelLoadEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketModelLoadCompleted,
|
||||
effect: (action) => {
|
||||
const { base_model, model_name, model_type, submodel } =
|
||||
action.payload.data;
|
||||
const { base_model, model_name, model_type, submodel } = action.payload.data;
|
||||
|
||||
let message = `Model load complete: ${base_model}/${model_type}/${model_name}`;
|
||||
|
||||
|
||||
@@ -13,10 +13,7 @@ export const addSocketQueueItemStatusChangedEventListener = () => {
|
||||
// we've got new status for the queue item, batch and queue
|
||||
const { queue_item, batch_status, queue_status } = action.payload.data;
|
||||
|
||||
log.debug(
|
||||
action.payload,
|
||||
`Queue item ${queue_item.item_id} status updated: ${queue_item.status}`
|
||||
);
|
||||
log.debug(action.payload, `Queue item ${queue_item.item_id} status updated: ${queue_item.status}`);
|
||||
|
||||
// Update this specific queue item in the list of queue items (this is the queue item DTO, without the session)
|
||||
dispatch(
|
||||
@@ -40,35 +37,23 @@ export const addSocketQueueItemStatusChangedEventListener = () => {
|
||||
|
||||
// Update the batch status
|
||||
dispatch(
|
||||
queueApi.util.updateQueryData(
|
||||
'getBatchStatus',
|
||||
{ batch_id: batch_status.batch_id },
|
||||
() => batch_status
|
||||
)
|
||||
queueApi.util.updateQueryData('getBatchStatus', { batch_id: batch_status.batch_id }, () => batch_status)
|
||||
);
|
||||
|
||||
// Update the queue item status (this is the full queue item, including the session)
|
||||
dispatch(
|
||||
queueApi.util.updateQueryData(
|
||||
'getQueueItem',
|
||||
queue_item.item_id,
|
||||
(draft) => {
|
||||
if (!draft) {
|
||||
return;
|
||||
}
|
||||
Object.assign(draft, queue_item);
|
||||
queueApi.util.updateQueryData('getQueueItem', queue_item.item_id, (draft) => {
|
||||
if (!draft) {
|
||||
return;
|
||||
}
|
||||
)
|
||||
Object.assign(draft, queue_item);
|
||||
})
|
||||
);
|
||||
|
||||
// Invalidate caches for things we cannot update
|
||||
// TODO: technically, we could possibly update the current session queue item, but feels safer to just request it again
|
||||
dispatch(
|
||||
queueApi.util.invalidateTags([
|
||||
'CurrentSessionQueueItem',
|
||||
'NextSessionQueueItem',
|
||||
'InvocationCacheStatus',
|
||||
])
|
||||
queueApi.util.invalidateTags(['CurrentSessionQueueItem', 'NextSessionQueueItem', 'InvocationCacheStatus'])
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,10 +9,7 @@ export const addSessionRetrievalErrorEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketSessionRetrievalError,
|
||||
effect: (action) => {
|
||||
log.error(
|
||||
action.payload,
|
||||
`Session retrieval error (${action.payload.data.graph_execution_state_id})`
|
||||
);
|
||||
log.error(action.payload, `Session retrieval error (${action.payload.data.graph_execution_state_id})`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,10 +3,7 @@ import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
||||
import { nodeReplaced } from 'features/nodes/store/nodesSlice';
|
||||
import { NodeUpdateError } from 'features/nodes/types/error';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import {
|
||||
getNeedsUpdate,
|
||||
updateNode,
|
||||
} from 'features/nodes/util/node/nodeUpdate';
|
||||
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { t } from 'i18next';
|
||||
|
||||
@@ -10,9 +10,7 @@ import type { BatchConfig, ImageDTO } from 'services/api/types';
|
||||
|
||||
import { startAppListening } from '..';
|
||||
|
||||
export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>(
|
||||
`upscale/upscaleRequested`
|
||||
);
|
||||
export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>(`upscale/upscaleRequested`);
|
||||
|
||||
export const addUpscaleRequestedListener = () => {
|
||||
startAppListening({
|
||||
@@ -24,8 +22,7 @@ export const addUpscaleRequestedListener = () => {
|
||||
const { image_name } = imageDTO;
|
||||
const state = getState();
|
||||
|
||||
const { isAllowedToUpscale, detailTKey } =
|
||||
createIsAllowedToUpscaleSelector(imageDTO)(state);
|
||||
const { isAllowedToUpscale, detailTKey } = createIsAllowedToUpscaleSelector(imageDTO)(state);
|
||||
|
||||
// if we can't upscale, show a toast and return
|
||||
if (!isAllowedToUpscale) {
|
||||
@@ -66,21 +63,11 @@ export const addUpscaleRequestedListener = () => {
|
||||
|
||||
const enqueueResult = await req.unwrap();
|
||||
req.reset();
|
||||
log.debug(
|
||||
{ enqueueResult: parseify(enqueueResult) },
|
||||
t('queue.graphQueued')
|
||||
);
|
||||
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||
} catch (error) {
|
||||
log.error(
|
||||
{ enqueueBatchArg: parseify(enqueueBatchArg) },
|
||||
t('queue.graphFailedToQueue')
|
||||
);
|
||||
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||
|
||||
if (
|
||||
error instanceof Object &&
|
||||
'status' in error &&
|
||||
error.status === 403
|
||||
) {
|
||||
if (error instanceof Object && 'status' in error && error.status === 403) {
|
||||
return;
|
||||
} else {
|
||||
dispatch(
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
workflowLoaded,
|
||||
workflowLoadRequested,
|
||||
} from 'features/nodes/store/actions';
|
||||
import { workflowLoaded, workflowLoadRequested } from 'features/nodes/store/actions';
|
||||
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
||||
import {
|
||||
WorkflowMigrationError,
|
||||
WorkflowVersionError,
|
||||
} from 'features/nodes/types/error';
|
||||
import { WorkflowMigrationError, WorkflowVersionError } from 'features/nodes/types/error';
|
||||
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
@@ -28,10 +22,7 @@ export const addWorkflowLoadRequestedListener = () => {
|
||||
const nodeTemplates = getState().nodeTemplates.templates;
|
||||
|
||||
try {
|
||||
const { workflow: validatedWorkflow, warnings } = validateWorkflow(
|
||||
workflow,
|
||||
nodeTemplates
|
||||
);
|
||||
const { workflow: validatedWorkflow, warnings } = validateWorkflow(workflow, nodeTemplates);
|
||||
|
||||
if (asCopy) {
|
||||
// If we're loading a copy, we need to remove the ID so that the backend will create a new workflow
|
||||
@@ -108,10 +99,7 @@ export const addWorkflowLoadRequestedListener = () => {
|
||||
);
|
||||
} else {
|
||||
// Some other error occurred
|
||||
log.error(
|
||||
{ error: parseify(e) },
|
||||
t('nodes.unknownErrorValidatingWorkflow')
|
||||
);
|
||||
log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow'));
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# nanostores
|
||||
|
||||
For non-serializable data that needs to be available throughout the app, or when redux is not appropriate, use nanostores.
|
||||
@@ -1,4 +1,3 @@
|
||||
import { atom } from 'nanostores';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const $customNavComponent = atom<ReactNode | undefined>(undefined);
|
||||
export const $customNavComponent = atom<undefined | (() => JSX.Element)>(undefined);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MenuItemProps } from '@invoke-ai/ui';
|
||||
import type { MenuItemProps } from '@invoke-ai/ui-library';
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
export type CustomStarUi = {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { atom } from 'nanostores';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const $galleryHeader = atom<ReactNode | undefined>(undefined);
|
||||
export const $galleryHeader = atom<undefined | (() => JSX.Element)>(undefined);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { atom } from 'nanostores';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const $logo = atom<ReactNode | undefined>(undefined);
|
||||
export const $logo = atom<undefined | (() => JSX.Element)>(undefined);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
export const $openAPISchemaUrl = atom<string | undefined>(undefined);
|
||||
@@ -8,6 +8,4 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export const $store = atom<
|
||||
Readonly<ReturnType<typeof createStore>> | undefined
|
||||
>();
|
||||
export const $store = atom<Readonly<ReturnType<typeof createStore>> | undefined>();
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
export const $workflowCategories = atom<WorkflowCategory[]>(['user', 'default']);
|
||||
@@ -1,17 +1,10 @@
|
||||
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
autoBatchEnhancer,
|
||||
combineReducers,
|
||||
configureStore,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { autoBatchEnhancer, combineReducers, configureStore } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
||||
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||
import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist';
|
||||
import canvasReducer, {
|
||||
initialCanvasState,
|
||||
migrateCanvasState,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import canvasReducer, { initialCanvasState, migrateCanvasState } from 'features/canvas/store/canvasSlice';
|
||||
import changeBoardModalReducer from 'features/changeBoardModal/store/slice';
|
||||
import { controlAdaptersPersistDenylist } from 'features/controlAdapters/store/controlAdaptersPersistDenylist';
|
||||
import controlAdaptersReducer, {
|
||||
@@ -25,32 +18,17 @@ import dynamicPromptsReducer, {
|
||||
migrateDynamicPromptsState,
|
||||
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||
import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist';
|
||||
import galleryReducer, {
|
||||
initialGalleryState,
|
||||
migrateGalleryState,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import hrfReducer, {
|
||||
initialHRFState,
|
||||
migrateHRFState,
|
||||
} from 'features/hrf/store/hrfSlice';
|
||||
import loraReducer, {
|
||||
initialLoraState,
|
||||
migrateLoRAState,
|
||||
} from 'features/lora/store/loraSlice';
|
||||
import galleryReducer, { initialGalleryState, migrateGalleryState } from 'features/gallery/store/gallerySlice';
|
||||
import hrfReducer, { initialHRFState, migrateHRFState } from 'features/hrf/store/hrfSlice';
|
||||
import loraReducer, { initialLoraState, migrateLoRAState } from 'features/lora/store/loraSlice';
|
||||
import modelmanagerReducer, {
|
||||
initialModelManagerState,
|
||||
migrateModelManagerState,
|
||||
} from 'features/modelManager/store/modelManagerSlice';
|
||||
import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist';
|
||||
import nodesReducer, {
|
||||
initialNodesState,
|
||||
migrateNodesState,
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import nodesReducer, { initialNodesState, migrateNodesState } from 'features/nodes/store/nodesSlice';
|
||||
import nodeTemplatesReducer from 'features/nodes/store/nodeTemplatesSlice';
|
||||
import workflowReducer, {
|
||||
initialWorkflowState,
|
||||
migrateWorkflowState,
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import workflowReducer, { initialWorkflowState, migrateWorkflowState } from 'features/nodes/store/workflowSlice';
|
||||
import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist';
|
||||
import generationReducer, {
|
||||
initialGenerationState,
|
||||
@@ -62,21 +40,12 @@ import postprocessingReducer, {
|
||||
migratePostprocessingState,
|
||||
} from 'features/parameters/store/postprocessingSlice';
|
||||
import queueReducer from 'features/queue/store/queueSlice';
|
||||
import sdxlReducer, {
|
||||
initialSDXLState,
|
||||
migrateSDXLState,
|
||||
} from 'features/sdxl/store/sdxlSlice';
|
||||
import sdxlReducer, { initialSDXLState, migrateSDXLState } from 'features/sdxl/store/sdxlSlice';
|
||||
import configReducer from 'features/system/store/configSlice';
|
||||
import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
|
||||
import systemReducer, {
|
||||
initialSystemState,
|
||||
migrateSystemState,
|
||||
} from 'features/system/store/systemSlice';
|
||||
import systemReducer, { initialSystemState, migrateSystemState } from 'features/system/store/systemSlice';
|
||||
import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
|
||||
import uiReducer, {
|
||||
initialUIState,
|
||||
migrateUIState,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import uiReducer, { initialUIState, migrateUIState } from 'features/ui/store/uiSlice';
|
||||
import { diff } from 'jsondiffpatch';
|
||||
import { defaultsDeep, keys, omit, pick } from 'lodash-es';
|
||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||
@@ -206,10 +175,7 @@ const unserialize: UnserializeFunction = (data, key) => {
|
||||
);
|
||||
return transformed;
|
||||
} catch (err) {
|
||||
log.warn(
|
||||
{ error: serializeError(err) },
|
||||
`Error rehydrating slice "${key}", falling back to default initial state`
|
||||
);
|
||||
log.warn({ error: serializeError(err) }, `Error rehydrating slice "${key}", falling back to default initial state`);
|
||||
return config.initialState;
|
||||
}
|
||||
};
|
||||
@@ -229,10 +195,7 @@ const serializationDenylist: {
|
||||
};
|
||||
|
||||
export const serialize: SerializeFunction = (data, key) => {
|
||||
const result = omit(
|
||||
data,
|
||||
serializationDenylist[key as keyof typeof serializationDenylist] ?? []
|
||||
);
|
||||
const result = omit(data, serializationDenylist[key as keyof typeof serializationDenylist] ?? []);
|
||||
return JSON.stringify(result);
|
||||
};
|
||||
|
||||
@@ -256,9 +219,7 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
||||
persistDebounce: 300,
|
||||
serialize,
|
||||
unserialize,
|
||||
prefix: uniqueStoreKey
|
||||
? `${STORAGE_PREFIX}${uniqueStoreKey}-`
|
||||
: STORAGE_PREFIX,
|
||||
prefix: uniqueStoreKey ? `${STORAGE_PREFIX}${uniqueStoreKey}-` : STORAGE_PREFIX,
|
||||
errorHandler,
|
||||
})
|
||||
);
|
||||
@@ -278,9 +239,7 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
||||
},
|
||||
});
|
||||
|
||||
export type AppGetState = ReturnType<
|
||||
ReturnType<typeof createStore>['getState']
|
||||
>;
|
||||
export type AppGetState = ReturnType<ReturnType<typeof createStore>['getState']>;
|
||||
export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import type { ChakraProps } from '@invoke-ai/ui';
|
||||
import {
|
||||
CompositeNumberInput,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
} from '@invoke-ai/ui';
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { CompositeNumberInput, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { RgbaColorPicker } from 'react-colorful';
|
||||
import type {
|
||||
ColorPickerBaseProps,
|
||||
RgbaColor,
|
||||
} from 'react-colorful/dist/types';
|
||||
import type { ColorPickerBaseProps, RgbaColor } from 'react-colorful/dist/types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & {
|
||||
@@ -39,30 +31,13 @@ const numberInputWidth: ChakraProps['w'] = '4.2rem';
|
||||
const IAIColorPicker = (props: IAIColorPickerProps) => {
|
||||
const { color, onChange, withNumberInput, ...rest } = props;
|
||||
const { t } = useTranslation();
|
||||
const handleChangeR = useCallback(
|
||||
(r: number) => onChange({ ...color, r }),
|
||||
[color, onChange]
|
||||
);
|
||||
const handleChangeG = useCallback(
|
||||
(g: number) => onChange({ ...color, g }),
|
||||
[color, onChange]
|
||||
);
|
||||
const handleChangeB = useCallback(
|
||||
(b: number) => onChange({ ...color, b }),
|
||||
[color, onChange]
|
||||
);
|
||||
const handleChangeA = useCallback(
|
||||
(a: number) => onChange({ ...color, a }),
|
||||
[color, onChange]
|
||||
);
|
||||
const handleChangeR = useCallback((r: number) => onChange({ ...color, r }), [color, onChange]);
|
||||
const handleChangeG = useCallback((g: number) => onChange({ ...color, g }), [color, onChange]);
|
||||
const handleChangeB = useCallback((b: number) => onChange({ ...color, b }), [color, onChange]);
|
||||
const handleChangeA = useCallback((a: number) => onChange({ ...color, a }), [color, onChange]);
|
||||
return (
|
||||
<Flex sx={sx}>
|
||||
<RgbaColorPicker
|
||||
color={color}
|
||||
onChange={onChange}
|
||||
style={colorPickerStyles}
|
||||
{...rest}
|
||||
/>
|
||||
<RgbaColorPicker color={color} onChange={onChange} style={colorPickerStyles} {...rest} />
|
||||
{withNumberInput && (
|
||||
<Flex>
|
||||
<FormControl>
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
import type { ChakraProps, FlexProps, SystemStyleObject } from '@invoke-ai/ui';
|
||||
import { Flex, Icon, Image } from '@invoke-ai/ui';
|
||||
import {
|
||||
IAILoadingImageFallback,
|
||||
IAINoContentFallback,
|
||||
} from 'common/components/IAIImageFallback';
|
||||
import type { ChakraProps, FlexProps, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex, Icon, Image } from '@invoke-ai/ui-library';
|
||||
import { IAILoadingImageFallback, IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||
import type {
|
||||
TypesafeDraggableData,
|
||||
TypesafeDroppableData,
|
||||
} from 'features/dnd/types';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
||||
import type {
|
||||
MouseEvent,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
SyntheticEvent,
|
||||
} from 'react';
|
||||
import type { MouseEvent, ReactElement, ReactNode, SyntheticEvent } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { PiImageBold, PiUploadSimpleBold } from 'react-icons/pi';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
@@ -161,14 +150,8 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
<Image
|
||||
src={thumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallbackSrc={
|
||||
useThumbailFallback ? imageDTO.thumbnail_url : undefined
|
||||
}
|
||||
fallback={
|
||||
useThumbailFallback ? undefined : (
|
||||
<IAILoadingImageFallback image={imageDTO} />
|
||||
)
|
||||
}
|
||||
fallbackSrc={useThumbailFallback ? imageDTO.thumbnail_url : undefined}
|
||||
fallback={useThumbailFallback ? undefined : <IAILoadingImageFallback image={imageDTO} />}
|
||||
onError={onError}
|
||||
draggable={false}
|
||||
w={imageDTO.width}
|
||||
@@ -179,13 +162,8 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
sx={imageSx}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
{withMetadataOverlay && (
|
||||
<ImageMetadataOverlay imageDTO={imageDTO} />
|
||||
)}
|
||||
<SelectionOverlay
|
||||
isSelected={isSelected}
|
||||
isHovered={withHoverOverlay ? isHovered : false}
|
||||
/>
|
||||
{withMetadataOverlay && <ImageMetadataOverlay imageDTO={imageDTO} />}
|
||||
<SelectionOverlay isSelected={isSelected} isHovered={withHoverOverlay ? isHovered : false} />
|
||||
</Flex>
|
||||
)}
|
||||
{!imageDTO && !isUploadDisabled && (
|
||||
@@ -198,20 +176,10 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
||||
)}
|
||||
{!imageDTO && isUploadDisabled && noContentFallback}
|
||||
{imageDTO && !isDragDisabled && (
|
||||
<IAIDraggable
|
||||
data={draggableData}
|
||||
disabled={isDragDisabled || !imageDTO}
|
||||
onClick={onClick}
|
||||
/>
|
||||
<IAIDraggable data={draggableData} disabled={isDragDisabled || !imageDTO} onClick={onClick} />
|
||||
)}
|
||||
{children}
|
||||
{!isDropDisabled && (
|
||||
<IAIDroppable
|
||||
data={droppableData}
|
||||
disabled={isDropDisabled}
|
||||
dropLabel={dropLabel}
|
||||
/>
|
||||
)}
|
||||
{!isDropDisabled && <IAIDroppable data={droppableData} disabled={isDropDisabled} dropLabel={dropLabel} />}
|
||||
</Flex>
|
||||
)}
|
||||
</ImageContextMenu>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui';
|
||||
import { IconButton } from '@invoke-ai/ui';
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import type { MouseEvent, ReactElement } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { BoxProps } from '@invoke-ai/ui';
|
||||
import { Box } from '@invoke-ai/ui';
|
||||
import type { BoxProps } from '@invoke-ai/ui-library';
|
||||
import { Box } from '@invoke-ai/ui-library';
|
||||
import { useDraggableTypesafe } from 'features/dnd/hooks/typesafeHooks';
|
||||
import type { TypesafeDraggableData } from 'features/dnd/types';
|
||||
import { memo, useRef } from 'react';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui';
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import type { AnimationProps } from 'framer-motion';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -27,12 +27,7 @@ const IAIDropOverlay = (props: Props) => {
|
||||
const { isOver, label = t('gallery.drop') } = props;
|
||||
const motionId = useRef(uuidv4());
|
||||
return (
|
||||
<motion.div
|
||||
key={motionId.current}
|
||||
initial={initial}
|
||||
animate={animate}
|
||||
exit={exit}
|
||||
>
|
||||
<motion.div key={motionId.current} initial={initial} animate={animate} exit={exit}>
|
||||
<Flex position="absolute" top={0} insetInlineStart={0} w="full" h="full">
|
||||
<Flex
|
||||
position="absolute"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box } from '@invoke-ai/ui';
|
||||
import { Box } from '@invoke-ai/ui-library';
|
||||
import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks';
|
||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||
@@ -36,9 +36,7 @@ const IAIDroppable = (props: IAIDroppableProps) => {
|
||||
pointerEvents={active ? 'auto' : 'none'}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{isValidDrop(data, active) && (
|
||||
<IAIDropOverlay isOver={isOver} label={dropLabel} />
|
||||
)}
|
||||
{isValidDrop(data, active) && <IAIDropOverlay isOver={isOver} label={dropLabel} />}
|
||||
</AnimatePresence>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui';
|
||||
import { Box, Skeleton } from '@invoke-ai/ui';
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Box, Skeleton } from '@invoke-ai/ui-library';
|
||||
import { memo } from 'react';
|
||||
|
||||
const skeletonStyles: SystemStyleObject = {
|
||||
@@ -16,13 +16,7 @@ const skeletonStyles: SystemStyleObject = {
|
||||
const IAIFillSkeleton = () => {
|
||||
return (
|
||||
<Skeleton sx={skeletonStyles}>
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
height="full"
|
||||
width="full"
|
||||
/>
|
||||
<Box position="absolute" top={0} insetInlineStart={0} height="full" width="full" />
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { As, ChakraProps, FlexProps } from '@invoke-ai/ui';
|
||||
import { Flex, Icon, Skeleton, Spinner, Text } from '@invoke-ai/ui';
|
||||
import type { As, ChakraProps, FlexProps } from '@invoke-ai/ui-library';
|
||||
import { Flex, Icon, Skeleton, Spinner, Text } from '@invoke-ai/ui-library';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { PiImageBold } from 'react-icons/pi';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
@@ -19,15 +19,7 @@ export const IAILoadingImageFallback = memo((props: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
opacity={0.7}
|
||||
w="full"
|
||||
h="full"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
borderRadius="base"
|
||||
bg="base.900"
|
||||
>
|
||||
<Flex opacity={0.7} w="full" h="full" alignItems="center" justifyContent="center" borderRadius="base" bg="base.900">
|
||||
<Spinner size="xl" />
|
||||
</Flex>
|
||||
);
|
||||
@@ -77,32 +69,30 @@ type IAINoImageFallbackWithSpinnerProps = FlexProps & {
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export const IAINoContentFallbackWithSpinner = memo(
|
||||
(props: IAINoImageFallbackWithSpinnerProps) => {
|
||||
const { sx, ...rest } = props;
|
||||
const styles = useMemo(
|
||||
() => ({
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
flexDir: 'column',
|
||||
gap: 2,
|
||||
userSelect: 'none',
|
||||
opacity: 0.7,
|
||||
color: 'base.500',
|
||||
...sx,
|
||||
}),
|
||||
[sx]
|
||||
);
|
||||
export const IAINoContentFallbackWithSpinner = memo((props: IAINoImageFallbackWithSpinnerProps) => {
|
||||
const { sx, ...rest } = props;
|
||||
const styles = useMemo(
|
||||
() => ({
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
flexDir: 'column',
|
||||
gap: 2,
|
||||
userSelect: 'none',
|
||||
opacity: 0.7,
|
||||
color: 'base.500',
|
||||
...sx,
|
||||
}),
|
||||
[sx]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex sx={styles} {...rest}>
|
||||
<Spinner size="xl" />
|
||||
{props.label && <Text textAlign="center">{props.label}</Text>}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<Flex sx={styles} {...rest}>
|
||||
<Spinner size="xl" />
|
||||
{props.label && <Text textAlign="center">{props.label}</Text>}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
IAINoContentFallbackWithSpinner.displayName = 'IAINoContentFallbackWithSpinner';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Badge, Flex } from '@invoke-ai/ui';
|
||||
import { Badge, Flex } from '@invoke-ai/ui-library';
|
||||
import { memo } from 'react';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user