Compare commits

...

144 Commits

Author SHA1 Message Date
4bd89ab2e1 How to run 2023-08-29 15:27:03 +05:30
d4427b97ae Merge pull request #1531 from easydiffusion/beta
Unset PYTHONHOME
2023-08-29 15:05:37 +05:30
4f336d9f25 Merge pull request #1260 from JeLuF/patch-25
Unset PYTHONHOME
2023-08-29 14:58:57 +05:30
1565530b0f Merge pull request #1530 from easydiffusion/beta
v3 in scripts
2023-08-29 14:55:30 +05:30
a21b01a0cd Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-29 14:54:43 +05:30
1c7e90576d v3 in scripts 2023-08-29 14:54:31 +05:30
c8de1cd49b Merge pull request #1527 from easydiffusion/beta
v3
2023-08-29 12:27:35 +05:30
5eb36e131d Merge branch 'main' into beta 2023-08-29 12:15:17 +05:30
b5d1adaa19 Use latest download link to avoid having to manually update readme (#1519) 2023-08-29 11:00:38 +05:30
b89d152540 Support lora models in subfolders when scanning the <lora> tag (#1521)
* Recursive lora search

* Support lora models in subfolders when scanning the <lora> tag
2023-08-29 10:48:57 +05:30
e49772030d changelog 2023-08-29 10:28:06 +05:30
b1cb03962c Fix embedding extraction for weights, commas, etc. This fixes the recent change where 'world' would match 'rld'. 2023-08-29 10:23:05 +05:30
a7b0858b22 Temporarily hide the support banner until v3 releases to main. Avoids the distraction from handling support/bugs during v3's release, will bring this back soon 2023-08-29 09:43:31 +05:30
ad227ca190 Remove safetensors hack (#1525)
We should upgrade to 0.3.3, since it has wheels for all our supported plattforms.
2023-08-29 09:21:44 +05:30
a8360484b2 Remove safetensors hack (#1525)
We should upgrade to 0.3.3, since it has wheels for all our supported plattforms.
2023-08-29 09:20:32 +05:30
80c4a50ca1 Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-29 09:20:25 +05:30
768b88a0ac sdkit 2.0.3 - use safetensors 0.3.3 2023-08-29 09:19:46 +05:30
82607573fa Update check_modules.py 2023-08-25 21:15:16 +05:30
d07e00cd74 Update check_modules.py 2023-08-25 21:15:06 +05:30
dfdd2b32e0 Update check_modules.py 2023-08-25 19:23:06 +05:30
844edbc865 safetensors 0.3.3 2023-08-25 19:07:26 +05:30
2bc66cc640 Merge branch 'banner' into beta 2023-08-24 18:24:28 +05:30
f9f9aba92d f 2023-08-24 17:39:39 +05:30
3f278cf2ad Pick the right embedding even if it has an underscore 2023-08-24 17:36:49 +05:30
cb7ba96dad Fix handling of embeddings with space in their name (#1402)
* Fix handling of files with space in their name

* Handle embeddings in save files

* Moved get_embedding_token

* Moved get_embedding_token

* Update save_utils.py
2023-08-24 16:32:17 +05:30
31edce4a60 Add ".pt" to the Lora extensions (#1518)
https://discord.com/channels/1014774730907209781/1014774732018683926/1144179143873929288

There seem to be ".pt" LORA files in the wild.
2023-08-24 16:09:42 +05:30
1b6aae9678 Cancel/Stop/Remove task buttons (#1493)
* Cancel/Stop/Remove task
So far, the button to remove a not yet rendered and a completed task was labeled 'Remove'. This can lead to confusions.
This PR changes the label to 'Cancel' for not yet rendered tasks. It also changes the color of the undoable 'Remove' button

* Keep the button color as red
2023-08-24 16:07:58 +05:30
9572ddf1c1 sdkit 2.0.2 - fix broken seamless tiling 2023-08-24 14:59:44 +05:30
1f44cebd0e Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-23 22:05:52 +05:30
843ea58c15 Force safetensors 0.3.2 for sdkit, the newer version has issues during installation 2023-08-23 22:04:57 +05:30
1e13c4e808 Support banner with kofi, patreon and itch.io links 2023-08-23 19:50:38 +05:30
c62161770d v3 readme 2023-08-23 13:52:37 +05:30
15b828b0f5 typo 2023-08-23 13:29:36 +05:30
faa83a87df rename kofi link 2023-08-23 13:21:46 +05:30
50da182e30 sdkit 2.0.0 - enable diffusers by default 2023-08-23 13:02:40 +05:30
dba573bf1a Show v3 engine selection even without beta 2023-08-23 13:02:20 +05:30
6a0eef3fe4 Show tabs on mobile 2023-08-23 12:19:35 +05:30
98f58e8672 Download button styling on mobile 2023-08-23 12:15:35 +05:30
04274f5839 Fix styling on mobile devices 2023-08-23 12:11:06 +05:30
f387b9f464 changelog 2023-08-23 11:07:38 +05:30
b8f533d0ea v3 changelog summary 2023-08-23 11:06:53 +05:30
5a49818a10 changelog 2023-08-22 19:06:38 +05:30
ad9d9e0b04 sdkit 1.0.185 - full support for custom inpainting models 2023-08-22 18:57:41 +05:30
c92470ff7e sdkit 1.0.184 - fix broken SD2 inpainting model 2023-08-22 17:45:00 +05:30
1cd9c7fdac Show the negative embeddings button only if the negative prompt panel is open 2023-08-22 16:59:40 +05:30
e607035c65 changelog 2023-08-22 16:11:20 +05:30
bde8113414 sdkit 1.0.183 - reduce VRAM usage of controlnet in low vram mode, and allow accelerating controlnets with xformers 2023-08-22 16:10:02 +05:30
1fd011b1be Don't fail if the prompt strength is too low 2023-08-22 15:41:21 +05:30
061380742c version 2023-08-22 15:17:24 +05:30
8f9feb3ed9 changelog 2023-08-22 15:16:46 +05:30
0dc01cb974 sdkit 1.0.182 - improve detection of SD 2.0 and 2.1 models, auto-detect v-parameterization and improve load time by speeding up the black-image test 2023-08-22 15:14:53 +05:30
55af328181 Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-22 12:02:42 +05:30
a8c0abfd5d Use document event handling for task events 2023-08-22 12:02:34 +05:30
4807744aa7 sdkit 1.0.181 - fix typo in watermark skip 2023-08-22 11:42:38 +05:30
669d40a9d2 fix embedding parser and use standard embedding varuable for metadata (#1516) 2023-08-22 09:08:59 +05:30
18049d529a Fix the lora prompt parser 2023-08-21 19:45:26 +05:30
f2b441d9fc typo 2023-08-21 19:20:23 +05:30
d2078d4dde sdkit 1.0.180 - auto-download the tile controlnet if necessary, by fixing the id in the models db 2023-08-21 19:17:30 +05:30
41d4ad2096 sdkit 1.0.179 - tile controlnet 2023-08-21 14:29:54 +05:30
29ec8291ad Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-21 14:29:05 +05:30
b93a206a48 sdkit 1.0.178 - prevent image size errors when blending mask with strict mask border 2023-08-21 14:04:40 +05:30
be83336cf7 WebP metadata support (#1511)
* WebP metadata support
- Replace piexif.js by Exif-Reader
- Merge plugin html/css to index.html/main.css

* Add webp to tooltip message
2023-08-21 13:28:26 +05:30
19fdba7d73 Send the controlnet filter preview request only when the task is about to start 2023-08-21 11:04:13 +05:30
2c2b3b75d5 Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-19 13:18:05 +05:30
47d5cb9e33 Refactor some of the task-related functions into task-manager.js, with callbacks for some UI-related code. This isn't exhaustive, just another step towards breaking up main.js 2023-08-19 13:15:32 +05:30
7b8e1bc919 type=number for number of images fields (#1507) 2023-08-19 09:51:34 +05:30
77aa7a0148 Improvements/Fixes for embeddings UI (#1509)
- Don't show "Use as thumbnail" if no embeddings were used in the prompt
- Fix layout issue on small screens
- use req.use_embeddings_model instead of parsing the prompt
- Add button to upload a thumb" ../../index.html ../css/main.css main.js
2023-08-19 09:50:40 +05:30
bdd7d2599f API to get SHA256 of a model file (#1510)
To be used from javascript to collect metadata from civitai
https://civitai.com/api/v1/model-versions/by-hash/0A35347528
2023-08-19 09:47:59 +05:30
ca8a96f956 Don't show or save hypernetwork info if using v3 (diffusers) 2023-08-18 19:09:14 +05:30
8957250db8 changelog 2023-08-18 19:02:14 +05:30
1b6ec418a1 sdkit 1.0.177 - rotate images if EXIF rotation present 2023-08-18 19:01:16 +05:30
3759d77945 changelog 2023-08-18 18:44:16 +05:30
ab4d34e509 sdkit 1.0.176 - resize control images to the task dimensions, to avoid memory errors with high-res control images 2023-08-18 18:43:23 +05:30
7f878f365b Don't add hypernetwork info in the metadata if using diffusers (v3 engine) 2023-08-18 18:23:21 +05:30
5efabfaea6 Don't include hypernetwork info in 'copy settings' if using diffusers 2023-08-18 18:10:35 +05:30
4cd8ae45e3 changelog 2023-08-18 18:01:15 +05:30
8999f9450f Show control image when 'Use these Settings' is used 2023-08-18 17:59:00 +05:30
1d54943d71 Support drag-and-drop and use-these-settings for controlnet 2023-08-18 17:53:16 +05:30
767d8fc35d Use these Settings work for multi-lora now 2023-08-18 17:18:01 +05:30
894f34678e Some more fixes for multi-lora use-these-settings 2023-08-18 17:08:19 +05:30
1190bedafd Don't include empty lora values in the metadata 2023-08-18 17:03:41 +05:30
e80db71d1c Allow downloading the controlnet preview image 2023-08-18 16:24:17 +05:30
846bb2134e changelog 2023-08-18 16:14:30 +05:30
38b2eec4be Show controlnet preview in the task entry after applying the filter 2023-08-18 16:14:01 +05:30
8dafe486a2 Show controlnet model in the task info 2023-08-18 14:19:50 +05:30
c895a96a43 changelog 2023-08-18 14:16:44 +05:30
67cae9725e Fix drag-and-drop and Use these Settings for LoRA 2023-08-18 14:16:23 +05:30
a2d06f87f6 Use the new lora models component while creating the render request 2023-08-18 13:27:00 +05:30
8e4afc8374 changelog 2023-08-18 13:18:39 +05:30
afd879a692 Auto-save LoRAs 2023-08-18 13:18:06 +05:30
83de2b8de7 formatting 2023-08-17 16:09:47 +05:30
4930f36a1a Remove tensorrt demo settings 2023-08-17 16:09:37 +05:30
fa3f196add Ignore commas while looking for embeddings 2023-08-17 16:09:17 +05:30
95004be0e9 Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-17 15:29:16 +05:30
281a849c8f changelog 2023-08-17 15:28:56 +05:30
5d82ce665c sdkit 1.0.175 - Automatically check for SDXL and use the correct yaml file 2023-08-17 15:27:48 +05:30
d632cfcde9 Hide empty folders in embeddings search results (#1506) 2023-08-17 13:23:13 +05:30
07f797a5e4 Negative embedding label 2023-08-17 12:32:50 +05:30
121107dd13 Shadow in dialogs 2023-08-17 12:30:18 +05:30
a2479b74be Show thumbnails for embeddings 2023-08-17 12:26:23 +05:30
7ee1d3cd91 Remove the embeddings in low warning (again) 2023-08-17 12:05:36 +05:30
4b28ddd691 Typo 2023-08-17 11:53:59 +05:30
7270b5fe0c Thumbnails for Embeddings (#1483)
* sqlalchemy

* sqlalchemy

* sqlalchemy bucket v1

* Bucket API

* Move easydb files to its own folders

* show images

* add croppr

* croppr ui

* profile, thumbnail croppr size limit

* fill list

* add upload

* Use modifiers card, resize thumbs

* Remove debugging code

* remove unused variable
2023-08-17 11:33:05 +05:30
285792f692 Controlnet thumb in taskConfig (#1502) 2023-08-17 11:18:47 +05:30
23a0a48b81 Warn when no controlnet model is chosen (#1503)
* Warn when no controlnet model is chosen

* Update main.js
2023-08-17 11:18:00 +05:30
2baad73bb9 Error messages for SDXL yaml files (#1504)
* Error messages for SDXL embeddings and SDXL yaml files

* Embeddings are supported now with SDXL
2023-08-17 11:08:18 +05:30
097dc99e77 changelog 2023-08-17 11:01:13 +05:30
edd10bcfe7 sdkit 1.0.174 - embedding support for SDXL models, refactor embeddings to use the standard context.models API in sdkit 2023-08-17 10:55:26 +05:30
ac1c65fba1 Move the extraction logic for embeddings-from-prompt, from sdkit to ED's UI 2023-08-17 10:54:47 +05:30
b4cc21ea89 changelog 2023-08-16 16:02:35 +05:30
3dfc3f5ff7 Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-16 16:02:03 +05:30
7c012df1d5 sdkit 1.0.173 - SDXL LoRA support 2023-08-16 15:59:07 +05:30
a1854d3734 'Use for Controlnet', Drag'n'Drop (#1501)
* Drop area for Controlnet

* 'Use for Controlnet', DND
2023-08-16 10:23:12 +05:30
074c566826 changelog 2023-08-15 19:06:48 +05:30
a2e7bfb30e sdkit 1.0.172 - fix broken tiling after diffusers 0.19.2 upgrade 2023-08-15 19:05:46 +05:30
01c1c77564 formatting 2023-08-15 16:58:12 +05:30
34de4fe8fe Remove warning about embeddings in low vram mode, works now 2023-08-15 16:57:51 +05:30
4975f8167e changelog 2023-08-15 16:50:21 +05:30
6777459e62 sdkit 1.0.171 - fix embeddings in low vram usage mode 2023-08-15 16:49:51 +05:30
253d0dbd5e changelog 2023-08-15 16:02:10 +05:30
e98bd70871 sdkit 1.0.170 - fix VAE in low vram mode 2023-08-15 16:00:47 +05:30
6a216be5cb Force-clean the local git repo, even if it has some unmerged changes 2023-08-15 13:31:08 +05:30
0adb7831e7 Use the correct nvidia wheels path 2023-08-15 12:53:16 +05:30
30ca98b597 sdkit 1.0.169 - don't clip-skip with SDXL, isn't supported yet 2023-08-14 19:02:56 +05:30
e80001e8c8 Merge branch 'beta' of github.com:cmdr2/stable-diffusion-ui into beta 2023-08-14 18:52:50 +05:30
b5490f7712 changelog 2023-08-14 18:52:38 +05:30
dc5748624f sdkit 1.0.168 - fail loudly if an embedding fails to load, disable watermarks for sdxl img2img 2023-08-14 18:52:20 +05:30
84c5a759d4 Resize slider for the advanced image popup (#1490) 2023-08-14 18:26:33 +05:30
ec43aa2f18 Merge pull request #1486 from ManInDark/beta2
Change Cursor type on logo hover
2023-08-14 18:25:05 +05:30
12fa08d7a7 Changed cursor on logo hover to indicate that it can be clicked 2023-08-08 21:45:18 +02:00
50dea4cb52 Use --pre for trt installs 2023-08-04 19:48:24 +05:30
20b06db359 Include onnx and polygraphy for TRT, and allow skipping the wheels for TRT 2023-08-04 19:46:37 +05:30
b6e512e65f v3 engine name 2023-08-04 11:21:31 +05:30
7d71c353b2 changelog 2023-08-04 11:19:35 +05:30
3216a68d63 Fix regression with new installations not being able to start ED 2023-08-03 21:01:21 +05:30
df518f822c SDXL 2023-08-03 20:11:27 +05:30
abdf0b6719 changelog 2023-08-03 20:02:52 +05:30
2d2a75f23c v2.6.0 (beta) - switch beta to use diffusers by default 2023-08-03 20:01:27 +05:30
fcb59c68d4 sdkit 1.0.167 - fix reversing loras 2023-08-03 19:42:56 +05:30
d47816e7b9 sdkit 1.0.166 - check for lora to SD model compatibility only for text-based loras 2023-08-03 18:47:58 +05:30
21297d98f2 Option to disable LoRA tag parsing 2023-08-03 18:38:25 +05:30
cc7452374d Remove hypernetworks from the UI options in diffusers. Sorry 2023-08-03 17:43:49 +05:30
c0dcf1633c Unset PYTHONHOME in start.sh 2023-05-09 18:07:46 +02:00
d8447ef1a9 Unset PYTHONHOME in Start Stable Diffusion UI.cmd
PYTHONHOME needs to be deleted before conda gets called for the first time.
2023-05-09 18:04:45 +02:00
43 changed files with 3837 additions and 3410 deletions

View File

@ -712,3 +712,411 @@ FileSaver.js is licensed under the MIT license:
SOFTWARE.
[1]: http://eligrey.com
croppr.js
=========
https://github.com/jamesssooi/Croppr.js
croppr.js is licensed under the MIT license:
MIT License
Copyright (c) 2017 James Ooi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
ExifReader
==========
https://github.com/mattiasw/ExifReader
ExifReader is licensed under the Mozilla Public License:
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@ -1,5 +1,44 @@
# What's new?
## v3.0
### Major Changes
- **ControlNet** - Full support for ControlNet, with native integration of the common ControlNet models. Just select a control image, then choose the ControlNet filter/model and run. No additional configuration or download necessary. Supports custom ControlNets as well.
- **SDXL** - Full support for SDXL. No configuration necessary, just put the SDXL model in the `models/stable-diffusion` folder.
- **Multiple LoRAs** - Use multiple LoRAs, including SDXL and SD2-compatible LoRAs. Put them in the `models/lora` folder.
- **Embeddings** - Use textual inversion embeddings easily, by putting them in the `models/embeddings` folder and using their names in the prompt (or by clicking the `+ Embeddings` button to select embeddings visually). Thanks @JeLuf.
- **Seamless Tiling** - Generate repeating textures that can be useful for games and other art projects. Works best in 512x512 resolution. Thanks @JeLuf.
- **Inpainting Models** - Full support for inpainting models, including custom inpainting models. No configuration (or yaml files) necessary.
- **Faster than v2.5** - Nearly 40% faster than Easy Diffusion v2.5, and can be even faster if you enable xFormers.
- **Even less VRAM usage** - Less than 2 GB for 512x512 images on 'low' VRAM usage setting (SD 1.5). Can generate large images with SDXL.
- **WebP images** - Supports saving images in the lossless webp format.
- **Undo/Redo in the UI** - Remove tasks or images from the queue easily, and undo the action if you removed anything accidentally. Thanks @JeLuf.
- **Three new samplers, and latent upscaler** - Added `DEIS`, `DDPM` and `DPM++ 2m SDE` as additional samplers. Thanks @ogmaresca and @rbertus2000.
- **Significantly faster 'Upscale' and 'Fix Faces' buttons on the images**
- **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use.
### Detailed changelog
* 3.0.2 - 29 Aug 2023 - Fixed incorrect matching of embeddings from prompts.
* 3.0.2 - 24 Aug 2023 - Fix broken seamless tiling.
* 3.0.2 - 23 Aug 2023 - Fix styling on mobile devices.
* 3.0.2 - 22 Aug 2023 - Full support for inpainting models, including custom models. Support SD 1.x and SD 2.x inpainting models. Does not require you to specify a yaml config file.
* 3.0.2 - 22 Aug 2023 - Reduce VRAM consumption of controlnet in 'low' VRAM mode, and allow accelerating controlnets using xformers.
* 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test.
* 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone.
* 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images.
* 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry.
* 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA and ControlNet.
* 3.0.1 - 18 Aug 2023 - Auto-save LoRA models and strengths.
* 3.0.1 - 17 Aug 2023 - Automatically use the correct yaml config file for custom SDXL models, even if a yaml file isn't present in the folder.
* 3.0.1 - 17 Aug 2023 - Fix broken embeddings with SDXL.
* 3.0.1 - 16 Aug 2023 - Fix broken LoRA with SDXL.
* 3.0.1 - 15 Aug 2023 - Fix broken seamless tiling.
* 3.0.1 - 15 Aug 2023 - Fix textual inversion embeddings not working in `low` VRAM usage mode.
* 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode.
* 3.0.1 - 14 Aug 2023 - Slider to change the image dimensions proportionally (in Image Settings). Thanks @JeLuf.
* 3.0.1 - 14 Aug 2023 - Show an error to the user if an embedding isn't compatible with the model, instead of failing silently without informing the user. Thanks @JeLuf.
* 3.0.1 - 14 Aug 2023 - Disable watermarking for SDXL img2img. Thanks @AvidGameFan.
* 3.0.0 - 3 Aug 2023 - Enabled diffusers for everyone by default. The old v2 engine can be used by disabling the "Use v3 engine" option in the Settings tab.
## v2.5
### Major Changes
- **Nearly twice as fast** - significantly faster speed of image generation. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast

View File

@ -1,18 +1,18 @@
Congrats on downloading Stable Diffusion UI, version 2!
Congrats on downloading Easy Diffusion, version 3!
If you haven't downloaded Stable Diffusion UI yet, please download from https://github.com/easydiffusion/easydiffusion#installation
If you haven't downloaded Easy Diffusion yet, please download from https://github.com/easydiffusion/easydiffusion#installation
After downloading, to install please follow these instructions:
For Windows:
- Please double-click the "Easy-Diffusion-Windows.exe" file and follow the instructions.
For Linux:
- Please open a terminal, unzip the Easy-Diffusion-Linux.zip file and go to the "easy-diffusion" directory. Then run ./start.sh
For Linux and Mac:
- Please open a terminal, and go to the "easy-diffusion" directory. Then run ./start.sh
That file will automatically install everything. After that it will start the Stable Diffusion interface in a web browser.
That file will automatically install everything. After that it will start the Easy Diffusion interface in a web browser.
To start the UI in the future, please run the same command mentioned above.
To start Easy Diffusion in the future, please run the same command mentioned above.
If you have any problems, please:

View File

@ -1,19 +1,21 @@
# Easy Diffusion 2.5
# Easy Diffusion 3.0
### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your computer.
Does not require technical knowledge, does not require pre-installed software. 1-click install, powerful features, friendly community.
[Installation guide](#installation) | [Troubleshooting guide](https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting) | <sub>[![Discord Server](https://img.shields.io/discord/1014774730907209781?label=Discord)](https://discord.com/invite/u9yhsFmEkB)</sub> <sup>(for support queries, and development discussions)</sup>
![t2i](https://raw.githubusercontent.com/Stability-AI/stablediffusion/main/assets/stable-samples/txt2img/768/merged-0006.png)
---
![262597678-11089485-2514-4a11-88fb-c3acc81fc9ec](https://github.com/easydiffusion/easydiffusion/assets/844287/050b5e15-e909-45bf-8162-a38234830e38)
# Installation
Click the download button for your operating system:
<p float="left">
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.41a/Easy-Diffusion-Windows.exe"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-win.png" width="200" /></a>
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.41a/Easy-Diffusion-Linux.zip"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-linux.png" width="200" /></a>
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.41a/Easy-Diffusion-Mac.zip"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-mac.png" width="200" /></a>
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/latest/download/Easy-Diffusion-Linux.zip"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-linux.png" width="200" /></a>
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/latest/download/Easy-Diffusion-Mac.zip"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-mac.png" width="200" /></a>
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/latest/download/Easy-Diffusion-Windows.exe"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-win.png" width="200" /></a>
</p>
**Hardware requirements:**
@ -62,17 +64,19 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages.
- **UI Themes**: Customize the program to your liking.
- **Searchable models dropdown**: organize your models into sub-folders, and search through them in the UI.
### Image generation
- **Supports**: "*Text to Image*" and "*Image to Image*".
- **21 Samplers**: `ddim`, `plms`, `heun`, `euler`, `euler_a`, `dpm2`, `dpm2_a`, `lms`, `dpm_solver_stability`, `dpmpp_2s_a`, `dpmpp_2m`, `dpmpp_sde`, `dpm_fast`, `dpm_adaptive`, `ddpm`, `deis`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`.
- **In-Painting**: Specify areas of your image to paint into.
### Powerful image generation
- **Supports**: "*Text to Image*", "*Image to Image*" and "*InPainting*"
- **ControlNet**: For advanced control over the image, e.g. by setting the pose or drawing the outline for the AI to fill in.
- **16 Samplers**: `PLMS`, `DDIM`, `DEIS`, `Heun`, `Euler`, `Euler Ancestral`, `DPM2`, `DPM2 Ancestral`, `LMS`, `DPM Solver`, `DPM++ 2s Ancestral`, `DPM++ 2m`, `DPM++ 2m SDE`, `DPM++ SDE`, `DDPM`, `UniPC`.
- **Stable Diffusion XL and 2.1**: Generate higher-quality images using the latest Stable Diffusion XL models.
- **Textual Inversion Embeddings**: For guiding the AI strongly towards a particular concept.
- **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program.
- **Face Correction (GFPGAN)**
- **Upscaling (RealESRGAN)**
- **Loopback**: Use the output image as the input image for the next img2img task.
- **Loopback**: Use the output image as the input image for the next image task.
- **Negative Prompt**: Specify aspects of the image to *remove*.
- **Attention/Emphasis**: () in the prompt increases the model's attention to enclosed words, and [] decreases it.
- **Weighted Prompts**: Use weights for specific words in your prompt to change their importance, e.g. `red:2.4 dragon:1.2`.
- **Attention/Emphasis**: `+` in the prompt increases the model's attention to enclosed words, and `-` decreases it. E.g. `apple++ falling from a tree`.
- **Weighted Prompts**: Use weights for specific words in your prompt to change their importance, e.g. `(red)2.4 (dragon)1.2`.
- **Prompt Matrix**: Quickly create multiple variations of your prompt, e.g. `a photograph of an astronaut riding a horse | illustration | cinematic lighting`.
- **Prompt Set**: Quickly create multiple variations of your prompt, e.g. `a photograph of an astronaut on the {moon,earth}`
- **1-click Upscale/Face Correction**: Upscale or correct an image after it has been generated.
@ -82,10 +86,11 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages.
### Advanced features
- **Custom Models**: Use your own `.ckpt` or `.safetensors` file, by placing it inside the `models/stable-diffusion` folder!
- **Stable Diffusion 2.1 support**
- **Stable Diffusion XL and 2.1 support**
- **Merge Models**
- **Use custom VAE models**
- **Use pre-trained Hypernetworks**
- **Textual Inversion Embeddings**
- **ControlNet**
- **Use custom GFPGAN models**
- **UI Plugins**: Choose from a growing list of [community-generated UI plugins](https://github.com/easydiffusion/easydiffusion/wiki/UI-Plugins), or write your own plugin to add features to the project!
@ -103,18 +108,8 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages.
----
## Easy for new users:
![Screenshot of the initial UI](https://user-images.githubusercontent.com/844287/217043152-29454d15-0387-4228-b70d-9a4b84aeb8ba.png)
## Powerful features for advanced users:
![Screenshot of advanced settings](https://user-images.githubusercontent.com/844287/217042588-fc53c975-bacd-4a9c-af88-37408734ade3.png)
## Live Preview
Useful for judging (and stopping) an image quickly, without waiting for it to finish rendering.
![live-512](https://user-images.githubusercontent.com/844287/192097249-729a0a1e-a677-485e-9ccc-16a9e848fabe.gif)
## Easy for new users, powerful features for advanced users:
![image](https://github.com/easydiffusion/easydiffusion/assets/844287/efbbac9f-42ce-4aef-8625-fd23c74a8241)
## Task Queue
![Screenshot of task queue](https://user-images.githubusercontent.com/844287/217043984-0b35f73b-1318-47cb-9eed-a2a91b430490.png)
@ -128,12 +123,6 @@ Please refer to our [guide](https://github.com/easydiffusion/easydiffusion/wiki/
# Bugs reports and code contributions welcome
If there are any problems or suggestions, please feel free to ask on the [discord server](https://discord.com/invite/u9yhsFmEkB) or [file an issue](https://github.com/easydiffusion/easydiffusion/issues).
We could really use help on these aspects (click to view tasks that need your help):
* [User Interface](https://github.com/users/cmdr2/projects/1/views/1)
* [Engine](https://github.com/users/cmdr2/projects/3/views/1)
* [Installer](https://github.com/users/cmdr2/projects/4/views/1)
* [Documentation](https://github.com/users/cmdr2/projects/5/views/1)
If you have any code contributions in mind, please feel free to say Hi to us on the [discord server](https://discord.com/invite/u9yhsFmEkB). We use the Discord server for development-related discussions, and for helping users.
# Credits

BIN
patch.patch Normal file

Binary file not shown.

View File

@ -4,6 +4,7 @@ cd /d %~dp0
echo Install dir: %~dp0
set PATH=C:\Windows\System32;%PATH%
set PYTHONHOME=
if exist "on_sd_start.bat" (
echo ================================================================================

View File

@ -18,13 +18,15 @@ os_name = platform.system()
modules_to_check = {
"torch": ("1.11.0", "1.13.1", "2.0.0"),
"torchvision": ("0.12.0", "0.14.1", "0.15.1"),
"sdkit": "1.0.165",
"sdkit": "2.0.3",
"stable-diffusion-sdkit": "2.1.4",
"rich": "12.6.0",
"uvicorn": "0.19.0",
"fastapi": "0.85.1",
"pycloudflared": "0.2.0",
"ruamel.yaml": "0.17.21",
"sqlalchemy": "2.0.19",
"python-multipart": "0.0.6",
# "xformers": "0.0.16",
}
modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"]
@ -58,11 +60,6 @@ def install(module_name: str, module_version: str):
install_cmd = f"python -m pip install --upgrade {module_name}=={module_version}"
# hack for safetensors, until v3 gets released to the main branch
if module_name == "sdkit":
install_cmd += " safetensors==0.3.2"
# /hack
if index_url:
install_cmd += f" --index-url {index_url}"
if module_name == "sdkit" and version("sdkit") is not None:

View File

@ -1,6 +1,6 @@
@echo off
@echo. & echo "Easy Diffusion - v2" & echo.
@echo. & echo "Easy Diffusion - v3" & echo.
set PATH=C:\Windows\System32;%PATH%
@ -46,6 +46,8 @@ if "%update_branch%"=="" (
@cd sd-ui-files
@call git add -A .
@call git stash
@call git reset --hard
@call git -c advice.detachedHead=false checkout "%update_branch%"
@call git pull

View File

@ -2,7 +2,7 @@
source ./scripts/functions.sh
printf "\n\nEasy Diffusion\n\n"
printf "\n\nEasy Diffusion - v3\n\n"
export PYTHONNOUSERSITE=y
@ -29,6 +29,8 @@ if [ -f "scripts/install_status.txt" ] && [ `grep -c sd_ui_git_cloned scripts/in
cd sd-ui-files
git add -A .
git stash
git reset --hard
git -c advice.detachedHead=false checkout "$update_branch"
git pull

View File

@ -19,6 +19,7 @@ if [ -f "on_sd_start.bat" ]; then
exit 1
fi
unset PYTHONHOME
# set legacy installer's PATH, if it exists
if [ -e "installer" ]; then export PATH="$(pwd)/installer/bin:$PATH"; fi

View File

@ -38,6 +38,7 @@ SD_UI_DIR = os.getenv("SD_UI_PATH", None)
CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "..", "scripts"))
MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "models"))
BUCKET_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "bucket"))
USER_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "plugins"))
CORE_PLUGINS_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "plugins"))
@ -60,6 +61,7 @@ APP_CONFIG_DEFAULTS = {
"ui": {
"open_browser_on_start": True,
},
"test_diffusers": True,
}
IMAGE_EXTENSIONS = [
@ -115,7 +117,7 @@ def getConfig(default_val=APP_CONFIG_DEFAULTS):
def set_config_on_startup(config: dict):
if getConfig.__test_diffusers_on_startup is None:
getConfig.__test_diffusers_on_startup = config.get("test_diffusers", False)
getConfig.__test_diffusers_on_startup = config.get("test_diffusers", True)
config["config_on_startup"] = {"test_diffusers": getConfig.__test_diffusers_on_startup}
if os.path.isfile(config_yaml_path):

View File

@ -0,0 +1,102 @@
from typing import List
from fastapi import Depends, FastAPI, HTTPException, Response, File
from sqlalchemy.orm import Session
from easydiffusion.easydb import crud, models, schemas
from easydiffusion.easydb.database import SessionLocal, engine
from requests.compat import urlparse
import base64, json
MIME_TYPES = {
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"png": "image/png",
"webp": "image/webp",
"js": "text/javascript",
"htm": "text/html",
"html": "text/html",
"css": "text/css",
"json": "application/json",
"mjs": "application/json",
"yaml": "application/yaml",
"svg": "image/svg+xml",
"txt": "text/plain",
}
def init():
from easydiffusion.server import server_api
models.BucketBase.metadata.create_all(bind=engine)
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@server_api.get("/bucket/{obj_path:path}")
def bucket_get_object(obj_path: str, db: Session = Depends(get_db)):
filename = get_filename_from_url(obj_path)
path = get_path_from_url(obj_path)
if filename==None:
bucket = crud.get_bucket_by_path(db, path=path)
if bucket == None:
raise HTTPException(status_code=404, detail="Bucket not found")
bucketfiles = db.query(models.BucketFile).with_entities(models.BucketFile.filename).filter(models.BucketFile.bucket_id == bucket.id).all()
bucketfiles = [ x.filename for x in bucketfiles ]
return bucketfiles
else:
bucket_id = crud.get_bucket_by_path(db, path).id
bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id == bucket_id, models.BucketFile.filename == filename).first()
suffix = get_suffix_from_filename(filename)
return Response(content=bucketfile.data, media_type=MIME_TYPES.get(suffix, "application/octet-stream"))
@server_api.post("/bucket/{obj_path:path}")
def bucket_post_object(obj_path: str, file: bytes = File(), db: Session = Depends(get_db)):
filename = get_filename_from_url(obj_path)
path = get_path_from_url(obj_path)
bucket = crud.get_bucket_by_path(db, path)
if bucket == None:
bucket = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path))
bucket_id = bucket.id
bucketfile = schemas.BucketFileCreate(filename=filename, data=file)
result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id)
result.data = base64.encodestring(result.data)
return result
@server_api.post("/buckets/{bucket_id}/items/", response_model=schemas.BucketFile)
def create_bucketfile_in_bucket(
bucket_id: int, bucketfile: schemas.BucketFileCreate, db: Session = Depends(get_db)
):
bucketfile.data = base64.decodestring(bucketfile.data)
result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id)
result.data = base64.encodestring(result.data)
return result
def get_filename_from_url(url):
path = urlparse(url).path
name = path[path.rfind('/')+1:]
return name or None
def get_path_from_url(url):
path = urlparse(url).path
path = path[0:path.rfind('/')]
return path or None
def get_suffix_from_filename(filename):
return filename[filename.rfind('.')+1:]

View File

@ -0,0 +1,24 @@
from sqlalchemy.orm import Session
from easydiffusion.easydb import models, schemas
def get_bucket_by_path(db: Session, path: str):
return db.query(models.Bucket).filter(models.Bucket.path == path).first()
def create_bucket(db: Session, bucket: schemas.BucketCreate):
db_bucket = models.Bucket(path=bucket.path)
db.add(db_bucket)
db.commit()
db.refresh(db_bucket)
return db_bucket
def create_bucketfile(db: Session, bucketfile: schemas.BucketFileCreate, bucket_id: int):
db_bucketfile = models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id)
db.merge(db_bucketfile)
db.commit()
db_bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id==bucket_id, models.BucketFile.filename==bucketfile.filename).first()
return db_bucketfile

View File

@ -0,0 +1,14 @@
import os
from easydiffusion import app
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
os.makedirs(app.BUCKET_DIR, exist_ok=True)
SQLALCHEMY_DATABASE_URL = "sqlite:///"+os.path.join(app.BUCKET_DIR, "bucket.db")
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
BucketBase = declarative_base()

View File

@ -0,0 +1,25 @@
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, BLOB
from sqlalchemy.orm import relationship
from easydiffusion.easydb.database import BucketBase
class Bucket(BucketBase):
__tablename__ = "bucket"
id = Column(Integer, primary_key=True, index=True)
path = Column(String, unique=True, index=True)
bucketfiles = relationship("BucketFile", back_populates="bucket")
class BucketFile(BucketBase):
__tablename__ = "bucketfile"
filename = Column(String, index=True, primary_key=True)
bucket_id = Column(Integer, ForeignKey("bucket.id"), primary_key=True)
data = Column(BLOB, index=False)
bucket = relationship("Bucket", back_populates="bucketfiles")

View File

@ -0,0 +1,36 @@
from typing import List, Union
from pydantic import BaseModel
class BucketFileBase(BaseModel):
filename: str
data: bytes
class BucketFileCreate(BucketFileBase):
pass
class BucketFile(BucketFileBase):
bucket_id: int
class Config:
orm_mode = True
class BucketBase(BaseModel):
path: str
class BucketCreate(BucketBase):
pass
class Bucket(BucketBase):
id: int
bucketfiles: List[BucketFile] = []
class Config:
orm_mode = True

View File

@ -11,6 +11,7 @@ from sdkit import Context
from sdkit.models import load_model, scan_model, unload_model, download_model, get_model_info_from_db
from sdkit.models.model_loader.controlnet_filters import filters as cn_filters
from sdkit.utils import hash_file_quick
from sdkit.models.model_loader.embeddings import get_embedding_token
KNOWN_MODEL_TYPES = [
"stable-diffusion",
@ -29,7 +30,7 @@ MODEL_EXTENSIONS = {
"hypernetwork": [".pt", ".safetensors"],
"gfpgan": [".pth"],
"realesrgan": [".pth"],
"lora": [".ckpt", ".safetensors"],
"lora": [".ckpt", ".safetensors", ".pt"],
"codeformer": [".pth"],
"embeddings": [".pt", ".bin", ".safetensors"],
"controlnet": [".pth", ".safetensors"],
@ -65,9 +66,6 @@ def load_default_models(context: Context):
runtime.set_vram_optimizations(context)
config = app.getConfig()
context.embeddings_path = os.path.join(app.MODELS_DIR, "embeddings")
# init default model paths
for model_type in MODELS_TO_LOAD_ON_START:
context.model_paths[model_type] = resolve_model_to_use(model_type=model_type, fail_if_not_found=False)
@ -102,7 +100,16 @@ def unload_all(context: Context):
def resolve_model_to_use(model_name: Union[str, list] = None, model_type: str = None, fail_if_not_found: bool = True):
model_names = model_name if isinstance(model_name, list) else [model_name]
model_paths = [resolve_model_to_use_single(m, model_type, fail_if_not_found) for m in model_names]
model_paths = []
for m in model_names:
if model_type == "embeddings":
try:
resolve_model_to_use_single(m, model_type)
except FileNotFoundError: # try with spaces
m = m.replace("_", " ")
path = resolve_model_to_use_single(m, model_type, fail_if_not_found)
model_paths.append(path)
return model_paths[0] if len(model_paths) == 1 else model_paths
@ -141,7 +148,9 @@ def resolve_model_to_use_single(model_name: str = None, model_type: str = None,
return default_model_path
if model_name and fail_if_not_found:
raise Exception(f"Could not find the desired model {model_name}! Is it present in the {model_dir} folder?")
raise FileNotFoundError(
f"Could not find the desired model {model_name}! Is it present in the {model_dir} folder?"
)
def reload_models_if_necessary(context: Context, models_data: ModelsData, models_to_force_reload: list = []):
@ -315,6 +324,7 @@ def getModels(scan_for_malicious: bool = True):
{"control_v11p_sd15_mlsd": "Straight Lines"},
{"control_v11p_sd15_seg": "Segment"},
{"control_v11e_sd15_shuffle": "Shuffle"},
{"control_v11f1e_sd15_tile": "Tile"},
],
},
}
@ -324,9 +334,11 @@ def getModels(scan_for_malicious: bool = True):
class MaliciousModelException(Exception):
"Raised when picklescan reports a problem with a model"
def scan_directory(directory, suffixes, directoriesFirst: bool = True, default_entries=[]):
tree = list(default_entries)
def scan_directory(directory, suffixes, directoriesFirst: bool = True, default_entries=[], nameFilter=None):
nonlocal models_scanned
tree = list(default_entries)
for entry in sorted(
os.scandir(directory),
key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower()),
@ -345,7 +357,11 @@ def getModels(scan_for_malicious: bool = True):
raise MaliciousModelException(entry.path)
if scan_for_malicious:
known_models[entry.path] = mtime
model_id = entry.name[: -len(matching_suffix)]
if callable(nameFilter):
model_id = nameFilter(model_id)
model_exists = False
for m in tree: # allows default "named" models, like CodeFormer and known ControlNet models
if (isinstance(m, str) and model_id == m) or (isinstance(m, dict) and model_id in m):
@ -353,14 +369,15 @@ def getModels(scan_for_malicious: bool = True):
break
if not model_exists:
tree.append(model_id)
elif entry.is_dir():
scan = scan_directory(entry.path, suffixes, directoriesFirst=False)
scan = scan_directory(entry.path, suffixes, directoriesFirst=False, nameFilter=nameFilter)
if len(scan) != 0:
tree.append((entry.name, scan))
return tree
def listModels(model_type):
def listModels(model_type, nameFilter=None):
nonlocal models_scanned
model_extensions = MODEL_EXTENSIONS.get(model_type, [])
@ -370,7 +387,9 @@ def getModels(scan_for_malicious: bool = True):
try:
default_tree = models["options"].get(model_type, [])
models["options"][model_type] = scan_directory(models_dir, model_extensions, default_entries=default_tree)
models["options"][model_type] = scan_directory(
models_dir, model_extensions, default_entries=default_tree, nameFilter=nameFilter
)
except MaliciousModelException as e:
models["scan-error"] = str(e)
@ -382,7 +401,7 @@ def getModels(scan_for_malicious: bool = True):
listModels(model_type="hypernetwork")
listModels(model_type="gfpgan")
listModels(model_type="lora")
listModels(model_type="embeddings")
listModels(model_type="embeddings", nameFilter=get_embedding_token)
listModels(model_type="controlnet")
if scan_for_malicious and models_scanned > 0:

View File

@ -12,9 +12,9 @@ from easydiffusion import app
manifest = {
"tensorrt": {
"install": [
"nvidia-cudnn --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com",
"tensorrt-libs --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com",
"tensorrt --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com",
"nvidia-cudnn --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com",
"tensorrt-libs --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com",
"tensorrt --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com",
],
"uninstall": ["tensorrt"],
# TODO also uninstall tensorrt-libs and nvidia-cudnn, but do it upon restarting (avoid 'file in use' error)
@ -25,7 +25,7 @@ installing = []
# remove this once TRT releases on pypi
if platform.system() == "Windows":
trt_dir = os.path.join(app.ROOT_DIR, "tensorrt")
if os.path.exists(trt_dir):
if os.path.exists(trt_dir) and os.path.isdir(trt_dir) and len(os.listdir(trt_dir)) > 0:
files = os.listdir(trt_dir)
packages = manifest["tensorrt"]["install"]
@ -61,6 +61,10 @@ def install(module_name):
raise RuntimeError(f"Can't install unknown package: {module_name}!")
commands = manifest[module_name]["install"]
if module_name == "tensorrt":
commands += [
"protobuf==3.20.3 polygraphy==0.47.1 onnx==1.14.0 --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com"
]
commands = [f"python -m pip install --upgrade {cmd}" for cmd in commands]
installing.append(module_name)

View File

@ -30,9 +30,7 @@ def init(device):
from easydiffusion import app
app_config = app.getConfig()
context.test_diffusers = (
app_config.get("test_diffusers", False) and app_config.get("update_branch", "main") != "main"
)
context.test_diffusers = app_config.get("test_diffusers", True)
log.info("Device usage during initialization:")
get_device_usage(device, log_info=True, process_usage_only=False)

View File

@ -63,7 +63,7 @@ class SetAppConfigRequest(BaseModel, extra=Extra.allow):
ui_open_browser_on_start: bool = None
listen_to_network: bool = None
listen_port: int = None
test_diffusers: bool = False
test_diffusers: bool = True
def init():
@ -139,6 +139,10 @@ def init():
def modify_package(package_name: str, req: dict):
return modify_package_internal(package_name, req)
@server_api.get("/sha256/{obj_path:path}")
def get_sha256(obj_path: str):
return get_sha256_internal(obj_path)
@server_api.get("/")
def read_root():
return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS)
@ -451,3 +455,26 @@ def modify_package_internal(package_name: str, req: dict):
log.error(str(e))
log.error(traceback.format_exc())
return HTTPException(status_code=500, detail=str(e))
def get_sha256_internal(obj_path):
import hashlib
from easydiffusion.utils import sha256sum
path = obj_path.split("/")
type = path.pop(0)
try:
model_path = model_manager.resolve_model_to_use("/".join(path), type)
except Exception as e:
log.error(str(e))
log.error(traceback.format_exc())
return HTTPException(status_code=404)
try:
digest = sha256sum(model_path)
return {"digest": digest}
except Exception as e:
log.error(str(e))
log.error(traceback.format_exc())
return HTTPException(status_code=500, detail=str(e))

View File

@ -3,7 +3,7 @@ import pprint
from sdkit.filter import apply_filters
from sdkit.models import load_model
from sdkit.utils import img_to_base64_str, log
from sdkit.utils import img_to_base64_str, get_image, log
from easydiffusion import model_manager, runtime
from easydiffusion.types import FilterImageRequest, FilterImageResponse, ModelsData, OutputFormatData
@ -42,7 +42,12 @@ class FilterTask(Task):
print_task_info(self.request, self.models_data, self.output_format)
images = filter_images(context, self.request.image, self.request.filter, self.request.filter_params)
if isinstance(self.request.image, list):
images = [get_image(img) for img in self.request.image]
else:
images = get_image(self.request.image)
images = filter_images(context, images, self.request.filter, self.request.filter_params)
output_format = self.output_format
images = [

View File

@ -15,6 +15,8 @@ from sdkit.utils import (
img_to_base64_str,
img_to_buffer,
latent_samples_to_images,
resize_img,
get_image,
log,
)
@ -226,8 +228,13 @@ def generate_images_internal(
req.width, req.height = map(lambda x: x - x % 8, (req.width, req.height)) # clamp to 8
if req.control_image and task_data.control_filter_to_apply:
req.control_image = get_image(req.control_image)
req.control_image = resize_img(req.control_image.convert("RGB"), req.width, req.height, clamp_to_8=True)
req.control_image = filter_images(context, req.control_image, task_data.control_filter_to_apply)[0]
if req.init_image is not None and int(req.num_inference_steps * req.prompt_strength) == 0:
req.prompt_strength = 1 / req.num_inference_steps if req.num_inference_steps > 0 else 1
if context.test_diffusers:
pipe = context.models["stable-diffusion"]["default"]
if hasattr(pipe.unet, "_allocate_trt_buffers_backup"):

View File

@ -73,6 +73,7 @@ class TaskData(BaseModel):
use_hypernetwork_model: Union[str, List[str]] = None
use_lora_model: Union[str, List[str]] = None
use_controlnet_model: Union[str, List[str]] = None
use_embeddings_model: Union[str, List[str]] = None
filters: List[str] = []
filter_params: Dict[str, Dict[str, Any]] = {}
control_filter_to_apply: Union[str, List[str]] = None
@ -200,6 +201,7 @@ def convert_legacy_render_req_to_new(old_req: dict):
model_paths["hypernetwork"] = old_req.get("use_hypernetwork_model")
model_paths["lora"] = old_req.get("use_lora_model")
model_paths["controlnet"] = old_req.get("use_controlnet_model")
model_paths["embeddings"] = old_req.get("use_embeddings_model")
model_paths["gfpgan"] = old_req.get("use_face_correction", "")
model_paths["gfpgan"] = model_paths["gfpgan"] if "gfpgan" in model_paths["gfpgan"].lower() else None

View File

@ -6,3 +6,15 @@ from .save_utils import (
save_images_to_disk,
get_printable_request,
)
def sha256sum(filename):
sha256 = hashlib.sha256()
with open(filename, "rb") as f:
while True:
data = f.read(8192) # Read in chunks of 8192 bytes
if not data:
break
sha256.update(data)
return sha256.hexdigest()

View File

@ -10,6 +10,7 @@ from easydiffusion import app
from easydiffusion.types import GenerateImageRequest, TaskData, OutputFormatData
from numpy import base_repr
from sdkit.utils import save_dicts, save_images
from sdkit.models.model_loader.embeddings import get_embedding_token
filename_regex = re.compile("[^a-zA-Z0-9._-]")
img_number_regex = re.compile("([0-9]{5,})")
@ -34,7 +35,7 @@ TASK_TEXT_MAPPING = {
"lora_alpha": "LoRA Strength",
"use_hypernetwork_model": "Hypernetwork model",
"hypernetwork_strength": "Hypernetwork Strength",
"use_embedding_models": "Embedding models",
"use_embeddings_model": "Embedding models",
"tiling": "Seamless Tiling",
"use_face_correction": "Use Face Correction",
"use_upscale": "Use Upscaling",
@ -219,7 +220,7 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output
task_data_metadata.update(output_format.dict())
app_config = app.getConfig()
using_diffusers = app_config.get("test_diffusers", False)
using_diffusers = app_config.get("test_diffusers", True)
# Save the metadata in the order defined in TASK_TEXT_MAPPING
metadata = {}
@ -228,20 +229,18 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output
metadata[key] = req_metadata[key]
elif key in task_data_metadata:
metadata[key] = task_data_metadata[key]
elif key == "use_embedding_models" and using_diffusers:
if key == "use_embeddings_model" and using_diffusers:
embeddings_extensions = {".pt", ".bin", ".safetensors"}
def scan_directory(directory_path: str):
used_embeddings = []
for entry in os.scandir(directory_path):
if entry.is_file():
entry_extension = os.path.splitext(entry.name)[1]
if entry_extension not in embeddings_extensions:
# Check if the filename has the right extension
if not any(map(lambda ext: entry.name.endswith(ext), embeddings_extensions)):
continue
embedding_name_regex = regex.compile(
r"(^|[\s,])" + regex.escape(os.path.splitext(entry.name)[0]) + r"([+-]*$|[\s,]|[+-]+[\s,])"
)
embedding_name_regex = regex.compile(r"(^|[\s,])" + regex.escape(get_embedding_token(entry.name)) + r"([+-]*$|[\s,]|[+-]+[\s,])")
if embedding_name_regex.search(req.prompt) or embedding_name_regex.search(req.negative_prompt):
used_embeddings.append(entry.path)
elif entry.is_dir():
@ -249,7 +248,7 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output
return used_embeddings
used_embeddings = scan_directory(os.path.join(app.MODELS_DIR, "embeddings"))
metadata["use_embedding_models"] = used_embeddings if len(used_embeddings) > 0 else None
metadata["use_embeddings_model"] = used_embeddings if len(used_embeddings) > 0 else None
# Clean up the metadata
if req.init_image is None and "prompt_strength" in metadata:
@ -265,7 +264,10 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output
if task_data.use_controlnet_model is None and "control_filter_to_apply" in metadata:
del metadata["control_filter_to_apply"]
if not using_diffusers:
if using_diffusers:
for key in (x for x in ["use_hypernetwork_model", "hypernetwork_strength"] if x in metadata):
del metadata[key]
else:
for key in (
x for x in ["use_lora_model", "lora_alpha", "clip_skip", "tiling", "latent_upscaler_steps", "use_controlnet_model", "control_filter_to_apply"] if x in metadata
):

View File

@ -18,12 +18,15 @@
<link rel="stylesheet" href="/media/css/image-modal.css">
<link rel="stylesheet" href="/media/css/plugins.css">
<link rel="stylesheet" href="/media/css/animations.css">
<link rel="stylesheet" href="/media/css/croppr.css" rel="stylesheet"/>
<link rel="manifest" href="/media/manifest.webmanifest">
<script src="/media/js/jquery-3.6.1.min.js"></script>
<script src="/media/js/jquery-confirm.min.js"></script>
<script src="/media/js/jszip.min.js"></script>
<script src="/media/js/FileSaver.min.js"></script>
<script src="/media/js/marked.min.js"></script>
<script src="/media/js/croppr.js"></script>
<script src="/media/js/exif-reader.js"></script>
</head>
<body>
<div id="container">
@ -32,7 +35,7 @@
<h1>
<img id="logo_img" src="/media/images/icon-512x512.png" >
Easy Diffusion
<small><span id="version">v2.5.48</span> <span id="updateBranchLabel"></span></small>
<small><span id="version">v3.0.2</span> <span id="updateBranchLabel"></span></small>
</h1>
</div>
<div id="server-status">
@ -59,7 +62,14 @@
<div id="editor-inputs-prompt" class="row">
<div id="prompt-toolbar" class="split-toolbar">
<div id="prompt-toolbar-left" class="toolbar-left">
<label for="prompt"><b>Enter Prompt</b></label> <small>or</small> <button id="promptsFromFileBtn" class="tertiaryButton smallButton">Load from a file</button>
<label for="prompt"><b>Enter Prompt</b>
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">
You can type your prompts in the below textbox or load them from a file. You can also
reload tasks from metadata embedded in PNG, WEBP and JPEG images (enable embedding from the Settings).
</span></i>
</label>
<small>or</small>
<button id="promptsFromFileBtn" class="tertiaryButton smallButton">Load from a file</button>
</div>
<div id="prompt-toolbar-right" class="toolbar-right">
<button id="image-modifier-dropdown" class="tertiaryButton smallButton">+ Image Modifiers</button>
@ -73,7 +83,7 @@
<a href="https://github.com/easydiffusion/easydiffusion/wiki/Writing-prompts#negative-prompts" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top">Click to learn more about Negative Prompts</span></i></a>
<small>(optional)</small>
</label>
<button id="negative-embeddings-button" class="tertiaryButton smallButton displayNone">+ Embedding</button>
<button id="negative-embeddings-button" class="tertiaryButton smallButton displayNone">+ Negative Embedding</button>
<div class="collapsible-content">
<textarea id="negative_prompt" name="negative_prompt" placeholder="list the things to remove from the image (e.g. fog, green)"></textarea>
</div>
@ -81,6 +91,11 @@
<div id="editor-inputs-init-image" class="row">
<label for="init_image">Initial Image (img2img) <small>(optional)</small> </label>
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top">
Add img2img source image using the Browse button, via drag & drop from external file or browser image (incl.
rendered image) or by pasting an image from the clipboard using Ctrl+V.<br /><br />
You may also reload the metadata embedded in a PNG, WEBP or JPEG image (enable embedding from the Settings).
</span></i>
<div id="init_image_preview_container" class="image_preview_container">
<div id="init_image_wrapper" class="preview_image_wrapper">
@ -141,7 +156,12 @@
<div><table>
<tr><b class="settings-subheader">Image Settings</b></tr>
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="0" onkeypress="preventNonNumericalInput(event)"> <input id="random_seed" name="random_seed" type="checkbox" checked><label for="random_seed">Random</label></td></tr>
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td><td><input id="num_outputs_total" name="num_outputs_total" value="1" size="1" onkeypress="preventNonNumericalInput(event)"> <label><small>(total)</small></label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="1" onkeypress="preventNonNumericalInput(event)"> <label id="num_outputs_parallel_label" for="num_outputs_parallel"><small>(in parallel)</small></label></td></tr>
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td>
<td><input id="num_outputs_total" name="num_outputs_total" value="1" type="number" value="1" min="1" step="1" onkeypres"="preventNonNumericalInput(event)">
<label><small>(total)</small></label>
<input id="num_outputs_parallel" name="num_outputs_parallel" value="1" type="number" value="1" min="1" step="1" onkeypress="preventNonNumericalInput(event)">
<label id="num_outputs_parallel_label" for="num_outputs_parallel"><small>(in parallel)</small></label></td>
</tr>
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td class="model-input">
<input id="stable_diffusion_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
<button id="reload-models" class="secondaryButton reloadModels"><i class='fa-solid fa-rotate'></i></button>
@ -270,7 +290,7 @@
<option value="1792">1792</option>
<option value="2048">2048</option>
</select>
<label for="width"><small>(width)</small></label>
<label id="widthLabel" for="width"><small><span>(width)</span></small></label>
<span id="swap-width-height" class="clickable smallButton" style="margin-left: 2px; margin-right:2px;"><i class="fa-solid fa-right-left"><span class="simple-tooltip top-left"> Swap width and height </span></i></span>
<select id="height" name="height" value="512">
<option value="128">128</option>
@ -293,7 +313,7 @@
<option value="1792">1792</option>
<option value="2048">2048</option>
</select>
<label for="height"><small>(height)</small></label>
<label id="heightLabel" for="height"><small><span>(height)</span></small></label>
<div id="recent-resolutions-container">
<span id="recent-resolutions-button" class="clickable"><i class="fa-solid fa-sliders"><span class="simple-tooltip top-left"> Advanced sizes </span></i></span>
<div id="recent-resolutions-popup" class="displayNone">
@ -301,12 +321,22 @@
<input id="custom-width" name="custom-width" type="number" min="128" value="512" onkeypress="preventNonNumericalInput(event)">
&times;
<input id="custom-height" name="custom-height" type="number" min="128" value="512" onkeypress="preventNonNumericalInput(event)"><br>
<small>Resize:</small><br>
<input id="resize-slider" name="resize-slider" class="editor-slider" value="1" type="range" min="0.4" max="2" step="0.005" style="width:100%;"><br>
<div id="enlarge-buttons"><button data-factor="0.5" class="tertiaryButton smallButton">×0.5</button>&nbsp;<button data-factor="1.2" class="tertiaryButton smallButton">×1.2</button>&nbsp;<button data-factor="1.5" class="tertiaryButton smallButton">×1.5</button>&nbsp;<button data-factor="2" class="tertiaryButton smallButton">×2</button>&nbsp;<button data-factor="3" class="tertiaryButton smallButton">×3</button></div>
<small>Enlarge:</small><br>
<div id="enlarge-buttons"><button id="enlarge15" class="tertiaryButton smallButton">×1.5</button>&nbsp;<button id="enlarge2" class="tertiaryButton smallButton">×2</button>&nbsp;<button id="enlarge3" class="tertiaryButton smallButton">×3</button></div>
<div class="two-column">
<div class="left-column">
<small>Recently&nbsp;used:</small><br>
<div id="recent-resolution-list">
</div>
</div>
<div class="right-column">
<small>Common&nbsp;sizes:</small><br>
<div id="common-resolution-list">
</div>
</div>
<small>Recently&nbsp;used:</small><br>
<div id="recent-resolution-list">
</div>
</div>
</div>
@ -320,11 +350,10 @@
<label for="lora_model">LoRA:</label>
</td>
<td class="diffusers-restart-needed">
<div class="model_entries"></div>
<button class="add_model_entry"><i class="fa-solid fa-plus"></i> add another LoRA</button>
<div id="lora_model" data-path=""></div>
</td>
</tr>
<tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</label></td><td>
<tr id="hypernetwork_model_container" class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</label></td><td>
<input id="hypernetwork_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
</td></tr>
<tr id="hypernetwork_strength_container" class="pl-5">
@ -389,7 +418,7 @@
</div>
</div>
<label><small><b>Note:</b> The Image Modifiers section has moved to the <code>+ Image Modifiers</code> button at the top, just below the Prompt textbox.</small></label>
<label><small><b>Note:</b> The Image Modifiers section has moved to the <code>+ Image Modifiers</code> button at the top, just above the Prompt textbox.</small></label>
</div>
<div id="preview" class="col-free">
@ -403,7 +432,7 @@
<div id="preview-content">
<div id="preview-tools" class="displayNone">
<button id="clear-all-previews" class="secondaryButton"><i class="fa-solid fa-trash-can icon"></i> Clear All</button>
<button class="tertiaryButton" id="show-download-popup"><i class="fa-solid fa-download"></i> Download images</button>
<button class="tertiaryButton" id="show-download-popup"><i class="fa-solid fa-download"></i><span> Download images</span></button>
<div class="display-settings">
<button id="undo" class="displayNone primaryButton">
Undo <i class="fa-solid fa-rotate-left icon"></i>
@ -432,6 +461,9 @@
</div>
<div class="clearfix" style="clear: both;"></div>
</div>
<div id="supportBanner" class="displayNone">
If you found this project useful and want to help keep it alive, please consider <a href="https://ko-fi.com/easydiffusion" target="_blank">buying me a coffee</a> or <a href="https://www.patreon.com/EasyDiffusion" target="_blank">supporting me on Patreon</a> to help cover the cost of development and maintenance! Or even better, <a href="https://cmdr2.itch.io/easydiffusion" target="_blank">purchasing it at the full price</a>. Thank you for your support!
</div>
</div>
</div>
</div>
@ -669,6 +701,8 @@
<span>
</div>
<div id="embeddings-dialog-header-right">
<button id="add-embeddings-thumb" class="tertiaryButton smallButton" style="background-color: var(--background-color4);"><i class="fa-solid fa-folder-plus"></i> Add thumbnail</button>
<input id="add-embeddings-thumb-input" name="add-embeddings-thumb-input" type="file" class="displayNone">
<i id="embeddings-dialog-close-button" class="fa-solid fa-xmark fa-lg"></i>
</div>
</div>
@ -679,6 +713,15 @@
</button>
<i class="fa-solid fa-magnifying-glass"></i>
<input id="embeddings-search-box" type="text" spellcheck="false" autocomplete="off" placeholder="Search...">
<label for="embedding-card-size-selector"><small>Thumbnail Size:</small></label>
<select id="embedding-card-size-selector" name="embedding-card-size-selector">
<option value="-2">0</option>
<option value="-1" selected>1</option>
<option value="0">2</option>
<option value="1">3</option>
<option value="2">4</option>
<option value="3">5</option>
</select>
<span style="float:right;"><label>Mode:</label>&nbsp;<select id="embeddings-mode"><option value="insert">Insert at cursor position</option><option value="append">Append at the end</option></select>
</div>
<div id="embeddings-list">
@ -686,6 +729,34 @@
</div>
</dialog>
<dialog id="use-as-thumb-dialog">
<div id="use-as-thumb-dialog-header" class="dialog-header">
<div id="use-as-thumb-dialog-header-left" class="dialog-header-left">
<h4>Use as thumbnail</h4>
<span>Use a pictures as thumbnail for embeddings, LORAs, etc.</span>
</div>
<div id="use-as-thumb-dialog-header-right">
<i id="use-as-thumb-dialog-close-button" class="fa-solid fa-xmark fa-lg"></i>
</div>
</div>
<div>
<div class="use-as-thumb-grid">
<div class="use-as-thumb-preview">
<div id="use-as-thumb-img-container"><img id="use-as-thumb-image" src="/media/images/noimg.png" width="512" height="512"></div>
</div>
<div class="use-as-thumb-select">
<label for="use-as-thumb-select">Use the thumbnail for:</label><br>
<select id="use-as-thumb-select" size="16" multiple>
</select>
</div>
<div class="use-as-thumb-buttons">
<button class="tertiaryButton" id="use-as-thumb-save">Save thumbnail</button>
<button class="tertiaryButton" id="use-as-thumb-cancel">Cancel</button>
</div>
</div>
</div>
</dialog>
<div id="image-editor" class="popup image-editor-popup">
<div>
<i class="close-button fa-solid fa-xmark"></i>
@ -721,7 +792,6 @@
<div id="footer-spacer"></div>
<div id="footer">
<div class="line-separator">&nbsp;</div>
<p>If you found this project useful and want to help keep it alive, please <a href="https://ko-fi.com/easydiffusion" target="_blank"><img src="/media/images/kofi.png" id="coffeeButton"></a> to help cover the cost of development and maintenance! Thank you for your support!</p>
<p>Please feel free to join the <a href="https://discord.com/invite/u9yhsFmEkB" target="_blank">discord community</a> or <a href="https://github.com/easydiffusion/easydiffusion/issues" target="_blank">file an issue</a> if you have any problems or suggestions in using this interface.</p>
<div id="footer-legal">
<p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
@ -739,6 +809,8 @@
<script src="media/js/auto-save.js"></script>
<script src="media/js/searchable-models.js"></script>
<script src="media/js/multi-model-selector.js"></script>
<script src="media/js/task-manager.js"></script>
<script src="media/js/main.js"></script>
<script src="media/js/plugins.js"></script>
<script src="media/js/themes.js"></script>

View File

@ -1,4 +1,4 @@
from easydiffusion import model_manager, app, server
from easydiffusion import model_manager, app, server, bucket_manager
from easydiffusion.server import server_api # required for uvicorn
app.init()
@ -8,6 +8,7 @@ server.init()
# Init the app
model_manager.init()
app.init_render_threads()
bucket_manager.init()
# start the browser ui
app.open_browser()

58
ui/media/css/croppr.css Normal file
View File

@ -0,0 +1,58 @@
.croppr-container * {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
.croppr-container img {
vertical-align: middle;
max-width: 100%;
}
.croppr {
position: relative;
display: inline-block;
}
.croppr-overlay {
background: rgba(0,0,0,0.5);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
cursor: crosshair;
}
.croppr-region {
border: 1px dashed rgba(0, 0, 0, 0.5);
position: absolute;
z-index: 3;
cursor: move;
top: 0;
}
.croppr-imageClipped {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
pointer-events: none;
}
.croppr-handle {
border: 1px solid black;
background-color: white;
width: 10px;
height: 10px;
position: absolute;
z-index: 4;
top: 0;
}

View File

@ -229,4 +229,27 @@
}
.inpainter .load_mask {
display: flex;
}
}
.editor-canvas-overlay {
cursor: none;
}
.image-brush-preview {
position: fixed;
background: black;
opacity: 0.3;
borderRadius: 50%;
cursor: none;
pointer-events: none;
transform: translate(-50%, -50%);
}
.editor-options-container > * > *:not(.active):not(.button) {
border: 1px dotted slategray;
}
.image_editor_opacity .editor-options-container > * > *:not(.active):not(.button) {
border: 1px dotted slategray;
}

View File

@ -34,6 +34,7 @@ code {
width: 32px;
height: 32px;
transform: translateY(4px);
cursor: pointer;
}
#prompt {
width: 100%;
@ -476,6 +477,7 @@ dialog {
background: var(--background-color2);
color: var(--text-color);
border-radius: 6px;
box-shadow: 0px 0px 30px black;
border: 2px solid rgb(255 255 255 / 10%);
padding: 0px;
}
@ -1088,7 +1090,7 @@ input::file-selector-button {
.tab-content-inner {
margin: 0px;
}
.tab {
#top-nav .tab {
font-size: 0;
}
.tab .icon {
@ -1114,6 +1116,9 @@ input::file-selector-button {
#preview-tools button .icon {
font-size: 12pt;
}
#show-download-popup .fa-solid {
font-size: 12pt;
}
}
@media screen and (max-width: 500px) {
@ -1418,6 +1423,10 @@ div.task-fs-initimage {
display: none;
position: absolute;
}
div.task-fs-initimage img {
max-height: 70vH;
max-width: 70vW;
}
div.task-initimg:hover div.task-fs-initimage {
display: block;
position: absolute;
@ -1433,9 +1442,13 @@ div.top-right {
right: 8px;
}
.task-fs-initimage .top-right button {
margin-top: 6px;
}
#small_image_warning {
font-size: smaller;
color: var(--status-orange);
font-size: smaller;
color: var(--status-orange);
}
button#save-system-settings-btn {
@ -1460,6 +1473,9 @@ button#save-system-settings-btn {
cursor: pointer;;
}
.validation-failed {
border: solid 2px red;
}
/* SCROLLBARS */
:root {
--scrollbar-width: 14px;
@ -1650,6 +1666,35 @@ body.wait-pause {
}
}
.spinner-container {
width: 80px;
height: 100px;
margin: 100px auto;
margin-top: 30vH;
}
.spinner-block {
position: relative;
box-sizing: border-box;
float: left;
margin: 0 10px 10px 0;
width: 12px;
height: 12px;
border-radius: 3px;
background: var(--accent-color);
}
.spinner-block:nth-child(4n+1) { animation: spinner-wave 2s ease .0s infinite; }
.spinner-block:nth-child(4n+2) { animation: spinner-wave 2s ease .2s infinite; }
.spinner-block:nth-child(4n+3) { animation: spinner-wave 2s ease .4s infinite; }
.spinner-block:nth-child(4n+4) { animation: spinner-wave 2s ease .6s infinite; margin-right: 0; }
@keyframes spinner-wave {
0% { top: 0; opacity: 1; }
50% { top: 30px; opacity: .2; }
100% { top: 0; opacity: 1; }
}
#embeddings-dialog {
overflow: clip;
}
@ -1664,6 +1709,12 @@ body.wait-pause {
overflow-y: scroll;
}
@media screen and (max-width: 1400px) {
#embeddings-list {
width: 80vW;
}
}
#embeddings-list button {
margin: 2px;
color: var(--button-color);
@ -1741,6 +1792,32 @@ body.wait-pause {
float: right;
}
.use-as-thumb-grid { display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: 1fr auto;
gap: 8px 8px;
grid-auto-flow: row;
grid-template-areas:
"uat-preview uat-select"
"uat-preview uat-buttons";
}
.use-as-thumb-preview {
justify-self: center;
align-self: center;
grid-area: uat-preview;
}
.use-as-thumb-select {
grid-area: uat-select;
}
.use-as-thumb-buttons {
justify-self: center;
grid-area: uat-buttons;
}
.diffusers-disabled-on-startup .diffusers-restart-needed {
font-size: 0;
}
@ -1778,6 +1855,10 @@ div#recent-resolutions-popup small {
opacity: 0.7;
}
div#common-resolution-list button {
background: var(--background-color1);
}
td#image-size-options small {
margin-right: 0px !important;
}
@ -1794,6 +1875,27 @@ div#enlarge-buttons {
text-align: center;
}
.two-column { display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
gap: 0px 0.5em;
grid-auto-flow: row;
grid-template-areas:
"left-column right-column";
}
.left-column {
justify-self: center;
align-self: center;
grid-area: left-column;
}
.right-column {
justify-self: center;
align-self: center;
grid-area: right-column;
}
.clickable {
cursor: pointer;
}
@ -1828,7 +1930,100 @@ div#enlarge-buttons {
width: 77%;
}
.drop-area {
width: 45%;
height: 50px;
border: 2px dashed #ccc;
text-align: center;
line-height: 50px;
font-size: small;
color: #ccc;
border-radius: 10px;
display: none;
margin: 12px 10px;
}
#num_outputs_total {
width: 42pt;
}
#num_outputs_parallel {
width: 42pt;
}
.model_entry .model_weight {
width: 50pt;
}
/* hack for fixing Image Modifier Improvements plugin */
#imageTagPopupContainer {
position: absolute;
}
@media screen and (max-width: 400px) {
.editor-slider {
width: 40%;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
/* Firefox */
}
#num_outputs_total {
width: 27pt;
}
#num_outputs_parallel {
width: 27pt;
margin-left: -4pt;
}
.model_entry .model_weight {
width: 30pt;
}
#width {
width: 50pt;
}
#height {
width: 50pt;
}
}
@media screen and (max-width: 460px) {
#widthLabel small span {
display: none;
}
#widthLabel small:after {
content: "(w)";
}
#heightLabel small span {
display: none;
}
#heightLabel small:after {
content: "(h)";
}
#prompt-toolbar-right {
text-align: right;
}
#editor-settings label {
font-size: 9pt;
}
#editor-settings .model-filter {
width: 56%;
}
#vae_model {
width: 65% !important;
}
.model_entry .model_name {
width: 60% !important;
}
}
#supportBanner {
font-size: 9pt;
padding: 5pt;
border: 1px solid var(--background-color2);
margin-bottom: 5pt;
border-radius: 4pt;
padding-top: 6pt;
color: var(--small-label-color);
}

BIN
ui/media/images/noimg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -15,14 +15,12 @@ const SETTINGS_IDS_LIST = [
"stable_diffusion_model",
"clip_skip",
"vae_model",
"hypernetwork_model",
"sampler_name",
"width",
"height",
"num_inference_steps",
"guidance_scale",
"prompt_strength",
"hypernetwork_strength",
"tiling",
"output_format",
"output_quality",
@ -45,6 +43,7 @@ const SETTINGS_IDS_LIST = [
"sound_toggle",
"vram_usage_level",
"confirm_dangerous_actions",
"profileName",
"metadata_output_format",
"auto_save_settings",
"apply_color_correction",
@ -54,10 +53,18 @@ const SETTINGS_IDS_LIST = [
"zip_toggle",
"tree_toggle",
"json_toggle",
"extract_lora_from_prompt",
"embedding-card-size-selector",
"lora_model",
]
const IGNORE_BY_DEFAULT = ["prompt"]
if (!testDiffusers.checked) {
SETTINGS_IDS_LIST.push("hypernetwork_model")
SETTINGS_IDS_LIST.push("hypernetwork_strength")
}
const SETTINGS_SECTIONS = [
// gets the "keys" property filled in with an ordered list of settings in this section via initSettings
{ id: "editor-inputs", name: "Prompt" },

1189
ui/media/js/croppr.js Executable file

File diff suppressed because it is too large Load Diff

View File

@ -289,42 +289,56 @@ const TASK_MAPPING = {
readUI: () => vaeModelField.value,
parse: (val) => val,
},
use_controlnet_model: {
name: "ControlNet model",
setUI: (use_controlnet_model) => {
controlnetModelField.value = getModelPath(use_controlnet_model, [".pth", ".safetensors"])
},
readUI: () => controlnetModelField.value,
parse: (val) => val,
},
control_filter_to_apply: {
name: "ControlNet Filter",
setUI: (control_filter_to_apply) => {
controlImageFilterField.value = control_filter_to_apply
},
readUI: () => controlImageFilterField.value,
parse: (val) => val,
},
use_lora_model: {
name: "LoRA model",
setUI: (use_lora_model) => {
// create rows
for (let i = loraModels.length; i < use_lora_model.length; i++) {
createLoraEntry()
}
use_lora_model.forEach((model_name, i) => {
let field = loraModels[i][0]
const oldVal = field.value
if (model_name !== "") {
model_name = getModelPath(model_name, [".ckpt", ".safetensors"])
model_name = model_name !== "" ? model_name : oldVal
let modelPaths = []
use_lora_model = Array.isArray(use_lora_model) ? use_lora_model : [use_lora_model]
use_lora_model.forEach((m) => {
if (m.includes("models\\lora\\")) {
m = m.split("models\\lora\\")[1]
} else if (m.includes("models\\\\lora\\\\")) {
m = m.split("models\\\\lora\\\\")[1]
} else if (m.includes("models/lora/")) {
m = m.split("models/lora/")[1]
}
field.value = model_name
m = m.replaceAll("\\\\", "/")
m = getModelPath(m, [".ckpt", ".safetensors"])
modelPaths.push(m)
})
// clear the remaining entries
let container = document.querySelector("#lora_model_container .model_entries")
for (let i = use_lora_model.length; i < loraModels.length; i++) {
let modelEntry = loraModels[i][2]
container.removeChild(modelEntry)
}
loraModels.splice(use_lora_model.length)
loraModelField.modelNames = modelPaths
},
readUI: () => {
let values = loraModels.map((e) => e[0].value)
values = values.filter((e) => e.trim() !== "")
values = values.length > 0 ? values : "None"
return values
return loraModelField.modelNames
},
parse: (val) => {
val = !val || val === "None" ? "" : val
if (typeof val === "string" && val.includes(",")) {
val = val.split(",")
val = val.map((v) => v.trim())
val = val.map((v) => v.replaceAll("\\", "\\\\"))
val = val.map((v) => v.replaceAll('"', ""))
val = val.map((v) => v.replaceAll("'", ""))
val = val.map((v) => '"' + v + '"')
val = "[" + val + "]"
val = JSON.parse(val)
}
val = Array.isArray(val) ? val : [val]
return val
},
@ -332,31 +346,17 @@ const TASK_MAPPING = {
lora_alpha: {
name: "LoRA Strength",
setUI: (lora_alpha) => {
for (let i = loraModels.length; i < lora_alpha.length; i++) {
createLoraEntry()
}
lora_alpha.forEach((model_strength, i) => {
let field = loraModels[i][1]
field.value = model_strength
})
// clear the remaining entries
let container = document.querySelector("#lora_model_container .model_entries")
for (let i = lora_alpha.length; i < loraModels.length; i++) {
let modelEntry = loraModels[i][2]
container.removeChild(modelEntry)
}
loraModels.splice(lora_alpha.length)
lora_alpha = Array.isArray(lora_alpha) ? lora_alpha : [lora_alpha]
loraModelField.modelWeights = lora_alpha
},
readUI: () => {
let models = loraModels.filter((e) => e[0].value.trim() !== "")
let values = models.map((e) => e[1].value)
values = values.length > 0 ? values : 0
return values
return loraModelField.modelWeights
},
parse: (val) => {
if (typeof val === "string" && val.includes(",")) {
val = "[" + val.replaceAll("'", '"') + "]"
val = JSON.parse(val)
}
val = Array.isArray(val) ? val : [val]
val = val.map((e) => parseFloat(e))
return val
@ -472,11 +472,8 @@ function restoreTaskToUI(task, fieldsToSkip) {
}
if (!("use_lora_model" in task.reqBody)) {
loraModels.forEach((e) => {
e[0].value = ""
e[1].value = 0
e[0].dispatchEvent(new Event("change"))
})
loraModelField.modelNames = []
loraModelField.modelWeights = []
}
// restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d)
@ -519,10 +516,23 @@ function restoreTaskToUI(task, fieldsToSkip) {
)
initImagePreview.src = task.reqBody.init_image
}
// hide/show controlnet picture as needed
if (IMAGE_REGEX.test(controlImagePreview.src) && task.reqBody.control_image == undefined) {
// hide source image
controlImageClearBtn.dispatchEvent(new Event("click"))
} else if (task.reqBody.control_image !== undefined) {
// listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpai
controlImagePreview.src = task.reqBody.control_image
}
}
function readUI() {
const reqBody = {}
for (const key in TASK_MAPPING) {
if (testDiffusers.checked && (key === "use_hypernetwork_model" || key === "hypernetwork_strength")) {
continue
}
reqBody[key] = TASK_MAPPING[key].readUI()
}
return {
@ -569,6 +579,10 @@ const TASK_TEXT_MAPPING = {
use_stable_diffusion_model: "Stable Diffusion model",
use_hypernetwork_model: "Hypernetwork model",
hypernetwork_strength: "Hypernetwork Strength",
use_lora_model: "LoRA model",
lora_alpha: "LoRA Strength",
use_controlnet_model: "ControlNet model",
control_filter_to_apply: "ControlNet Filter",
}
function parseTaskFromText(str) {
const taskReqBody = {}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,256 @@
/**
* A component consisting of multiple model dropdowns, along with a "weight" field per model.
*
* Behaves like a single input element, giving an object in response to the .value field.
*
* Inspired by the design of the ModelDropdown component (searchable-models.js).
*/
class MultiModelSelector {
root
modelType
modelNameFriendly
defaultWeight
weightStep
modelContainer
addNewButton
counter = 0
/* MIMIC A REGULAR INPUT FIELD */
get id() {
return this.root.id
}
get parentElement() {
return this.root.parentElement
}
get parentNode() {
return this.root.parentNode
}
get value() {
return { modelNames: this.modelNames, modelWeights: this.modelWeights }
}
set value(modelData) {
if (typeof modelData !== "object") {
throw new Error("Multi-model selector expects an object containing modelNames and modelWeights as keys!")
}
if (!("modelNames" in modelData) || !("modelWeights" in modelData)) {
throw new Error("modelNames or modelWeights not present in the data passed to the multi-model selector")
}
let newModelNames = modelData["modelNames"]
let newModelWeights = modelData["modelWeights"]
if (newModelNames.length !== newModelWeights.length) {
throw new Error("Need to pass an equal number of modelNames and modelWeights!")
}
// update weight first, name second.
// for some unholy reason this order matters for dispatch chains
// the root of all this unholiness is because searchable-models automatically dispatches an update event
// as soon as the value is updated via JS, which is against the DOM pattern of not dispatching an event automatically
// unless the caller explicitly dispatches the event.
this.modelWeights = newModelWeights
this.modelNames = newModelNames
}
get disabled() {
return false
}
set disabled(state) {
// do nothing
}
getModelElements(ignoreEmpty = false) {
let entries = this.root.querySelectorAll(".model_entry")
entries = [...entries]
let elements = entries.map((e) => {
let modelName = e.querySelector(".model_name").field
let modelWeight = e.querySelector(".model_weight")
if (ignoreEmpty && modelName.value.trim() === "") {
return null
}
return { name: modelName, weight: modelWeight }
})
elements = elements.filter((e) => e !== null)
return elements
}
addEventListener(type, listener, options) {
// do nothing
}
dispatchEvent(event) {
// do nothing
}
appendChild(option) {
// do nothing
}
// remember 'this' - http://blog.niftysnippets.org/2008/04/you-must-remember-this.html
bind(f, obj) {
return function() {
return f.apply(obj, arguments)
}
}
constructor(root, modelType, modelNameFriendly = undefined, defaultWeight = 0.5, weightStep = 0.02) {
this.root = root
this.modelType = modelType
this.modelNameFriendly = modelNameFriendly || modelType
this.defaultWeight = defaultWeight
this.weightStep = weightStep
let self = this
document.addEventListener("refreshModels", function() {
setTimeout(self.bind(self.populateModels, self), 1)
})
this.createStructure()
this.populateModels()
}
createStructure() {
this.modelContainer = document.createElement("div")
this.modelContainer.className = "model_entries"
this.root.appendChild(this.modelContainer)
this.addNewButton = document.createElement("button")
this.addNewButton.className = "add_model_entry"
this.addNewButton.innerHTML = '<i class="fa-solid fa-plus"></i> add another ' + this.modelNameFriendly
this.addNewButton.addEventListener("click", this.bind(this.addModelEntry, this))
this.root.appendChild(this.addNewButton)
}
populateModels() {
if (this.root.dataset.path === "") {
if (this.length === 0) {
this.addModelEntry() // create a single blank entry
}
} else {
this.value = JSON.parse(this.root.dataset.path)
}
}
addModelEntry() {
let idx = this.counter++
let currLength = this.length
const modelElement = document.createElement("div")
modelElement.className = "model_entry"
modelElement.innerHTML = `
<input id="${this.modelType}_${idx}" class="model_name model-filter" type="text" spellcheck="false" autocomplete="off" data-path="" />
<input class="model_weight" type="number" step="${this.weightStep}" value="${this.defaultWeight}" pattern="^-?[0-9]*\.?[0-9]*$" onkeypress="preventNonNumericalInput(event)">
`
this.modelContainer.appendChild(modelElement)
let modelNameEl = modelElement.querySelector(".model_name")
modelNameEl.field = new ModelDropdown(modelNameEl, this.modelType, "None")
let modelWeightEl = modelElement.querySelector(".model_weight")
let self = this
function makeUpdateEvent(type) {
return function(e) {
e.stopPropagation()
let modelData = self.value
self.root.dataset.path = JSON.stringify(modelData)
self.root.dispatchEvent(new Event(type))
}
}
modelNameEl.addEventListener("change", makeUpdateEvent("change"))
modelNameEl.addEventListener("input", makeUpdateEvent("input"))
modelWeightEl.addEventListener("change", makeUpdateEvent("change"))
modelWeightEl.addEventListener("input", makeUpdateEvent("input"))
let removeBtn = document.createElement("button")
removeBtn.className = "remove_model_btn"
removeBtn.setAttribute("title", "Remove model")
removeBtn.innerHTML = '<i class="fa-solid fa-minus"></i>'
if (currLength === 0) {
removeBtn.classList.add("displayNone")
}
removeBtn.addEventListener(
"click",
this.bind(function(e) {
this.modelContainer.removeChild(modelElement)
makeUpdateEvent("change")(e)
}, this)
)
modelElement.appendChild(removeBtn)
}
removeModelEntry() {
if (this.length === 0) {
return
}
let lastEntry = this.modelContainer.lastElementChild
lastEntry.remove()
}
get length() {
return this.getModelElements().length
}
get modelNames() {
return this.getModelElements(true).map((e) => e.name.value)
}
set modelNames(newModelNames) {
this.resizeEntryList(newModelNames.length)
if (newModelNames.length === 0) {
this.getModelElements()[0].name.value = ""
}
// assign to the corresponding elements
let currElements = this.getModelElements()
for (let i = 0; i < newModelNames.length; i++) {
let curr = currElements[i]
curr.name.value = newModelNames[i]
}
}
get modelWeights() {
return this.getModelElements(true).map((e) => e.weight.value)
}
set modelWeights(newModelWeights) {
this.resizeEntryList(newModelWeights.length)
if (newModelWeights.length === 0) {
this.getModelElements()[0].weight.value = this.defaultWeight
}
// assign to the corresponding elements
let currElements = this.getModelElements()
for (let i = 0; i < newModelWeights.length; i++) {
let curr = currElements[i]
curr.weight.value = newModelWeights[i]
}
}
resizeEntryList(newLength) {
if (newLength === 0) {
newLength = 1
}
let currLength = this.length
if (currLength < newLength) {
for (let i = currLength; i < newLength; i++) {
this.addModelEntry()
}
} else {
for (let i = newLength; i < currLength; i++) {
this.removeModelEntry()
}
}
}
}

View File

@ -121,6 +121,15 @@ var PARAMETERS = [
icon: "fa-arrow-down-short-wide",
default: false,
},
{
id: "extract_lora_from_prompt",
type: ParameterType.checkbox,
label: "Extract LoRA tags from the prompt",
note:
"Automatically extract lora tags like &lt;lora:name:0.4&gt; from the prompt, and apply the correct LoRA (if present)",
icon: "fa-code",
default: true,
},
{
id: "ui_open_browser_on_start",
type: ParameterType.checkbox,
@ -185,6 +194,17 @@ var PARAMETERS = [
icon: "fa-check-double",
default: true,
},
{
id: "profileName",
type: ParameterType.custom,
label: "Profile Name",
note:
"Name of the profile for model manager settings, e.g. thumbnails for embeddings. Use this to have different settings for different users.",
render: (parameter) => {
return `<input id="${parameter.id}" name="${parameter.id}" value="default" size="12">`
},
icon: "fa-user-gear",
},
{
id: "listen_to_network",
type: ParameterType.checkbox,
@ -220,11 +240,11 @@ var PARAMETERS = [
{
id: "test_diffusers",
type: ParameterType.checkbox,
label: "Test Diffusers",
label: "Use the new v3 engine (diffusers)",
note:
"<b>Experimental! Can have bugs!</b> Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.",
"Use our new v3 engine, with additional features like LoRA, ControlNet, SDXL, Embeddings, Tiling and lots more! Please press Save, then restart the program after changing this.",
icon: "fa-bolt",
default: false,
default: true,
saveInAppConfig: true,
},
{
@ -401,6 +421,7 @@ let useBetaChannelField = document.querySelector("#use_beta_channel")
let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_start")
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
let testDiffusers = document.querySelector("#test_diffusers")
let profileNameField = document.querySelector("#profileName")
let saveSettingsBtn = document.querySelector("#save-system-settings-btn")
@ -432,8 +453,6 @@ async function getAppConfig() {
if (config.update_branch === "beta") {
useBetaChannelField.checked = true
document.querySelector("#updateBranchLabel").innerText = "(beta)"
} else {
getParameterSettingsEntry("test_diffusers").classList.add("displayNone")
}
if (config.ui && config.ui.open_browser_on_start === false) {
uiOpenBrowserOnStartField.checked = false
@ -445,11 +464,14 @@ async function getAppConfig() {
listenPortField.value = config.net.listen_port
}
const testDiffusersEnabled = config.test_diffusers && config.update_branch !== "main"
let testDiffusersEnabled = true
if (config.test_diffusers === false) {
testDiffusersEnabled = false
}
testDiffusers.checked = testDiffusersEnabled
if (config.config_on_startup) {
if (config.config_on_startup?.test_diffusers && config.update_branch !== "main") {
if (config.config_on_startup?.test_diffusers) {
document.body.classList.add("diffusers-enabled-on-startup")
document.body.classList.remove("diffusers-disabled-on-startup")
} else {
@ -462,7 +484,9 @@ async function getAppConfig() {
document.querySelector("#lora_model_container").style.display = "none"
document.querySelector("#tiling_container").style.display = "none"
document.querySelector("#controlnet_model_container").style.display = "none"
document.querySelector("#hypernetwork_model_container").style.display = ""
document.querySelector("#hypernetwork_strength_container").style.display = ""
document.querySelector("#negative-embeddings-button").style.display = "none"
document.querySelectorAll("#sampler_name option.diffusers-only").forEach((option) => {
option.style.display = "none"
@ -474,6 +498,7 @@ async function getAppConfig() {
document.querySelector("#lora_model_container").style.display = ""
document.querySelector("#tiling_container").style.display = ""
document.querySelector("#controlnet_model_container").style.display = ""
document.querySelector("#hypernetwork_model_container").style.display = "none"
document.querySelector("#hypernetwork_strength_container").style.display = "none"
document.querySelectorAll("#sampler_name option.k_diffusion-only").forEach((option) => {
@ -481,7 +506,6 @@ async function getAppConfig() {
})
document.querySelector("#clip_skip_config").classList.remove("displayNone")
document.querySelector("#embeddings-button").classList.remove("displayNone")
document.querySelector("#negative-embeddings-button").classList.remove("displayNone")
IMAGE_STEP_SIZE = 8
customWidthField.step = IMAGE_STEP_SIZE
customHeightField.step = IMAGE_STEP_SIZE
@ -794,11 +818,3 @@ navigator.permissions.query({ name: "clipboard-write" }).then(function(result) {
})
document.addEventListener("system_info_update", (e) => setDeviceInfo(e.detail))
useBetaChannelField.addEventListener("change", (e) => {
if (e.target.checked) {
getParameterSettingsEntry("test_diffusers").classList.remove("displayNone")
} else {
getParameterSettingsEntry("test_diffusers").classList.add("displayNone")
}
})

View File

@ -118,13 +118,16 @@ class ModelDropdown {
)
}
saveCurrentSelection(elem, value, path) {
saveCurrentSelection(elem, value, path, dispatchEvent = true) {
this.currentSelection.elem = elem
this.currentSelection.value = value
this.currentSelection.path = path
this.modelFilter.dataset.path = path
this.modelFilter.value = value
this.modelFilter.dispatchEvent(new Event("change"))
if (dispatchEvent) {
this.modelFilter.dispatchEvent(new Event("change"))
}
}
processClick(e) {
@ -348,13 +351,13 @@ class ModelDropdown {
}
}
selectEntry(path) {
selectEntry(path, dispatchEvent = true) {
if (path !== undefined) {
const entries = this.modelElements
for (const elem of entries) {
if (elem.dataset.path == path) {
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path, dispatchEvent)
this.highlightedModelEntry = elem
elem.scrollIntoView({ block: "nearest" })
break
@ -529,7 +532,7 @@ class ModelDropdown {
rootModelList.style.minWidth = modelFilterStyle.width
})
this.selectEntry(this.activeModel)
this.selectEntry(this.activeModel, false)
}
/**

409
ui/media/js/task-manager.js Normal file
View File

@ -0,0 +1,409 @@
const htmlTaskMap = new WeakMap()
const pauseBtn = document.querySelector("#pause")
const resumeBtn = document.querySelector("#resume")
const processOrder = document.querySelector("#process_order_toggle")
let pauseClient = false
async function onIdle() {
const serverCapacity = SD.serverCapacity
if (pauseClient === true) {
await resumeClient()
}
for (const taskEntry of getUncompletedTaskEntries()) {
if (SD.activeTasks.size >= serverCapacity) {
break
}
const task = htmlTaskMap.get(taskEntry)
if (!task) {
const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel")
taskStatusLabel.style.display = "none"
continue
}
await onTaskStart(task)
}
}
function getUncompletedTaskEntries() {
const taskEntries = Array.from(document.querySelectorAll("#preview .imageTaskContainer .taskStatusLabel"))
.filter((taskLabel) => taskLabel.style.display !== "none")
.map(function(taskLabel) {
let imageTaskContainer = taskLabel.parentNode
while (!imageTaskContainer.classList.contains("imageTaskContainer") && imageTaskContainer.parentNode) {
imageTaskContainer = imageTaskContainer.parentNode
}
return imageTaskContainer
})
if (!processOrder.checked) {
taskEntries.reverse()
}
return taskEntries
}
async function onTaskStart(task) {
if (!task.isProcessing || task.batchesDone >= task.batchCount) {
return
}
if (typeof task.startTime !== "number") {
task.startTime = Date.now()
}
if (!("instances" in task)) {
task["instances"] = []
}
task["stopTask"].innerHTML = '<i class="fa-solid fa-circle-stop"></i> Stop'
task["taskStatusLabel"].innerText = "Starting"
task["taskStatusLabel"].classList.add("waitingTaskLabel")
if (task.previewTaskReq !== undefined) {
let controlImagePreview = task.taskConfig.querySelector(".controlnet-img-preview > img")
try {
let result = await SD.filter(task.previewTaskReq)
controlImagePreview.src = result.output[0]
let controlImageLargePreview = task.taskConfig.querySelector(
".controlnet-img-preview .task-fs-initimage img"
)
controlImageLargePreview.src = controlImagePreview.src
} catch (error) {
console.log("filter error", error)
}
delete task.previewTaskReq
}
let newTaskReqBody = task.reqBody
if (task.batchCount > 1) {
// Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed.
newTaskReqBody = Object.assign({}, task.reqBody)
if (task.batchesDone == task.batchCount - 1) {
// Last batch of the task
// If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images
// E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image.
newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1)
}
}
const startSeed = task.seed || newTaskReqBody.seed
const genSeeds = Boolean(
typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1)
)
if (genSeeds) {
newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs
}
const outputContainer = document.createElement("div")
outputContainer.className = "img-batch"
task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild)
const eventInfo = { reqBody: newTaskReqBody }
const callbacksPromises = PLUGINS["TASK_CREATE"].map((hook) => {
if (typeof hook !== "function") {
console.error("The provided TASK_CREATE hook is not a function. Hook: %o", hook)
return Promise.reject(new Error("hook is not a function."))
}
try {
return Promise.resolve(hook.call(task, eventInfo))
} catch (err) {
console.error(err)
return Promise.reject(err)
}
})
await Promise.allSettled(callbacksPromises)
let instance = eventInfo.instance
if (!instance) {
const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format)
if (factory) {
instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody))
}
if (!instance) {
console.error(
`${factory ? "Factory " + String(factory) : "No factory defined"} for output format ${eventInfo.reqBody
?.output_format || newTaskReqBody.output_format}. Instance is ${instance ||
"undefined"}. Using default renderer.`
)
instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody)
}
}
task["instances"].push(instance)
task.batchesDone++
document.dispatchEvent(new CustomEvent("before_task_start", { detail: { task: task } }))
instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then(
(renderResult) => {
onRenderTaskCompleted(task, newTaskReqBody, instance, outputContainer, renderResult)
},
(reason) => {
onTaskErrorHandler(task, newTaskReqBody, instance, reason)
}
)
document.dispatchEvent(new CustomEvent("after_task_start", { detail: { task: task } }))
}
function getTaskUpdater(task, reqBody, outputContainer) {
const outputMsg = task["outputMsg"]
const progressBar = task["progressBar"]
const progressBarInner = progressBar.querySelector("div")
const batchCount = task.batchCount
let lastStatus = undefined
return async function(event) {
if (this.status !== lastStatus) {
lastStatus = this.status
switch (this.status) {
case SD.TaskStatus.pending:
task["taskStatusLabel"].innerText = "Pending"
task["taskStatusLabel"].classList.add("waitingTaskLabel")
break
case SD.TaskStatus.waiting:
task["taskStatusLabel"].innerText = "Waiting"
task["taskStatusLabel"].classList.add("waitingTaskLabel")
task["taskStatusLabel"].classList.remove("activeTaskLabel")
break
case SD.TaskStatus.processing:
case SD.TaskStatus.completed:
task["taskStatusLabel"].innerText = "Processing"
task["taskStatusLabel"].classList.add("activeTaskLabel")
task["taskStatusLabel"].classList.remove("waitingTaskLabel")
break
case SD.TaskStatus.stopped:
break
case SD.TaskStatus.failed:
if (!SD.isServerAvailable()) {
logError(
"Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.",
event,
outputMsg
)
} else if (typeof event?.response === "object") {
let msg = "Stable Diffusion had an error reading the response:<br/><pre>"
if (this.exception) {
msg += `Error: ${this.exception.message}<br/>`
}
try {
// 'Response': body stream already read
msg += "Read: " + (await event.response.text())
} catch (e) {
msg += "Unexpected end of stream. "
}
const bufferString = event.reader.bufferedString
if (bufferString) {
msg += "Buffered data: " + bufferString
}
msg += "</pre>"
logError(msg, event, outputMsg)
}
break
}
}
if ("update" in event) {
const stepUpdate = event.update
if (!("step" in stepUpdate)) {
return
}
// task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks)
const instancesWithProgressUpdates = task.instances.filter((instance) => instance.step !== undefined)
const overallStepCount =
instancesWithProgressUpdates.reduce(
(sum, instance) =>
sum +
(instance.isPending
? Math.max(0, instance.step || stepUpdate.step) /
(instance.total_steps || stepUpdate.total_steps)
: 1),
0 // Initial value
) * stepUpdate.total_steps // Scale to current number of steps.
const totalSteps = instancesWithProgressUpdates.reduce(
(sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps),
stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps)
)
const percent = Math.min(100, 100 * (overallStepCount / totalSteps)).toFixed(0)
const timeTaken = stepUpdate.step_time // sec
const stepsRemaining = Math.max(0, totalSteps - overallStepCount)
const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000)
outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}`
outputMsg.style.display = "block"
progressBarInner.style.width = `${percent}%`
if (stepUpdate.output) {
document.dispatchEvent(
new CustomEvent("on_task_step", {
detail: {
task: task,
reqBody: reqBody,
stepUpdate: stepUpdate,
outputContainer: outputContainer,
},
})
)
}
}
}
}
function onRenderTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) {
if (typeof stepUpdate === "object") {
if (stepUpdate.status === "succeeded") {
document.dispatchEvent(
new CustomEvent("on_render_task_success", {
detail: {
task: task,
reqBody: reqBody,
stepUpdate: stepUpdate,
outputContainer: outputContainer,
},
})
)
} else {
task.isProcessing = false
document.dispatchEvent(
new CustomEvent("on_render_task_fail", {
detail: {
task: task,
reqBody: reqBody,
stepUpdate: stepUpdate,
outputContainer: outputContainer,
},
})
)
}
}
if (task.isProcessing && task.batchesDone < task.batchCount) {
task["taskStatusLabel"].innerText = "Pending"
task["taskStatusLabel"].classList.add("waitingTaskLabel")
task["taskStatusLabel"].classList.remove("activeTaskLabel")
return
}
if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) {
return
}
task.isProcessing = false
task["stopTask"].innerHTML = '<i class="fa-solid fa-trash-can"></i> Remove'
task["taskStatusLabel"].style.display = "none"
let time = millisecondsToStr(Date.now() - task.startTime)
if (task.batchesDone == task.batchCount) {
if (!task.outputMsg.innerText.toLowerCase().includes("error")) {
task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}`
}
task.progressBar.style.height = "0px"
task.progressBar.style.border = "0px solid var(--background-color3)"
task.progressBar.classList.remove("active")
// setStatus("request", "done", "success")
} else {
task.outputMsg.innerText += `. Task ended after ${time}`
}
// if (randomSeedField.checked) { // we already update this before the task starts
// seedField.value = task.seed
// }
if (SD.activeTasks.size > 0) {
return
}
const uncompletedTasks = getUncompletedTaskEntries()
if (uncompletedTasks && uncompletedTasks.length > 0) {
return
}
if (pauseClient) {
resumeBtn.click()
}
document.dispatchEvent(
new CustomEvent("on_all_tasks_complete", {
detail: {},
})
)
}
function resumeClient() {
if (pauseClient) {
document.body.classList.remove("wait-pause")
document.body.classList.add("pause")
}
return new Promise((resolve) => {
let playbuttonclick = function() {
resumeBtn.removeEventListener("click", playbuttonclick)
resolve("resolved")
}
resumeBtn.addEventListener("click", playbuttonclick)
})
}
function abortTask(task) {
if (!task.isProcessing) {
return false
}
task.isProcessing = false
task.progressBar.classList.remove("active")
task["taskStatusLabel"].style.display = "none"
task["stopTask"].innerHTML = '<i class="fa-solid fa-trash-can"></i> Remove'
if (!task.instances?.some((r) => r.isPending)) {
return
}
task.instances.forEach((instance) => {
try {
instance.abort()
} catch (e) {
console.error(e)
}
})
}
async function stopAllTasks() {
getUncompletedTaskEntries().forEach((taskEntry) => {
const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel")
if (taskStatusLabel) {
taskStatusLabel.style.display = "none"
}
const task = htmlTaskMap.get(taskEntry)
if (!task) {
return
}
abortTask(task)
})
}
function onTaskErrorHandler(task, reqBody, instance, reason) {
if (!task.isProcessing) {
return
}
console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason)
abortTask(task)
const outputMsg = task["outputMsg"]
logError(
"Stable Diffusion had an error. Please check the logs in the command-line window. <br/><br/>" +
reason +
"<br/><pre>" +
reason.stack +
"</pre>",
task,
outputMsg
)
// setStatus("request", "error", "error")
}
pauseBtn.addEventListener("click", function() {
pauseClient = true
pauseBtn.style.display = "none"
resumeBtn.style.display = "inline"
document.body.classList.add("wait-pause")
})
resumeBtn.addEventListener("click", function() {
pauseClient = false
resumeBtn.style.display = "none"
pauseBtn.style.display = "inline"
document.body.classList.remove("pause")
document.body.classList.remove("wait-pause")
})

View File

@ -1097,6 +1097,48 @@ async function deleteKeys(keyToDelete) {
}
}
/**
* @param {String} Data URL of the image
* @param {Integer} Top left X-coordinate of the crop area
* @param {Integer} Top left Y-coordinate of the crop area
* @param {Integer} Width of the crop area
* @param {Integer} Height of the crop area
* @return {String}
*/
function cropImageDataUrl(dataUrl, x, y, width, height) {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = dataUrl
image.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
ctx.drawImage(image, x, y, width, height, 0, 0, width, height)
const croppedDataUrl = canvas.toDataURL('image/png')
resolve(croppedDataUrl)
}
image.onerror = (error) => {
reject(error)
}
})
}
/**
* @param {String} HTML representing a single element
* @return {Element}
*/
function htmlToElement(html) {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
function modalDialogCloseOnBackdropClick(dialog) {
dialog.addEventListener('mousedown', function (event) {
// Firefox creates an event with clientX|Y = 0|0 when choosing an <option>.
@ -1156,4 +1198,37 @@ function makeDialogDraggable(element) {
})() )
}
function logMsg(msg, level, outputMsg) {
if (outputMsg.hasChildNodes()) {
outputMsg.appendChild(document.createElement("br"))
}
if (level === "error") {
outputMsg.innerHTML += '<span style="color: red">Error: ' + msg + "</span>"
} else if (level === "warn") {
outputMsg.innerHTML += '<span style="color: orange">Warning: ' + msg + "</span>"
} else {
outputMsg.innerText += msg
}
console.log(level, msg)
}
function logError(msg, res, outputMsg) {
logMsg(msg, "error", outputMsg)
console.log("request error", res)
console.trace()
// setStatus("request", "error", "error")
}
function playSound() {
const audio = new Audio("/media/ding.mp3")
audio.volume = 0.2
var promise = audio.play()
if (promise !== undefined) {
promise
.then((_) => {})
.catch((error) => {
console.warn("browser blocked autoplay")
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,11 @@
"use strict"
promptField.addEventListener('input', function(e) {
let loraExtractSetting = document.getElementById("extract_lora_from_prompt")
if (!loraExtractSetting.checked) {
return
}
const { LoRA, prompt } = extractLoraTags(e.target.value);
//console.log('e.target: ' + JSON.stringify(LoRA));
@ -20,44 +25,17 @@
}
if (LoRA !== null && LoRA.length > 0 && testDiffusers?.checked) {
for (let i = 0; i < LoRA.length; i++) {
//if (loraModelField.value !== LoRA[0].lora_model) {
// Set the new LoRA value
//console.log("Loading info");
//console.log(LoRA[0].lora_model_0);
//console.log(JSON.stringify(LoRa));
let lora = `lora_model_${i}`;
let alpha = `lora_alpha_${i}`;
let loramodel = document.getElementById(lora);
let alphavalue = document.getElementById(alpha);
loramodel.setAttribute("data-path", LoRA[i].lora_model_0);
loramodel.value = LoRA[i].lora_model_0;
alphavalue.value = LoRA[i].lora_alpha_0;
if (i != LoRA.length - 1)
createLoraEntry();
}
//loraAlphaSlider.value = loraAlphaField.value * 100;
//TBD.value = LoRA[0].blockweights; // block weights not supported by ED at this time
//}
showToast("Prompt successfully processed", LoRA[0].lora_model_0);
//console.log('LoRa: ' + LoRA[0].lora_model_0);
//showToast("Prompt successfully processed", lora_model_0.value);
let modelNames = LoRA.map(e => e.lora_model_0)
let modelWeights = LoRA.map(e => e.lora_alpha_0)
loraModelField.value = {modelNames: modelNames, modelWeights: modelWeights}
showToast("Prompt successfully processed")
}
//promptField.dispatchEvent(new Event('change'));
});
function isModelAvailable(array, searchString) {
const foundItem = array.find(function(item) {
item = item.toString().toLowerCase();
return item === searchString.toLowerCase()
});
return foundItem || "";
}
// extract LoRA tags from strings
function extractLoraTags(prompt) {
// Define the regular expression for the tags
@ -68,11 +46,13 @@
// Iterate over the string, finding matches
for (const match of prompt.matchAll(regex)) {
const modelFileName = isModelAvailable(modelsCache.options.lora, match[1].trim())
if (modelFileName !== "") {
const modelFileName = match[1].trim()
const loraPathes = getAllModelPathes("lora", modelFileName)
if (loraPathes.length > 0) {
const loraPath = loraPathes[0]
// Initialize an object to hold a match
let loraTag = {
lora_model_0: modelFileName,
lora_model_0: loraPath,
}
//console.log("Model:" + modelFileName);