mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2025-06-24 11:51:26 +02:00
Merge branch 'beta' into yaml
This commit is contained in:
commit
af7073d9b6
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ installer
|
|||||||
installer.tar
|
installer.tar
|
||||||
dist
|
dist
|
||||||
.idea/*
|
.idea/*
|
||||||
|
node_modules/*
|
9
.prettierignore
Normal file
9
.prettierignore
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
*.min.*
|
||||||
|
*.py
|
||||||
|
*.json
|
||||||
|
*.html
|
||||||
|
/*
|
||||||
|
!/ui
|
||||||
|
/ui/easydiffusion
|
||||||
|
!/ui/plugins
|
||||||
|
!/ui/media
|
7
.prettierrc.json
Normal file
7
.prettierrc.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
60
CHANGES.md
60
CHANGES.md
@ -2,18 +2,19 @@
|
|||||||
|
|
||||||
## v2.5
|
## v2.5
|
||||||
### Major Changes
|
### Major Changes
|
||||||
- **Nearly twice as fast** - significantly faster speed of image generation. We're now pretty close to automatic1111's speed. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast
|
- **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
|
||||||
- **Mac M1/M2 support** - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae.
|
- **Mac M1/M2 support** - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae.
|
||||||
|
- **AMD support for Linux** - Experimental support for AMD GPUs on Linux. Thanks @DianaNites and @JeLuf.
|
||||||
- **Full support for Stable Diffusion 2.1 (including CPU)** - supports loading v1.4 or v2.0 or v2.1 models seamlessly. No need to enable "Test SD2", and no need to add `sd2_` to your SD 2.0 model file names. Works on CPU as well.
|
- **Full support for Stable Diffusion 2.1 (including CPU)** - supports loading v1.4 or v2.0 or v2.1 models seamlessly. No need to enable "Test SD2", and no need to add `sd2_` to your SD 2.0 model file names. Works on CPU as well.
|
||||||
- **Memory optimized Stable Diffusion 2.1** - you can now use Stable Diffusion 2.1 models, with the same low VRAM optimizations that we've always had for SD 1.4. Please note, the SD 2.0 and 2.1 models require more GPU and System RAM, as compared to the SD 1.4 and 1.5 models.
|
- **Memory optimized Stable Diffusion 2.1** - you can now use Stable Diffusion 2.1 models, with the same low VRAM optimizations that we've always had for SD 1.4. Please note, the SD 2.0 and 2.1 models require more GPU and System RAM, as compared to the SD 1.4 and 1.5 models.
|
||||||
- **11 new samplers!** - explore the new samplers, some of which can generate great images in less than 10 inference steps! We've added the Karras and UniPC samplers. Thanks @Schorny for the UniPC samplers.
|
- **11 new samplers!** - explore the new samplers, some of which can generate great images in less than 10 inference steps! We've added the Karras and UniPC samplers. Thanks @Schorny for the UniPC samplers.
|
||||||
- **Model Merging** - You can now merge two models (`.ckpt` or `.safetensors`) and output `.ckpt` or `.safetensors` models, optionally in `fp16` precision. Details: https://github.com/cmdr2/stable-diffusion-ui/wiki/Model-Merging . Thanks @JeLuf.
|
- **Model Merging** - You can now merge two models (`.ckpt` or `.safetensors`) and output `.ckpt` or `.safetensors` models, optionally in `fp16` precision. Details: https://github.com/easydiffusion/easydiffusion/wiki/Model-Merging . Thanks @JeLuf.
|
||||||
- **Fast loading/unloading of VAEs** - No longer needs to reload the entire Stable Diffusion model, each time you change the VAE
|
- **Fast loading/unloading of VAEs** - No longer needs to reload the entire Stable Diffusion model, each time you change the VAE
|
||||||
- **Database of known models** - automatically picks the right configuration for known models. E.g. we automatically detect and apply "v" parameterization (required for some SD 2.0 models), and "fp32" attention precision (required for some SD 2.1 models).
|
- **Database of known models** - automatically picks the right configuration for known models. E.g. we automatically detect and apply "v" parameterization (required for some SD 2.0 models), and "fp32" attention precision (required for some SD 2.1 models).
|
||||||
- **Color correction for img2img** - an option to preserve the color profile (histogram) of the initial image. This is especially useful if you're getting red-tinted images after inpainting/masking.
|
- **Color correction for img2img** - an option to preserve the color profile (histogram) of the initial image. This is especially useful if you're getting red-tinted images after inpainting/masking.
|
||||||
- **Three GPU Memory Usage Settings** - `High` (fastest, maximum VRAM usage), `Balanced` (default - almost as fast, significantly lower VRAM usage), `Low` (slowest, very low VRAM usage). The `Low` setting is applied automatically for GPUs with less than 4 GB of VRAM.
|
- **Three GPU Memory Usage Settings** - `High` (fastest, maximum VRAM usage), `Balanced` (default - almost as fast, significantly lower VRAM usage), `Low` (slowest, very low VRAM usage). The `Low` setting is applied automatically for GPUs with less than 4 GB of VRAM.
|
||||||
- **Find models in sub-folders** - This allows you to organize your models into sub-folders inside `models/stable-diffusion`, instead of keeping them all in a single folder. Thanks @patriceac and @ogmaresca.
|
- **Find models in sub-folders** - This allows you to organize your models into sub-folders inside `models/stable-diffusion`, instead of keeping them all in a single folder. Thanks @patriceac and @ogmaresca.
|
||||||
- **Custom Modifier Categories** - Ability to create custom modifiers with thumbnails, and custom categories (and hierarchy of categories). Details: https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Modifiers . Thanks @ogmaresca.
|
- **Custom Modifier Categories** - Ability to create custom modifiers with thumbnails, and custom categories (and hierarchy of categories). Details: https://github.com/easydiffusion/easydiffusion/wiki/Custom-Modifiers . Thanks @ogmaresca.
|
||||||
- **Embed metadata, or save as TXT/JSON** - You can now embed the metadata directly into the images, or save them as text or json files (choose in the Settings tab). Thanks @patriceac.
|
- **Embed metadata, or save as TXT/JSON** - You can now embed the metadata directly into the images, or save them as text or json files (choose in the Settings tab). Thanks @patriceac.
|
||||||
- **Major rewrite of the code** - Most of the codebase has been reorganized and rewritten, to make it more manageable and easier for new developers to contribute features. We've separated our core engine into a new project called `sdkit`, which allows anyone to easily integrate Stable Diffusion (and related modules like GFPGAN etc) into their programming projects (via a simple `pip install sdkit`): https://github.com/easydiffusion/sdkit/
|
- **Major rewrite of the code** - Most of the codebase has been reorganized and rewritten, to make it more manageable and easier for new developers to contribute features. We've separated our core engine into a new project called `sdkit`, which allows anyone to easily integrate Stable Diffusion (and related modules like GFPGAN etc) into their programming projects (via a simple `pip install sdkit`): https://github.com/easydiffusion/sdkit/
|
||||||
- **Name change** - Last, and probably the least, the UI is now called "Easy Diffusion". It indicates the focus of this project - an easy way for people to play with Stable Diffusion.
|
- **Name change** - Last, and probably the least, the UI is now called "Easy Diffusion". It indicates the focus of this project - an easy way for people to play with Stable Diffusion.
|
||||||
@ -21,14 +22,49 @@
|
|||||||
Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed.
|
Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed.
|
||||||
|
|
||||||
### Detailed changelog
|
### Detailed changelog
|
||||||
|
* 2.5.41 - 24 Jun 2023 - (beta-only) Fix broken inpainting in low VRAM usage mode.
|
||||||
|
* 2.5.41 - 24 Jun 2023 - (beta-only) Fix a recent regression where the LoRA would not get applied when changing SD models.
|
||||||
|
* 2.5.41 - 23 Jun 2023 - Fix a regression where latent upscaler stopped working on PCs without a graphics card.
|
||||||
|
* 2.5.41 - 20 Jun 2023 - Automatically fix black images if fp32 attention precision is required in diffusers.
|
||||||
|
* 2.5.41 - 19 Jun 2023 - Another fix for multi-gpu rendering (in all VRAM usage modes).
|
||||||
|
* 2.5.41 - 13 Jun 2023 - Fix multi-gpu bug with "low" VRAM usage mode while generating images.
|
||||||
|
* 2.5.41 - 12 Jun 2023 - Fix multi-gpu bug with CodeFormer.
|
||||||
|
* 2.5.41 - 6 Jun 2023 - Allow changing the strength of CodeFormer, and slightly improved styling of the CodeFormer options.
|
||||||
|
* 2.5.41 - 5 Jun 2023 - Allow sharing an Easy Diffusion instance via https://try.cloudflare.com/ . You can find this option at the bottom of the Settings tab. Thanks @JeLuf.
|
||||||
|
* 2.5.41 - 5 Jun 2023 - Show an option to download for tiled images. Shows a button on the generated image. Creates larger images by tiling them with the image generated by Easy Diffusion. Thanks @JeLuf.
|
||||||
|
* 2.5.41 - 5 Jun 2023 - (beta-only) Allow LoRA strengths between -2 and 2. Thanks @ogmaresca.
|
||||||
|
* 2.5.40 - 5 Jun 2023 - Reduce the VRAM usage of Latent Upscaling when using "balanced" VRAM usage mode.
|
||||||
|
* 2.5.40 - 5 Jun 2023 - Fix the "realesrgan" key error when using CodeFormer with more than 1 image in a batch.
|
||||||
|
* 2.5.40 - 3 Jun 2023 - Added CodeFormer as another option for fixing faces and eyes. CodeFormer tends to perform better than GFPGAN for many images. Thanks @patriceac for the implementation, and for contacting the CodeFormer team (who were supportive of it being integrated into Easy Diffusion).
|
||||||
|
* 2.5.39 - 25 May 2023 - (beta-only) Seamless Tiling - make seamlessly tiled images, e.g. rock and grass textures. Thanks @JeLuf.
|
||||||
|
* 2.5.38 - 24 May 2023 - Better reporting of errors, and show an explanation if the user cannot disable the "Use CPU" setting.
|
||||||
|
* 2.5.38 - 23 May 2023 - Add Latent Upscaler as another option for upscaling images. Thanks @JeLuf for the implementation of the Latent Upscaler model.
|
||||||
|
* 2.5.37 - 19 May 2023 - (beta-only) Two more samplers: DDPM and DEIS. Also disables the samplers that aren't working yet in the Diffusers version. Thanks @ogmaresca.
|
||||||
|
* 2.5.37 - 19 May 2023 - (beta-only) Support CLIP-Skip. You can set this option under the models dropdown. Thanks @JeLuf.
|
||||||
|
* 2.5.37 - 19 May 2023 - (beta-only) More VRAM optimizations for all modes in diffusers. The VRAM usage for diffusers in "low" and "balanced" should now be equal or less than the non-diffusers version. Performs softmax in half precision, like sdkit does.
|
||||||
|
* 2.5.36 - 16 May 2023 - (beta-only) More VRAM optimizations for "balanced" VRAM usage mode.
|
||||||
|
* 2.5.36 - 11 May 2023 - (beta-only) More VRAM optimizations for "low" VRAM usage mode.
|
||||||
|
* 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode.
|
||||||
|
* 2.5.35 - 8 May 2023 - Allow dragging a zoomed-in image (after opening an image with the "expand" button). Thanks @ogmaresca.
|
||||||
|
* 2.5.35 - 3 May 2023 - (beta-only) First round of VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. The VRAM usage is still not equal to the "non-diffusers" version, but more optimizations are coming soon.
|
||||||
|
* 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf.
|
||||||
|
* 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux).
|
||||||
|
* 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files.
|
||||||
|
* 2.5.32 - 18 Apr 2023 - Automatic support for AMD graphics cards on Linux. Thanks @DianaNites and @JeLuf.
|
||||||
|
* 2.5.31 - 10 Apr 2023 - Reduce VRAM usage while upscaling.
|
||||||
|
* 2.5.31 - 6 Apr 2023 - Allow seeds upto `4,294,967,295`. Thanks @ogmaresca.
|
||||||
|
* 2.5.31 - 6 Apr 2023 - Buttons to show the previous/next image in the image popup. Thanks @ogmaresca.
|
||||||
|
* 2.5.30 - 5 Apr 2023 - Fix a bug where the JPEG image quality wasn't being respected when embedding the metadata into it. Thanks @JeLuf.
|
||||||
|
* 2.5.30 - 1 Apr 2023 - (beta-only) Slider to control the strength of the LoRA model.
|
||||||
|
* 2.5.30 - 28 Mar 2023 - Refactor task entry config to use a generating method. Added ability for plugins to easily add to this. Removed confusing sentence from `contributing.md`
|
||||||
* 2.5.30 - 28 Mar 2023 - Allow the user to undo the deletion of tasks or images, instead of showing a pop-up each time. The new `Undo` button will be present at the top of the UI. Thanks @JeLuf.
|
* 2.5.30 - 28 Mar 2023 - Allow the user to undo the deletion of tasks or images, instead of showing a pop-up each time. The new `Undo` button will be present at the top of the UI. Thanks @JeLuf.
|
||||||
* 2.5.30 - 28 Mar 2023 - Support saving lossless WEBP images. Thanks @ogmaresca.
|
* 2.5.30 - 28 Mar 2023 - Support saving lossless WEBP images. Thanks @ogmaresca.
|
||||||
* 2.5.30 - 28 Mar 2023 - Lots of bug fixes for the UI (Read LoRA flag in metadata files, new prompt weight format with scrollwheel, fix overflow with lots of tabs, clear button in image editor, shorter filenames in download). Thanks @patriceac, @JeLuf and @ogmaresca.
|
* 2.5.30 - 28 Mar 2023 - Lots of bug fixes for the UI (Read LoRA flag in metadata files, new prompt weight format with scrollwheel, fix overflow with lots of tabs, clear button in image editor, shorter filenames in download). Thanks @patriceac, @JeLuf and @ogmaresca.
|
||||||
* 2.5.29 - 27 Mar 2023 - Fix a bug where some non-square images would fail while inpainting with a `The size of tensor a must match size of tensor b` error.
|
* 2.5.29 - 27 Mar 2023 - (beta-only) Fix a bug where some non-square images would fail while inpainting with a `The size of tensor a must match size of tensor b` error.
|
||||||
* 2.5.29 - 27 Mar 2023 - Fix the `incorrect number of channels` error, when given a PNG image with an alpha channel in `Test Diffusers`.
|
* 2.5.29 - 27 Mar 2023 - (beta-only) Fix the `incorrect number of channels` error, when given a PNG image with an alpha channel in `Test Diffusers`.
|
||||||
* 2.5.29 - 27 Mar 2023 - Fix broken inpainting in `Test Diffusers` (beta).
|
* 2.5.29 - 27 Mar 2023 - (beta-only) Fix broken inpainting in `Test Diffusers`.
|
||||||
* 2.5.28 - 24 Mar 2023 - Support for weighted prompts and long prompt lengths (not limited to 77 tokens). This change requires enabling the `Test Diffusers` setting in beta (in the Settings tab), and restarting the program.
|
* 2.5.28 - 24 Mar 2023 - (beta-only) Support for weighted prompts and long prompt lengths (not limited to 77 tokens). This change requires enabling the `Test Diffusers` setting in beta (in the Settings tab), and restarting the program.
|
||||||
* 2.5.27 - 21 Mar 2023 - LoRA support, accessible by enabling the `Test Diffusers` setting (in the Settings tab in the UI). This change switches the internal engine to diffusers (if the `Test Diffusers` setting is enabled). If the `Test Diffusers` flag is disabled, it'll have no impact for the user.
|
* 2.5.27 - 21 Mar 2023 - (beta-only) LoRA support, accessible by enabling the `Test Diffusers` setting (in the Settings tab in the UI). This change switches the internal engine to diffusers (if the `Test Diffusers` setting is enabled). If the `Test Diffusers` flag is disabled, it'll have no impact for the user.
|
||||||
* 2.5.26 - 15 Mar 2023 - Allow styling the buttons displayed on an image. Update the API to allow multiple buttons and text labels in a single row. Thanks @ogmaresca.
|
* 2.5.26 - 15 Mar 2023 - Allow styling the buttons displayed on an image. Update the API to allow multiple buttons and text labels in a single row. Thanks @ogmaresca.
|
||||||
* 2.5.26 - 15 Mar 2023 - View images in full-screen, by either clicking on the image, or clicking the "Full screen" icon next to the Seed number on the image. Thanks @ogmaresca for the internal API.
|
* 2.5.26 - 15 Mar 2023 - View images in full-screen, by either clicking on the image, or clicking the "Full screen" icon next to the Seed number on the image. Thanks @ogmaresca for the internal API.
|
||||||
* 2.5.25 - 14 Mar 2023 - Button to download all the images, and all the metadata as a zip file. This is available at the top of the UI, as well as on each image. Thanks @JeLuf.
|
* 2.5.25 - 14 Mar 2023 - Button to download all the images, and all the metadata as a zip file. This is available at the top of the UI, as well as on each image. Thanks @JeLuf.
|
||||||
@ -36,7 +72,7 @@ Our focus continues to remain on an easy installation experience, and an easy us
|
|||||||
* 2.5.24 - 11 Mar 2023 - Button to load an image mask from a file.
|
* 2.5.24 - 11 Mar 2023 - Button to load an image mask from a file.
|
||||||
* 2.5.24 - 10 Mar 2023 - Logo change. Image credit: @lazlo_vii.
|
* 2.5.24 - 10 Mar 2023 - Logo change. Image credit: @lazlo_vii.
|
||||||
* 2.5.23 - 8 Mar 2023 - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae!
|
* 2.5.23 - 8 Mar 2023 - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae!
|
||||||
* 2.5.23 - 8 Mar 2023 - Ability to create custom modifiers with thumbnails, and custom categories (and hierarchy of categories). More details - https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Modifiers . Thanks @ogmaresca.
|
* 2.5.23 - 8 Mar 2023 - Ability to create custom modifiers with thumbnails, and custom categories (and hierarchy of categories). More details - https://github.com/easydiffusion/easydiffusion/wiki/Custom-Modifiers . Thanks @ogmaresca.
|
||||||
* 2.5.22 - 28 Feb 2023 - Minor styling changes to UI buttons, and the models dropdown.
|
* 2.5.22 - 28 Feb 2023 - Minor styling changes to UI buttons, and the models dropdown.
|
||||||
* 2.5.22 - 28 Feb 2023 - Lots of UI-related bug fixes. Thanks @patriceac.
|
* 2.5.22 - 28 Feb 2023 - Lots of UI-related bug fixes. Thanks @patriceac.
|
||||||
* 2.5.21 - 22 Feb 2023 - An option to control the size of the image thumbnails. You can use the `Display options` in the top-right corner to change this. Thanks @JeLuf.
|
* 2.5.21 - 22 Feb 2023 - An option to control the size of the image thumbnails. You can use the `Display options` in the top-right corner to change this. Thanks @JeLuf.
|
||||||
@ -61,7 +97,7 @@ Our focus continues to remain on an easy installation experience, and an easy us
|
|||||||
* 2.5.14 - 3 Feb 2023 - Fix the 'Make Similar Images' button, which was producing incorrect images (weren't very similar).
|
* 2.5.14 - 3 Feb 2023 - Fix the 'Make Similar Images' button, which was producing incorrect images (weren't very similar).
|
||||||
* 2.5.13 - 1 Feb 2023 - Fix the remaining GPU memory leaks, including a better fix (more comprehensive) for the change in 2.5.12 (27 Jan).
|
* 2.5.13 - 1 Feb 2023 - Fix the remaining GPU memory leaks, including a better fix (more comprehensive) for the change in 2.5.12 (27 Jan).
|
||||||
* 2.5.12 - 27 Jan 2023 - Fix a memory leak, which made the UI unresponsive after an out-of-memory error. The allocated memory is now freed-up after an error.
|
* 2.5.12 - 27 Jan 2023 - Fix a memory leak, which made the UI unresponsive after an out-of-memory error. The allocated memory is now freed-up after an error.
|
||||||
* 2.5.11 - 25 Jan 2023 - UI for Merging Models. Thanks @JeLuf. More info: https://github.com/cmdr2/stable-diffusion-ui/wiki/Model-Merging
|
* 2.5.11 - 25 Jan 2023 - UI for Merging Models. Thanks @JeLuf. More info: https://github.com/easydiffusion/easydiffusion/wiki/Model-Merging
|
||||||
* 2.5.10 - 24 Jan 2023 - Reduce the VRAM usage for img2img in 'balanced' mode (without reducing the rendering speed), to make it similar to v2.4 of this UI.
|
* 2.5.10 - 24 Jan 2023 - Reduce the VRAM usage for img2img in 'balanced' mode (without reducing the rendering speed), to make it similar to v2.4 of this UI.
|
||||||
* 2.5.9 - 23 Jan 2023 - Fix a bug where img2img would produce poorer-quality images for the same settings, as compared to version 2.4 of this UI.
|
* 2.5.9 - 23 Jan 2023 - Fix a bug where img2img would produce poorer-quality images for the same settings, as compared to version 2.4 of this UI.
|
||||||
* 2.5.9 - 23 Jan 2023 - Reduce the VRAM usage for 'balanced' mode (without reducing the rendering speed), to make it similar to v2.4 of the UI.
|
* 2.5.9 - 23 Jan 2023 - Reduce the VRAM usage for 'balanced' mode (without reducing the rendering speed), to make it similar to v2.4 of the UI.
|
||||||
@ -90,8 +126,8 @@ Our focus continues to remain on an easy installation experience, and an easy us
|
|||||||
- **Automatic scanning for malicious model files** - using `picklescan`, and support for `safetensor` model format. Thanks @JeLuf
|
- **Automatic scanning for malicious model files** - using `picklescan`, and support for `safetensor` model format. Thanks @JeLuf
|
||||||
- **Image Editor** - for drawing simple images for guiding the AI. Thanks @mdiller
|
- **Image Editor** - for drawing simple images for guiding the AI. Thanks @mdiller
|
||||||
- **Use pre-trained hypernetworks** - for improving the quality of images. Thanks @C0bra5
|
- **Use pre-trained hypernetworks** - for improving the quality of images. Thanks @C0bra5
|
||||||
- **Support for custom VAE models**. You can place your VAE files in the `models/vae` folder, and refresh the browser page to use them. More info: https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder
|
- **Support for custom VAE models**. You can place your VAE files in the `models/vae` folder, and refresh the browser page to use them. More info: https://github.com/easydiffusion/easydiffusion/wiki/VAE-Variational-Auto-Encoder
|
||||||
- **Experimental support for multiple GPUs!** It should work automatically. Just open one browser tab per GPU, and spread your tasks across your GPUs. For e.g. open our UI in two browser tabs if you have two GPUs. You can customize which GPUs it should use in the "Settings" tab, otherwise let it automatically pick the best GPUs. Thanks @madrang . More info: https://github.com/cmdr2/stable-diffusion-ui/wiki/Run-on-Multiple-GPUs
|
- **Experimental support for multiple GPUs!** It should work automatically. Just open one browser tab per GPU, and spread your tasks across your GPUs. For e.g. open our UI in two browser tabs if you have two GPUs. You can customize which GPUs it should use in the "Settings" tab, otherwise let it automatically pick the best GPUs. Thanks @madrang . More info: https://github.com/easydiffusion/easydiffusion/wiki/Run-on-Multiple-GPUs
|
||||||
- **Cleaner UI design** - Show settings and help in new tabs, instead of dropdown popups (which were buggy). Thanks @mdiller
|
- **Cleaner UI design** - Show settings and help in new tabs, instead of dropdown popups (which were buggy). Thanks @mdiller
|
||||||
- **Progress bar.** Thanks @mdiller
|
- **Progress bar.** Thanks @mdiller
|
||||||
- **Custom Image Modifiers** - You can now save your custom image modifiers! Your saved modifiers can include special characters like `{}, (), [], |`
|
- **Custom Image Modifiers** - You can now save your custom image modifiers! Your saved modifiers can include special characters like `{}, (), [], |`
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
Hi there, these instructions are meant for the developers of this project.
|
Hi there, these instructions are meant for the developers of this project.
|
||||||
|
|
||||||
If you only want to use the Stable Diffusion UI, you've downloaded the wrong file. In that case, please download and follow the instructions at https://github.com/cmdr2/stable-diffusion-ui#installation
|
If you only want to use the Stable Diffusion UI, you've downloaded the wrong file. In that case, please download and follow the instructions at https://github.com/easydiffusion/easydiffusion#installation
|
||||||
|
|
||||||
Thanks
|
Thanks
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ If you would like to contribute to this project, there is a discord for discussi
|
|||||||
This is in-flux, but one way to get a development environment running for editing the UI of this project is:
|
This is in-flux, but one way to get a development environment running for editing the UI of this project is:
|
||||||
(swap `.sh` or `.bat` in instructions depending on your environment, and be sure to adjust any paths to match where you're working)
|
(swap `.sh` or `.bat` in instructions depending on your environment, and be sure to adjust any paths to match where you're working)
|
||||||
|
|
||||||
1) Install the project to a new location using the [usual installation process](https://github.com/cmdr2/stable-diffusion-ui#installation), e.g. to `/projects/stable-diffusion-ui-archive`
|
1) Install the project to a new location using the [usual installation process](https://github.com/easydiffusion/easydiffusion#installation), e.g. to `/projects/stable-diffusion-ui-archive`
|
||||||
2) Start the newly installed project, and check that you can view and generate images on `localhost:9000`
|
2) Start the newly installed project, and check that you can view and generate images on `localhost:9000`
|
||||||
3) Next, please clone the project repository using `git clone` (e.g. to `/projects/stable-diffusion-ui-repo`)
|
3) Next, please clone the project repository using `git clone` (e.g. to `/projects/stable-diffusion-ui-repo`)
|
||||||
4) Close the server (started in step 2), and edit `/projects/stable-diffusion-ui-archive/scripts/on_env_start.sh` (or `on_env_start.bat`)
|
4) Close the server (started in step 2), and edit `/projects/stable-diffusion-ui-archive/scripts/on_env_start.sh` (or `on_env_start.bat`)
|
||||||
@ -42,8 +42,6 @@ or for Windows
|
|||||||
10) Congrats, now any changes you make in your repo `ui` folder are linked to this running archive of the app and can be previewed in the browser.
|
10) Congrats, now any changes you make in your repo `ui` folder are linked to this running archive of the app and can be previewed in the browser.
|
||||||
11) Please update CHANGES.md in your pull requests.
|
11) Please update CHANGES.md in your pull requests.
|
||||||
|
|
||||||
Check the `ui/frontend/build/README.md` for instructions on running and building the React code.
|
|
||||||
|
|
||||||
## Development environment for Installer changes
|
## Development environment for Installer changes
|
||||||
Build the Windows installer using Windows, and the Linux installer using Linux. Don't mix the two, and don't use WSL. An Ubuntu VM is fine for building the Linux installer on a Windows host.
|
Build the Windows installer using Windows, and the Linux installer using Linux. Don't mix the two, and don't use WSL. An Ubuntu VM is fine for building the Linux installer on a Windows host.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
Congrats on downloading Stable Diffusion UI, version 2!
|
Congrats on downloading Stable Diffusion UI, version 2!
|
||||||
|
|
||||||
If you haven't downloaded Stable Diffusion UI yet, please download from https://github.com/cmdr2/stable-diffusion-ui#installation
|
If you haven't downloaded Stable Diffusion UI yet, please download from https://github.com/easydiffusion/easydiffusion#installation
|
||||||
|
|
||||||
After downloading, to install please follow these instructions:
|
After downloading, to install please follow these instructions:
|
||||||
|
|
||||||
@ -16,9 +16,9 @@ To start the UI in the future, please run the same command mentioned above.
|
|||||||
|
|
||||||
|
|
||||||
If you have any problems, please:
|
If you have any problems, please:
|
||||||
1. Try the troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting
|
1. Try the troubleshooting steps at https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting
|
||||||
2. Or, seek help from the community at https://discord.com/invite/u9yhsFmEkB
|
2. Or, seek help from the community at https://discord.com/invite/u9yhsFmEkB
|
||||||
3. Or, file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues
|
3. Or, file an issue at https://github.com/easydiffusion/easydiffusion/issues
|
||||||
|
|
||||||
Thanks
|
Thanks
|
||||||
cmdr2 (and contributors to the project)
|
cmdr2 (and contributors to the project)
|
@ -235,7 +235,7 @@ Section "MainSection" SEC01
|
|||||||
NScurl::http get "https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt" "$INSTDIR\models\stable-diffusion\sd-v1-4.ckpt" /CANCEL /INSIST /END
|
NScurl::http get "https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt" "$INSTDIR\models\stable-diffusion\sd-v1-4.ckpt" /CANCEL /INSIST /END
|
||||||
|
|
||||||
DetailPrint 'Downloading the GFPGAN model...'
|
DetailPrint 'Downloading the GFPGAN model...'
|
||||||
NScurl::http get "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth" "$INSTDIR\models\gfpgan\GFPGANv1.3.pth" /CANCEL /INSIST /END
|
NScurl::http get "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth" "$INSTDIR\models\gfpgan\GFPGANv1.4.pth" /CANCEL /INSIST /END
|
||||||
|
|
||||||
DetailPrint 'Downloading the RealESRGAN_x4plus model...'
|
DetailPrint 'Downloading the RealESRGAN_x4plus model...'
|
||||||
NScurl::http get "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth" "$INSTDIR\models\realesrgan\RealESRGAN_x4plus.pth" /CANCEL /INSIST /END
|
NScurl::http get "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth" "$INSTDIR\models\realesrgan\RealESRGAN_x4plus.pth" /CANCEL /INSIST /END
|
||||||
|
9
PRIVACY.md
Normal file
9
PRIVACY.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// placeholder until a more formal and legal-sounding privacy policy document is written. but the information below is true.
|
||||||
|
|
||||||
|
This is a summary of whether Easy Diffusion uses your data or tracks you:
|
||||||
|
* The short answer is - Easy Diffusion does *not* use your data, and does *not* track you.
|
||||||
|
* Easy Diffusion does not send your prompts or usage or analytics to anyone. There is no tracking. We don't even know how many people use Easy Diffusion, let alone their prompts.
|
||||||
|
* Easy Diffusion fetches updates to the code whenever it starts up. It does this by contacting GitHub directly, via SSL (secure connection). Only your computer and GitHub and [this repository](https://github.com/easydiffusion/easydiffusion) are involved, and no third party is involved. Some countries intercepts SSL connections, that's not something we can do much about. GitHub does *not* share statistics (even with me) about how many people fetched code updates.
|
||||||
|
* Easy Diffusion fetches the models from huggingface.co and github.com, if they don't exist on your PC. For e.g. if the safety checker (NSFW) model doesn't exist, it'll try to download it.
|
||||||
|
* Easy Diffusion fetches code packages from pypi.org, which is the standard hosting service for all Python projects. That's where packages installed via `pip install` are stored.
|
||||||
|
* Occasionally, antivirus software are known to *incorrectly* flag and delete some model files, which will result in Easy Diffusion re-downloading `pytorch_model.bin`. This *incorrect deletion* affects other Stable Diffusion UIs as well, like Invoke AI - https://itch.io/post/7509488
|
@ -3,6 +3,6 @@ Hi there,
|
|||||||
What you have downloaded is meant for the developers of this project, not for users.
|
What you have downloaded is meant for the developers of this project, not for users.
|
||||||
|
|
||||||
If you only want to use the Stable Diffusion UI, you've downloaded the wrong file.
|
If you only want to use the Stable Diffusion UI, you've downloaded the wrong file.
|
||||||
Please download and follow the instructions at https://github.com/cmdr2/stable-diffusion-ui#installation
|
Please download and follow the instructions at https://github.com/easydiffusion/easydiffusion#installation
|
||||||
|
|
||||||
Thanks
|
Thanks
|
36
README.md
36
README.md
@ -1,9 +1,9 @@
|
|||||||
# Easy Diffusion 2.5
|
# Easy Diffusion 2.5
|
||||||
### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your own computer.
|
### 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.
|
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/cmdr2/stable-diffusion-ui/wiki/Troubleshooting) | <sub>[](https://discord.com/invite/u9yhsFmEkB)</sub> <sup>(for support queries, and development discussions)</sup>
|
[Installation guide](#installation) | [Troubleshooting guide](https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting) | <sub>[](https://discord.com/invite/u9yhsFmEkB)</sub> <sup>(for support queries, and development discussions)</sup>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -11,11 +11,18 @@ Does not require technical knowledge, does not require pre-installed software. 1
|
|||||||
Click the download button for your operating system:
|
Click the download button for your operating system:
|
||||||
|
|
||||||
<p float="left">
|
<p float="left">
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.24/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/easydiffusion/easydiffusion/releases/download/v2.5.24/Easy-Diffusion-Windows.exe"><img src="https://github.com/easydiffusion/easydiffusion/raw/main/media/download-win.png" width="200" /></a>
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.24/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/easydiffusion/easydiffusion/releases/download/v2.5.24/Easy-Diffusion-Linux.zip"><img src="https://github.com/easydiffusion/easydiffusion/raw/main/media/download-linux.png" width="200" /></a>
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.24/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/easydiffusion/easydiffusion/releases/download/v2.5.24/Easy-Diffusion-Mac.zip"><img src="https://github.com/easydiffusion/easydiffusion/raw/main/media/download-mac.png" width="200" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
**Hardware requirements:**
|
||||||
|
- **Windows:** NVIDIA graphics card (minimum 2 GB RAM), or run on your CPU.
|
||||||
|
- **Linux:** NVIDIA or AMD graphics card (minimum 2 GB RAM), or run on your CPU.
|
||||||
|
- **Mac:** M1 or M2, or run on your CPU.
|
||||||
|
- Minimum 8 GB of system RAM.
|
||||||
|
- Atleast 25 GB of space on the hard disk.
|
||||||
|
|
||||||
The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance.
|
The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance.
|
||||||
|
|
||||||
## On Windows:
|
## On Windows:
|
||||||
@ -53,7 +60,7 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages.
|
|||||||
|
|
||||||
### Image generation
|
### Image generation
|
||||||
- **Supports**: "*Text to Image*" and "*Image to Image*".
|
- **Supports**: "*Text to Image*" and "*Image to Image*".
|
||||||
- **19 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`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`.
|
- **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.
|
- **In-Painting**: Specify areas of your image to paint into.
|
||||||
- **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program.
|
- **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program.
|
||||||
- **Face Correction (GFPGAN)**
|
- **Face Correction (GFPGAN)**
|
||||||
@ -63,6 +70,7 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages.
|
|||||||
- **Attention/Emphasis**: () in the prompt increases the model's attention to enclosed words, and [] decreases it.
|
- **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`.
|
- **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 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.
|
- **1-click Upscale/Face Correction**: Upscale or correct an image after it has been generated.
|
||||||
- **Make Similar Images**: Click to generate multiple variations of a generated image.
|
- **Make Similar Images**: Click to generate multiple variations of a generated image.
|
||||||
- **NSFW Setting**: A setting in the UI to control *NSFW content*.
|
- **NSFW Setting**: A setting in the UI to control *NSFW content*.
|
||||||
@ -75,11 +83,11 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages.
|
|||||||
- **Use custom VAE models**
|
- **Use custom VAE models**
|
||||||
- **Use pre-trained Hypernetworks**
|
- **Use pre-trained Hypernetworks**
|
||||||
- **Use custom GFPGAN models**
|
- **Use custom GFPGAN models**
|
||||||
- **UI Plugins**: Choose from a growing list of [community-generated UI plugins](https://github.com/cmdr2/stable-diffusion-ui/wiki/UI-Plugins), or write your own plugin to add features to the project!
|
- **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!
|
||||||
|
|
||||||
### Performance and security
|
### Performance and security
|
||||||
- **Fast**: Creates a 512x512 image with euler_a in 5 seconds, on an NVIDIA 3060 12GB.
|
- **Fast**: Creates a 512x512 image with euler_a in 5 seconds, on an NVIDIA 3060 12GB.
|
||||||
- **Low Memory Usage**: Create 512x512 images with less than 3 GB of GPU RAM, and 768x768 images with less than 4 GB of GPU RAM!
|
- **Low Memory Usage**: Create 512x512 images with less than 2 GB of GPU RAM, and 768x768 images with less than 3 GB of GPU RAM!
|
||||||
- **Use CPU setting**: If you don't have a compatible graphics card, but still want to run it on your CPU.
|
- **Use CPU setting**: If you don't have a compatible graphics card, but still want to run it on your CPU.
|
||||||
- **Multi-GPU support**: Automatically spreads your tasks across multiple GPUs (if available), for faster performance!
|
- **Multi-GPU support**: Automatically spreads your tasks across multiple GPUs (if available), for faster performance!
|
||||||
- **Auto scan for malicious models**: Uses picklescan to prevent malicious models.
|
- **Auto scan for malicious models**: Uses picklescan to prevent malicious models.
|
||||||
@ -108,21 +116,13 @@ Useful for judging (and stopping) an image quickly, without waiting for it to fi
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# System Requirements
|
|
||||||
1. Windows 10/11, or Linux. Experimental support for Mac is coming soon.
|
|
||||||
2. An NVIDIA graphics card, preferably with 4GB or more of VRAM. If you don't have a compatible graphics card, it'll automatically run in the slower "CPU Mode".
|
|
||||||
3. Minimum 8 GB of RAM and 25GB of disk space.
|
|
||||||
|
|
||||||
You don't need to install or struggle with Python, Anaconda, Docker etc. The installer will take care of whatever is needed.
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
# How to use?
|
# How to use?
|
||||||
Please refer to our [guide](https://github.com/cmdr2/stable-diffusion-ui/wiki/How-to-Use) to understand how to use the features in this UI.
|
Please refer to our [guide](https://github.com/easydiffusion/easydiffusion/wiki/How-to-Use) to understand how to use the features in this UI.
|
||||||
|
|
||||||
# Bugs reports and code contributions welcome
|
# 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/cmdr2/stable-diffusion-ui/issues).
|
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):
|
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)
|
* [User Interface](https://github.com/users/cmdr2/projects/1/views/1)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
@echo "Hi there, what you are running is meant for the developers of this project, not for users." & echo.
|
@echo "Hi there, what you are running is meant for the developers of this project, not for users." & echo.
|
||||||
@echo "If you only want to use the Stable Diffusion UI, you've downloaded the wrong file."
|
@echo "If you only want to use the Stable Diffusion UI, you've downloaded the wrong file."
|
||||||
@echo "Please download and follow the instructions at https://github.com/cmdr2/stable-diffusion-ui#installation" & echo.
|
@echo "Please download and follow the instructions at https://github.com/easydiffusion/easydiffusion#installation" & echo.
|
||||||
@echo "If you are actually a developer of this project, please type Y and press enter" & echo.
|
@echo "If you are actually a developer of this project, please type Y and press enter" & echo.
|
||||||
|
|
||||||
set /p answer=Are you a developer of this project (Y/N)?
|
set /p answer=Are you a developer of this project (Y/N)?
|
||||||
|
2
build.sh
2
build.sh
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
printf "Hi there, what you are running is meant for the developers of this project, not for users.\n\n"
|
printf "Hi there, what you are running is meant for the developers of this project, not for users.\n\n"
|
||||||
printf "If you only want to use the Stable Diffusion UI, you've downloaded the wrong file.\n"
|
printf "If you only want to use the Stable Diffusion UI, you've downloaded the wrong file.\n"
|
||||||
printf "Please download and follow the instructions at https://github.com/cmdr2/stable-diffusion-ui#installation\n\n"
|
printf "Please download and follow the instructions at https://github.com/easydiffusion/easydiffusion#installation \n\n"
|
||||||
printf "If you are actually a developer of this project, please type Y and press enter\n\n"
|
printf "If you are actually a developer of this project, please type Y and press enter\n\n"
|
||||||
|
|
||||||
read -p "Are you a developer of this project (Y/N) " yn
|
read -p "Are you a developer of this project (Y/N) " yn
|
||||||
|
9
package.json
Normal file
9
package.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"prettier-fix": "npx prettier --write \"./**/*.js\"",
|
||||||
|
"prettier-check": "npx prettier --check \"./**/*.js\""
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^1.19.1"
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
echo "Opening Stable Diffusion UI - Developer Console.." & echo.
|
echo "Opening Stable Diffusion UI - Developer Console.." & echo.
|
||||||
|
|
||||||
|
cd /d %~dp0
|
||||||
|
|
||||||
set PATH=C:\Windows\System32;%PATH%
|
set PATH=C:\Windows\System32;%PATH%
|
||||||
|
|
||||||
@rem set legacy and new installer's PATH, if they exist
|
@rem set legacy and new installer's PATH, if they exist
|
||||||
@ -21,6 +23,8 @@ call git --version
|
|||||||
call where conda
|
call where conda
|
||||||
call conda --version
|
call conda --version
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo COMSPEC=%COMSPEC%
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
@rem activate the legacy environment (if present) and set PYTHONPATH
|
@rem activate the legacy environment (if present) and set PYTHONPATH
|
||||||
|
@ -36,8 +36,9 @@ call git --version
|
|||||||
|
|
||||||
call where conda
|
call where conda
|
||||||
call conda --version
|
call conda --version
|
||||||
|
echo .
|
||||||
|
echo COMSPEC=%COMSPEC%
|
||||||
|
|
||||||
@rem Download the rest of the installer and UI
|
@rem Download the rest of the installer and UI
|
||||||
call scripts\on_env_start.bat
|
call scripts\on_env_start.bat
|
||||||
|
|
||||||
@pause
|
@pause
|
||||||
|
@ -11,7 +11,7 @@ setlocal enabledelayedexpansion
|
|||||||
set MAMBA_ROOT_PREFIX=%cd%\installer_files\mamba
|
set MAMBA_ROOT_PREFIX=%cd%\installer_files\mamba
|
||||||
set INSTALL_ENV_DIR=%cd%\installer_files\env
|
set INSTALL_ENV_DIR=%cd%\installer_files\env
|
||||||
set LEGACY_INSTALL_ENV_DIR=%cd%\installer
|
set LEGACY_INSTALL_ENV_DIR=%cd%\installer
|
||||||
set MICROMAMBA_DOWNLOAD_URL=https://github.com/cmdr2/stable-diffusion-ui/releases/download/v1.1/micromamba.exe
|
set MICROMAMBA_DOWNLOAD_URL=https://github.com/easydiffusion/easydiffusion/releases/download/v1.1/micromamba.exe
|
||||||
set umamba_exists=F
|
set umamba_exists=F
|
||||||
|
|
||||||
set OLD_APPDATA=%APPDATA%
|
set OLD_APPDATA=%APPDATA%
|
||||||
|
@ -1,13 +1,161 @@
|
|||||||
'''
|
"""
|
||||||
This script checks if the given modules exist
|
This script checks and installs the required modules.
|
||||||
'''
|
|
||||||
|
|
||||||
import sys
|
This script runs inside the legacy "stable-diffusion" folder
|
||||||
import pkgutil
|
|
||||||
|
|
||||||
modules = sys.argv[1:]
|
TODO - Maybe replace the bulk of this script with a call to `pip install -f requirements.txt`, with
|
||||||
missing_modules = []
|
a custom index URL depending on the platform.
|
||||||
for m in modules:
|
|
||||||
if pkgutil.find_loader(m) is None:
|
"""
|
||||||
print('module', m, 'not found')
|
|
||||||
|
import os
|
||||||
|
from importlib.metadata import version as pkg_version
|
||||||
|
import platform
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
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.112",
|
||||||
|
"stable-diffusion-sdkit": "2.1.4",
|
||||||
|
"rich": "12.6.0",
|
||||||
|
"uvicorn": "0.19.0",
|
||||||
|
"fastapi": "0.85.1",
|
||||||
|
"pycloudflared": "0.2.0",
|
||||||
|
# "xformers": "0.0.16",
|
||||||
|
}
|
||||||
|
modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"]
|
||||||
|
|
||||||
|
|
||||||
|
def version(module_name: str) -> str:
|
||||||
|
try:
|
||||||
|
return pkg_version(module_name)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def install(module_name: str, module_version: str):
|
||||||
|
if module_name == "xformers" and (os_name == "Darwin" or is_amd_on_linux()):
|
||||||
|
return
|
||||||
|
|
||||||
|
index_url = None
|
||||||
|
if module_name in ("torch", "torchvision"):
|
||||||
|
module_version, index_url = apply_torch_install_overrides(module_version)
|
||||||
|
|
||||||
|
if is_amd_on_linux(): # hack until AMD works properly on torch 2.0 (avoids black images on some cards)
|
||||||
|
if module_name == "torch":
|
||||||
|
module_version = "1.13.1+rocm5.2"
|
||||||
|
elif module_name == "torchvision":
|
||||||
|
module_version = "0.14.1+rocm5.2"
|
||||||
|
elif os_name == "Darwin":
|
||||||
|
if module_name == "torch":
|
||||||
|
module_version = "1.13.1"
|
||||||
|
elif module_name == "torchvision":
|
||||||
|
module_version = "0.14.1"
|
||||||
|
|
||||||
|
install_cmd = f"python -m pip install --upgrade {module_name}=={module_version}"
|
||||||
|
if index_url:
|
||||||
|
install_cmd += f" --index-url {index_url}"
|
||||||
|
if module_name == "sdkit" and version("sdkit") is not None:
|
||||||
|
install_cmd += " -q"
|
||||||
|
|
||||||
|
print(">", install_cmd)
|
||||||
|
os.system(install_cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
for module_name, allowed_versions in modules_to_check.items():
|
||||||
|
if os.path.exists(f"../src/{module_name}"):
|
||||||
|
print(f"Skipping {module_name} update, since it's in developer/editable mode")
|
||||||
|
continue
|
||||||
|
|
||||||
|
allowed_versions, latest_version = get_allowed_versions(module_name, allowed_versions)
|
||||||
|
|
||||||
|
requires_install = False
|
||||||
|
if module_name in ("torch", "torchvision"):
|
||||||
|
if version(module_name) is None: # allow any torch version
|
||||||
|
requires_install = True
|
||||||
|
elif os_name == "Darwin" and ( # force mac to downgrade from torch 2.0
|
||||||
|
version("torch").startswith("2.") or version("torchvision").startswith("0.15.")
|
||||||
|
):
|
||||||
|
requires_install = True
|
||||||
|
elif version(module_name) not in allowed_versions:
|
||||||
|
requires_install = True
|
||||||
|
|
||||||
|
if requires_install:
|
||||||
|
try:
|
||||||
|
install(module_name, latest_version)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
fail(module_name)
|
||||||
|
|
||||||
|
if module_name in modules_to_log:
|
||||||
|
print(f"{module_name}: {version(module_name)}")
|
||||||
|
|
||||||
|
|
||||||
|
### utilities
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_versions(module_name: str, allowed_versions: tuple):
|
||||||
|
allowed_versions = (allowed_versions,) if isinstance(allowed_versions, str) else allowed_versions
|
||||||
|
latest_version = allowed_versions[-1]
|
||||||
|
|
||||||
|
if module_name in ("torch", "torchvision"):
|
||||||
|
allowed_versions = include_cuda_versions(allowed_versions)
|
||||||
|
|
||||||
|
return allowed_versions, latest_version
|
||||||
|
|
||||||
|
|
||||||
|
def apply_torch_install_overrides(module_version: str):
|
||||||
|
index_url = None
|
||||||
|
if os_name == "Windows":
|
||||||
|
module_version += "+cu117"
|
||||||
|
index_url = "https://download.pytorch.org/whl/cu117"
|
||||||
|
elif is_amd_on_linux():
|
||||||
|
index_url = "https://download.pytorch.org/whl/rocm5.2"
|
||||||
|
|
||||||
|
return module_version, index_url
|
||||||
|
|
||||||
|
|
||||||
|
def include_cuda_versions(module_versions: tuple) -> tuple:
|
||||||
|
"Adds CUDA-specific versions to the list of allowed version numbers"
|
||||||
|
|
||||||
|
allowed_versions = tuple(module_versions)
|
||||||
|
allowed_versions += tuple(f"{v}+cu116" for v in module_versions)
|
||||||
|
allowed_versions += tuple(f"{v}+cu117" for v in module_versions)
|
||||||
|
allowed_versions += tuple(f"{v}+rocm5.2" for v in module_versions)
|
||||||
|
allowed_versions += tuple(f"{v}+rocm5.4.2" for v in module_versions)
|
||||||
|
|
||||||
|
return allowed_versions
|
||||||
|
|
||||||
|
|
||||||
|
def is_amd_on_linux():
|
||||||
|
if os_name == "Linux":
|
||||||
|
try:
|
||||||
|
with open("/proc/bus/pci/devices", "r") as f:
|
||||||
|
device_info = f.read()
|
||||||
|
if "amdgpu" in device_info and "nvidia" not in device_info:
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def fail(module_name):
|
||||||
|
print(
|
||||||
|
f"""Error installing {module_name}. Sorry about that, please try to:
|
||||||
|
1. Run this installer again.
|
||||||
|
2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting
|
||||||
|
3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB
|
||||||
|
4. If that doesn't solve the problem, please file an issue at https://github.com/easydiffusion/easydiffusion/issues
|
||||||
|
Thanks!"""
|
||||||
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
### start
|
||||||
|
|
||||||
|
init()
|
||||||
|
@ -39,6 +39,8 @@ if [ "$0" == "bash" ]; then
|
|||||||
export PYTHONPATH="$(pwd)/stable-diffusion/env/lib/python3.8/site-packages"
|
export PYTHONPATH="$(pwd)/stable-diffusion/env/lib/python3.8/site-packages"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export PYTHONNOUSERSITE=y
|
||||||
|
|
||||||
which python
|
which python
|
||||||
python --version
|
python --version
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ fail() {
|
|||||||
|
|
||||||
Error downloading Stable Diffusion UI. Sorry about that, please try to:
|
Error downloading Stable Diffusion UI. Sorry about that, please try to:
|
||||||
1. Run this installer again.
|
1. Run this installer again.
|
||||||
2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting
|
2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting
|
||||||
3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB
|
3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB
|
||||||
4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues
|
4. If that doesn't solve the problem, please file an issue at https://github.com/easydiffusion/easydiffusion/issues
|
||||||
|
|
||||||
Thanks!
|
Thanks!
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ EOF
|
|||||||
filesize() {
|
filesize() {
|
||||||
case "$(uname -s)" in
|
case "$(uname -s)" in
|
||||||
Linux*) stat -c "%s" $1;;
|
Linux*) stat -c "%s" $1;;
|
||||||
Darwin*) stat -f "%z" $1;;
|
Darwin*) /usr/bin/stat -f "%z" $1;;
|
||||||
*) echo "Unknown OS: $OS_NAME! This script runs only on Linux or Mac" && exit
|
*) echo "Unknown OS: $OS_NAME! This script runs only on Linux or Mac" && exit
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
46
scripts/get_config.py
Normal file
46
scripts/get_config.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# The config file is in the same directory as this script
|
||||||
|
config_directory = os.path.dirname(__file__)
|
||||||
|
config_yaml = os.path.join(config_directory, "config.yaml")
|
||||||
|
config_json = os.path.join(config_directory, "config.json")
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Get values from config file')
|
||||||
|
parser.add_argument('--default', dest='default', action='store',
|
||||||
|
help='default value, to be used if the setting is not defined in the config file')
|
||||||
|
parser.add_argument('key', metavar='key', nargs='+',
|
||||||
|
help='config key to return')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.isfile(config_yaml):
|
||||||
|
import yaml
|
||||||
|
with open(config_yaml, 'r') as configfile:
|
||||||
|
try:
|
||||||
|
config = yaml.safe_load(configfile)
|
||||||
|
except Exception as e:
|
||||||
|
print(e, file=sys.stderr)
|
||||||
|
config = {}
|
||||||
|
elif os.path.isfile(config_json):
|
||||||
|
import json
|
||||||
|
with open(config_json, 'r') as configfile:
|
||||||
|
try:
|
||||||
|
config = json.load(configfile)
|
||||||
|
except Exception as e:
|
||||||
|
print(e, file=sys.stderr)
|
||||||
|
config = {}
|
||||||
|
else:
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
for k in args.key:
|
||||||
|
if k in config:
|
||||||
|
config = config[k]
|
||||||
|
else:
|
||||||
|
if args.default != None:
|
||||||
|
print(args.default)
|
||||||
|
exit()
|
||||||
|
|
||||||
|
print(config)
|
@ -8,6 +8,20 @@ if exist "scripts\config.bat" (
|
|||||||
@call scripts\config.bat
|
@call scripts\config.bat
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if exist "scripts\user_config.bat" (
|
||||||
|
@call scripts\user_config.bat
|
||||||
|
)
|
||||||
|
|
||||||
|
if exist "stable-diffusion\env" (
|
||||||
|
@set PYTHONPATH=%PYTHONPATH%;%cd%\stable-diffusion\env\lib\site-packages
|
||||||
|
)
|
||||||
|
|
||||||
|
if exist "scripts\get_config.py" (
|
||||||
|
@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=main update_branch`) DO (
|
||||||
|
@SET update_branch=%%F
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if "%update_branch%"=="" (
|
if "%update_branch%"=="" (
|
||||||
set update_branch=main
|
set update_branch=main
|
||||||
)
|
)
|
||||||
@ -41,10 +55,10 @@ if "%update_branch%"=="" (
|
|||||||
@echo. & echo "Downloading Easy Diffusion..." & echo.
|
@echo. & echo "Downloading Easy Diffusion..." & echo.
|
||||||
@echo "Using the %update_branch% channel" & echo.
|
@echo "Using the %update_branch% channel" & echo.
|
||||||
|
|
||||||
@call git clone -b "%update_branch%" https://github.com/cmdr2/stable-diffusion-ui.git sd-ui-files && (
|
@call git clone -b "%update_branch%" https://github.com/easydiffusion/easydiffusion.git sd-ui-files && (
|
||||||
@echo sd_ui_git_cloned >> scripts\install_status.txt
|
@echo sd_ui_git_cloned >> scripts\install_status.txt
|
||||||
) || (
|
) || (
|
||||||
@echo "Error downloading Easy Diffusion. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
|
@echo "Error downloading Easy Diffusion. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/easydiffusion/easydiffusion/issues" & echo "Thanks!"
|
||||||
pause
|
pause
|
||||||
@exit /b
|
@exit /b
|
||||||
)
|
)
|
||||||
@ -53,6 +67,7 @@ if "%update_branch%"=="" (
|
|||||||
@xcopy sd-ui-files\ui ui /s /i /Y /q
|
@xcopy sd-ui-files\ui ui /s /i /Y /q
|
||||||
@copy sd-ui-files\scripts\on_sd_start.bat scripts\ /Y
|
@copy sd-ui-files\scripts\on_sd_start.bat scripts\ /Y
|
||||||
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
||||||
|
@copy sd-ui-files\scripts\get_config.py scripts\ /Y
|
||||||
@copy "sd-ui-files\scripts\Start Stable Diffusion UI.cmd" . /Y
|
@copy "sd-ui-files\scripts\Start Stable Diffusion UI.cmd" . /Y
|
||||||
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y
|
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y
|
||||||
|
|
||||||
|
@ -4,10 +4,22 @@ source ./scripts/functions.sh
|
|||||||
|
|
||||||
printf "\n\nEasy Diffusion\n\n"
|
printf "\n\nEasy Diffusion\n\n"
|
||||||
|
|
||||||
|
export PYTHONNOUSERSITE=y
|
||||||
|
|
||||||
if [ -f "scripts/config.sh" ]; then
|
if [ -f "scripts/config.sh" ]; then
|
||||||
source scripts/config.sh
|
source scripts/config.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "scripts/user_config.sh" ]; then
|
||||||
|
source scripts/user_config.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PYTHONPATH=$(pwd)/installer_files/env/lib/python3.8/site-packages:$(pwd)/stable-diffusion/env/lib/python3.8/site-packages
|
||||||
|
|
||||||
|
if [ -f "scripts/get_config.py" ]; then
|
||||||
|
export update_branch="$( python scripts/get_config.py --default=main update_branch )"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$update_branch" == "" ]; then
|
if [ "$update_branch" == "" ]; then
|
||||||
export update_branch="main"
|
export update_branch="main"
|
||||||
fi
|
fi
|
||||||
@ -26,7 +38,7 @@ else
|
|||||||
printf "\n\nDownloading Easy Diffusion..\n\n"
|
printf "\n\nDownloading Easy Diffusion..\n\n"
|
||||||
printf "Using the $update_branch channel\n\n"
|
printf "Using the $update_branch channel\n\n"
|
||||||
|
|
||||||
if git clone -b "$update_branch" https://github.com/cmdr2/stable-diffusion-ui.git sd-ui-files ; then
|
if git clone -b "$update_branch" https://github.com/easydiffusion/easydiffusion.git sd-ui-files ; then
|
||||||
echo sd_ui_git_cloned >> scripts/install_status.txt
|
echo sd_ui_git_cloned >> scripts/install_status.txt
|
||||||
else
|
else
|
||||||
fail "git clone failed"
|
fail "git clone failed"
|
||||||
@ -38,6 +50,7 @@ cp -Rf sd-ui-files/ui .
|
|||||||
cp sd-ui-files/scripts/on_sd_start.sh scripts/
|
cp sd-ui-files/scripts/on_sd_start.sh scripts/
|
||||||
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
||||||
cp sd-ui-files/scripts/check_modules.py scripts/
|
cp sd-ui-files/scripts/check_modules.py scripts/
|
||||||
|
cp sd-ui-files/scripts/get_config.py scripts/
|
||||||
cp sd-ui-files/scripts/start.sh .
|
cp sd-ui-files/scripts/start.sh .
|
||||||
cp sd-ui-files/scripts/developer_console.sh .
|
cp sd-ui-files/scripts/developer_console.sh .
|
||||||
cp sd-ui-files/scripts/functions.sh scripts/
|
cp sd-ui-files/scripts/functions.sh scripts/
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
@REM Note to self: Please rewrite this in Python. For the sake of your own sanity.
|
@REM Note to self: Please rewrite this in Python. For the sake of your own sanity.
|
||||||
|
|
||||||
@copy sd-ui-files\scripts\on_env_start.bat scripts\ /Y
|
@copy sd-ui-files\scripts\on_env_start.bat scripts\ /Y
|
||||||
@copy sd-ui-files\scripts\bootstrap.bat scripts\ /Y
|
|
||||||
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
||||||
|
@copy sd-ui-files\scripts\get_config.py scripts\ /Y
|
||||||
|
|
||||||
if exist "%cd%\profile" (
|
if exist "%cd%\profile" (
|
||||||
set USERPROFILE=%cd%\profile
|
set HF_HOME=%cd%\profile\.cache\huggingface
|
||||||
)
|
)
|
||||||
|
|
||||||
@rem set the correct installer path (current vs legacy)
|
@rem set the correct installer path (current vs legacy)
|
||||||
@ -26,7 +26,7 @@ if exist "%cd%\stable-diffusion\env" (
|
|||||||
@rem activate the installer env
|
@rem activate the installer env
|
||||||
call conda activate
|
call conda activate
|
||||||
@if "%ERRORLEVEL%" NEQ "0" (
|
@if "%ERRORLEVEL%" NEQ "0" (
|
||||||
@echo. & echo "Error activating conda for Easy Diffusion. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
@echo. & echo "Error activating conda for Easy Diffusion. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/easydiffusion/easydiffusion/issues" & echo "Thanks!" & echo.
|
||||||
pause
|
pause
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
@ -34,8 +34,6 @@ call conda activate
|
|||||||
@REM remove the old version of the dev console script, if it's still present
|
@REM remove the old version of the dev console script, if it's still present
|
||||||
if exist "Open Developer Console.cmd" del "Open Developer Console.cmd"
|
if exist "Open Developer Console.cmd" del "Open Developer Console.cmd"
|
||||||
|
|
||||||
@call python -c "import os; import shutil; frm = 'sd-ui-files\\ui\\hotfix\\9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'; dst = os.path.join(os.path.expanduser('~'), '.cache', 'huggingface', 'transformers', '9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'); shutil.copyfile(frm, dst) if os.path.exists(dst) else print(''); print('Hotfixed broken JSON file from OpenAI');"
|
|
||||||
|
|
||||||
@rem create the stable-diffusion folder, to work with legacy installations
|
@rem create the stable-diffusion folder, to work with legacy installations
|
||||||
if not exist "stable-diffusion" mkdir stable-diffusion
|
if not exist "stable-diffusion" mkdir stable-diffusion
|
||||||
cd stable-diffusion
|
cd stable-diffusion
|
||||||
@ -49,134 +47,28 @@ if exist "env" (
|
|||||||
if exist src rename src src-old
|
if exist src rename src src-old
|
||||||
if exist ldm rename ldm ldm-old
|
if exist ldm rename ldm ldm-old
|
||||||
|
|
||||||
if not exist "..\models\stable-diffusion" mkdir "..\models\stable-diffusion"
|
|
||||||
if not exist "..\models\gfpgan" mkdir "..\models\gfpgan"
|
|
||||||
if not exist "..\models\realesrgan" mkdir "..\models\realesrgan"
|
|
||||||
if not exist "..\models\vae" mkdir "..\models\vae"
|
|
||||||
|
|
||||||
@rem migrate the legacy models to the correct path (if already downloaded)
|
|
||||||
if exist "sd-v1-4.ckpt" move sd-v1-4.ckpt ..\models\stable-diffusion\
|
|
||||||
if exist "custom-model.ckpt" move custom-model.ckpt ..\models\stable-diffusion\
|
|
||||||
if exist "GFPGANv1.3.pth" move GFPGANv1.3.pth ..\models\gfpgan\
|
|
||||||
if exist "RealESRGAN_x4plus.pth" move RealESRGAN_x4plus.pth ..\models\realesrgan\
|
|
||||||
if exist "RealESRGAN_x4plus_anime_6B.pth" move RealESRGAN_x4plus_anime_6B.pth ..\models\realesrgan\
|
|
||||||
|
|
||||||
if not exist "%INSTALL_ENV_DIR%\DLLs\libssl-1_1-x64.dll" copy "%INSTALL_ENV_DIR%\Library\bin\libssl-1_1-x64.dll" "%INSTALL_ENV_DIR%\DLLs\"
|
if not exist "%INSTALL_ENV_DIR%\DLLs\libssl-1_1-x64.dll" copy "%INSTALL_ENV_DIR%\Library\bin\libssl-1_1-x64.dll" "%INSTALL_ENV_DIR%\DLLs\"
|
||||||
if not exist "%INSTALL_ENV_DIR%\DLLs\libcrypto-1_1-x64.dll" copy "%INSTALL_ENV_DIR%\Library\bin\libcrypto-1_1-x64.dll" "%INSTALL_ENV_DIR%\DLLs\"
|
if not exist "%INSTALL_ENV_DIR%\DLLs\libcrypto-1_1-x64.dll" copy "%INSTALL_ENV_DIR%\Library\bin\libcrypto-1_1-x64.dll" "%INSTALL_ENV_DIR%\DLLs\"
|
||||||
|
|
||||||
@rem install torch and torchvision
|
@rem install or upgrade the required modules
|
||||||
call python ..\scripts\check_modules.py torch torchvision
|
|
||||||
if "%ERRORLEVEL%" EQU "0" (
|
|
||||||
echo "torch and torchvision have already been installed."
|
|
||||||
) else (
|
|
||||||
echo "Installing torch and torchvision.."
|
|
||||||
|
|
||||||
@REM prevent from using packages from the user's home directory, to avoid conflicts
|
|
||||||
set PYTHONNOUSERSITE=1
|
|
||||||
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
|
|
||||||
|
|
||||||
call python -m pip install --upgrade torch==1.13.1+cu116 torchvision==0.14.1+cu116 --extra-index-url https://download.pytorch.org/whl/cu116 || (
|
|
||||||
echo "Error installing torch. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
set PATH=C:\Windows\System32;%PATH%
|
set PATH=C:\Windows\System32;%PATH%
|
||||||
|
|
||||||
@rem install/upgrade sdkit
|
@REM prevent from using packages from the user's home directory, to avoid conflicts
|
||||||
call python ..\scripts\check_modules.py sdkit sdkit.models ldm transformers numpy antlr4 gfpgan realesrgan
|
set PYTHONNOUSERSITE=1
|
||||||
if "%ERRORLEVEL%" EQU "0" (
|
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
|
||||||
echo "sdkit is already installed."
|
|
||||||
|
|
||||||
@rem skip sdkit upgrade if in developer-mode
|
@rem Download the required packages
|
||||||
if not exist "..\src\sdkit" (
|
call python ..\scripts\check_modules.py
|
||||||
@REM prevent from using packages from the user's home directory, to avoid conflicts
|
if "%ERRORLEVEL%" NEQ "0" (
|
||||||
set PYTHONNOUSERSITE=1
|
|
||||||
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
|
|
||||||
|
|
||||||
call python -m pip install --upgrade sdkit==1.0.60 -q || (
|
|
||||||
echo "Error updating sdkit"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo "Installing sdkit: https://pypi.org/project/sdkit/"
|
|
||||||
|
|
||||||
@REM prevent from using packages from the user's home directory, to avoid conflicts
|
|
||||||
set PYTHONNOUSERSITE=1
|
|
||||||
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
|
|
||||||
|
|
||||||
call python -m pip install sdkit==1.0.60 || (
|
|
||||||
echo "Error installing sdkit. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
|
|
||||||
pause
|
pause
|
||||||
exit /b
|
exit /b
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
call python -c "from importlib.metadata import version; print('sdkit version:', version('sdkit'))"
|
|
||||||
|
|
||||||
@rem upgrade stable-diffusion-sdkit
|
|
||||||
call python -m pip install --upgrade stable-diffusion-sdkit==2.1.4 -q || (
|
|
||||||
echo "Error updating stable-diffusion-sdkit"
|
|
||||||
)
|
|
||||||
call python -c "from importlib.metadata import version; print('stable-diffusion version:', version('stable-diffusion-sdkit'))"
|
|
||||||
|
|
||||||
@rem install rich
|
|
||||||
call python ..\scripts\check_modules.py rich
|
|
||||||
if "%ERRORLEVEL%" EQU "0" (
|
|
||||||
echo "rich has already been installed."
|
|
||||||
) else (
|
|
||||||
echo "Installing rich.."
|
|
||||||
|
|
||||||
set PYTHONNOUSERSITE=1
|
|
||||||
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
|
|
||||||
|
|
||||||
call python -m pip install rich || (
|
|
||||||
echo "Error installing rich. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@rem install ruamel.yaml
|
|
||||||
call python ..\scripts\check_modules.py ruamel.yaml
|
|
||||||
if "%ERRORLEVEL%" EQU "0" (
|
|
||||||
echo "ruamel.yaml has already been installed."
|
|
||||||
) else (
|
|
||||||
echo "Installing ruamel.yaml.."
|
|
||||||
|
|
||||||
set PYTHONNOUSERSITE=1
|
|
||||||
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
|
|
||||||
|
|
||||||
call python -m pip install ruamel.yaml==0.17.21 || (
|
|
||||||
echo "Error installing ruamel.yaml. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
set PATH=C:\Windows\System32;%PATH%
|
|
||||||
|
|
||||||
call python ..\scripts\check_modules.py uvicorn fastapi
|
|
||||||
@if "%ERRORLEVEL%" EQU "0" (
|
|
||||||
echo "Packages necessary for Easy Diffusion were already installed"
|
|
||||||
) else (
|
|
||||||
@echo. & echo "Downloading packages necessary for Easy Diffusion..." & echo.
|
|
||||||
|
|
||||||
set PYTHONNOUSERSITE=1
|
|
||||||
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
|
|
||||||
|
|
||||||
@call conda install -c conda-forge -y uvicorn fastapi || (
|
|
||||||
echo "Error installing the packages necessary for Easy Diffusion. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
call WHERE uvicorn > .tmp
|
call WHERE uvicorn > .tmp
|
||||||
@>nul findstr /m "uvicorn" .tmp
|
@>nul findstr /m "uvicorn" .tmp
|
||||||
@if "%ERRORLEVEL%" NEQ "0" (
|
@if "%ERRORLEVEL%" NEQ "0" (
|
||||||
@echo. & echo "UI packages not found! Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
@echo. & echo "UI packages not found! Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/easydiffusion/easydiffusion/issues" & echo "Thanks!" & echo.
|
||||||
pause
|
pause
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
@ -186,162 +78,6 @@ call WHERE uvicorn > .tmp
|
|||||||
@echo conda_sd_ui_deps_installed >> ..\scripts\install_status.txt
|
@echo conda_sd_ui_deps_installed >> ..\scripts\install_status.txt
|
||||||
)
|
)
|
||||||
|
|
||||||
@if exist "..\models\stable-diffusion\sd-v1-4.ckpt" (
|
|
||||||
for %%I in ("..\models\stable-diffusion\sd-v1-4.ckpt") do if "%%~zI" EQU "4265380512" (
|
|
||||||
echo "Data files (weights) necessary for Stable Diffusion were already downloaded. Using the HuggingFace 4 GB Model."
|
|
||||||
) else (
|
|
||||||
for %%J in ("..\models\stable-diffusion\sd-v1-4.ckpt") do if "%%~zJ" EQU "7703807346" (
|
|
||||||
echo "Data files (weights) necessary for Stable Diffusion were already downloaded. Using the HuggingFace 7 GB Model."
|
|
||||||
) else (
|
|
||||||
for %%K in ("..\models\stable-diffusion\sd-v1-4.ckpt") do if "%%~zK" EQU "7703810927" (
|
|
||||||
echo "Data files (weights) necessary for Stable Diffusion were already downloaded. Using the Waifu Model."
|
|
||||||
) else (
|
|
||||||
echo. & echo "The model file present at models\stable-diffusion\sd-v1-4.ckpt is invalid. It is only %%~zK bytes in size. Re-downloading.." & echo.
|
|
||||||
del "..\models\stable-diffusion\sd-v1-4.ckpt"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@if not exist "..\models\stable-diffusion\sd-v1-4.ckpt" (
|
|
||||||
@echo. & echo "Downloading data files (weights) for Stable Diffusion.." & echo.
|
|
||||||
|
|
||||||
@call curl -L -k https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt > ..\models\stable-diffusion\sd-v1-4.ckpt
|
|
||||||
|
|
||||||
@if exist "..\models\stable-diffusion\sd-v1-4.ckpt" (
|
|
||||||
for %%I in ("..\models\stable-diffusion\sd-v1-4.ckpt") do if "%%~zI" NEQ "4265380512" (
|
|
||||||
echo. & echo "Error: The downloaded model file was invalid! Bytes downloaded: %%~zI" & echo.
|
|
||||||
echo. & echo "Error downloading the data files (weights) for Stable Diffusion. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
@echo. & echo "Error downloading the data files (weights) for Stable Diffusion. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@if exist "..\models\gfpgan\GFPGANv1.3.pth" (
|
|
||||||
for %%I in ("..\models\gfpgan\GFPGANv1.3.pth") do if "%%~zI" EQU "348632874" (
|
|
||||||
echo "Data files (weights) necessary for GFPGAN (Face Correction) were already downloaded"
|
|
||||||
) else (
|
|
||||||
echo. & echo "The GFPGAN model file present at models\gfpgan\GFPGANv1.3.pth is invalid. It is only %%~zI bytes in size. Re-downloading.." & echo.
|
|
||||||
del "..\models\gfpgan\GFPGANv1.3.pth"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@if not exist "..\models\gfpgan\GFPGANv1.3.pth" (
|
|
||||||
@echo. & echo "Downloading data files (weights) for GFPGAN (Face Correction).." & echo.
|
|
||||||
|
|
||||||
@call curl -L -k https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth > ..\models\gfpgan\GFPGANv1.3.pth
|
|
||||||
|
|
||||||
@if exist "..\models\gfpgan\GFPGANv1.3.pth" (
|
|
||||||
for %%I in ("..\models\gfpgan\GFPGANv1.3.pth") do if "%%~zI" NEQ "348632874" (
|
|
||||||
echo. & echo "Error: The downloaded GFPGAN model file was invalid! Bytes downloaded: %%~zI" & echo.
|
|
||||||
echo. & echo "Error downloading the data files (weights) for GFPGAN (Face Correction). Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
@echo. & echo "Error downloading the data files (weights) for GFPGAN (Face Correction). Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@if exist "..\models\realesrgan\RealESRGAN_x4plus.pth" (
|
|
||||||
for %%I in ("..\models\realesrgan\RealESRGAN_x4plus.pth") do if "%%~zI" EQU "67040989" (
|
|
||||||
echo "Data files (weights) necessary for ESRGAN (Resolution Upscaling) x4plus were already downloaded"
|
|
||||||
) else (
|
|
||||||
echo. & echo "The RealESRGAN model file present at models\realesrgan\RealESRGAN_x4plus.pth is invalid. It is only %%~zI bytes in size. Re-downloading.." & echo.
|
|
||||||
del "..\models\realesrgan\RealESRGAN_x4plus.pth"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@if not exist "..\models\realesrgan\RealESRGAN_x4plus.pth" (
|
|
||||||
@echo. & echo "Downloading data files (weights) for ESRGAN (Resolution Upscaling) x4plus.." & echo.
|
|
||||||
|
|
||||||
@call curl -L -k https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth > ..\models\realesrgan\RealESRGAN_x4plus.pth
|
|
||||||
|
|
||||||
@if exist "..\models\realesrgan\RealESRGAN_x4plus.pth" (
|
|
||||||
for %%I in ("..\models\realesrgan\RealESRGAN_x4plus.pth") do if "%%~zI" NEQ "67040989" (
|
|
||||||
echo. & echo "Error: The downloaded ESRGAN x4plus model file was invalid! Bytes downloaded: %%~zI" & echo.
|
|
||||||
echo. & echo "Error downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
@echo. & echo "Error downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@if exist "..\models\realesrgan\RealESRGAN_x4plus_anime_6B.pth" (
|
|
||||||
for %%I in ("..\models\realesrgan\RealESRGAN_x4plus_anime_6B.pth") do if "%%~zI" EQU "17938799" (
|
|
||||||
echo "Data files (weights) necessary for ESRGAN (Resolution Upscaling) x4plus_anime were already downloaded"
|
|
||||||
) else (
|
|
||||||
echo. & echo "The RealESRGAN model file present at models\realesrgan\RealESRGAN_x4plus_anime_6B.pth is invalid. It is only %%~zI bytes in size. Re-downloading.." & echo.
|
|
||||||
del "..\models\realesrgan\RealESRGAN_x4plus_anime_6B.pth"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@if not exist "..\models\realesrgan\RealESRGAN_x4plus_anime_6B.pth" (
|
|
||||||
@echo. & echo "Downloading data files (weights) for ESRGAN (Resolution Upscaling) x4plus_anime.." & echo.
|
|
||||||
|
|
||||||
@call curl -L -k https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth > ..\models\realesrgan\RealESRGAN_x4plus_anime_6B.pth
|
|
||||||
|
|
||||||
@if exist "..\models\realesrgan\RealESRGAN_x4plus_anime_6B.pth" (
|
|
||||||
for %%I in ("..\models\realesrgan\RealESRGAN_x4plus_anime_6B.pth") do if "%%~zI" NEQ "17938799" (
|
|
||||||
echo. & echo "Error: The downloaded ESRGAN x4plus_anime model file was invalid! Bytes downloaded: %%~zI" & echo.
|
|
||||||
echo. & echo "Error downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus_anime. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
@echo. & echo "Error downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus_anime. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@if exist "..\models\vae\vae-ft-mse-840000-ema-pruned.ckpt" (
|
|
||||||
for %%I in ("..\models\vae\vae-ft-mse-840000-ema-pruned.ckpt") do if "%%~zI" EQU "334695179" (
|
|
||||||
echo "Data files (weights) necessary for the default VAE (sd-vae-ft-mse-original) were already downloaded"
|
|
||||||
) else (
|
|
||||||
echo. & echo "The default VAE (sd-vae-ft-mse-original) file present at models\vae\vae-ft-mse-840000-ema-pruned.ckpt is invalid. It is only %%~zI bytes in size. Re-downloading.." & echo.
|
|
||||||
del "..\models\vae\vae-ft-mse-840000-ema-pruned.ckpt"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@if not exist "..\models\vae\vae-ft-mse-840000-ema-pruned.ckpt" (
|
|
||||||
@echo. & echo "Downloading data files (weights) for the default VAE (sd-vae-ft-mse-original).." & echo.
|
|
||||||
|
|
||||||
@call curl -L -k https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.ckpt > ..\models\vae\vae-ft-mse-840000-ema-pruned.ckpt
|
|
||||||
|
|
||||||
@if exist "..\models\vae\vae-ft-mse-840000-ema-pruned.ckpt" (
|
|
||||||
for %%I in ("..\models\vae\vae-ft-mse-840000-ema-pruned.ckpt") do if "%%~zI" NEQ "334695179" (
|
|
||||||
echo. & echo "Error: The downloaded default VAE (sd-vae-ft-mse-original) file was invalid! Bytes downloaded: %%~zI" & echo.
|
|
||||||
echo. & echo "Error downloading the data files (weights) for the default VAE (sd-vae-ft-mse-original). Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
@echo. & echo "Error downloading the data files (weights) for the default VAE (sd-vae-ft-mse-original). Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" & echo.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@>nul findstr /m "sd_install_complete" ..\scripts\install_status.txt
|
@>nul findstr /m "sd_install_complete" ..\scripts\install_status.txt
|
||||||
@if "%ERRORLEVEL%" NEQ "0" (
|
@if "%ERRORLEVEL%" NEQ "0" (
|
||||||
@echo sd_weights_downloaded >> ..\scripts\install_status.txt
|
@echo sd_weights_downloaded >> ..\scripts\install_status.txt
|
||||||
@ -360,14 +96,25 @@ call python --version
|
|||||||
|
|
||||||
@cd ..
|
@cd ..
|
||||||
@set SD_UI_PATH=%cd%\ui
|
@set SD_UI_PATH=%cd%\ui
|
||||||
|
|
||||||
|
@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=9000 net listen_port`) DO (
|
||||||
|
@SET ED_BIND_PORT=%%F
|
||||||
|
)
|
||||||
|
|
||||||
|
@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=False net listen_to_network`) DO (
|
||||||
|
if "%%F" EQU "True" (
|
||||||
|
@SET ED_BIND_IP=0.0.0.0
|
||||||
|
) else (
|
||||||
|
@SET ED_BIND_IP=127.0.0.1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@cd stable-diffusion
|
@cd stable-diffusion
|
||||||
|
|
||||||
@rem set any overrides
|
@rem set any overrides
|
||||||
set HF_HUB_DISABLE_SYMLINKS_WARNING=true
|
set HF_HUB_DISABLE_SYMLINKS_WARNING=true
|
||||||
|
|
||||||
@if NOT DEFINED SD_UI_BIND_PORT set SD_UI_BIND_PORT=9000
|
@uvicorn main:server_api --app-dir "%SD_UI_PATH%" --port %ED_BIND_PORT% --host %ED_BIND_IP% --log-level error
|
||||||
@if NOT DEFINED SD_UI_BIND_IP set SD_UI_BIND_IP=0.0.0.0
|
|
||||||
@uvicorn main:server_api --app-dir "%SD_UI_PATH%" --port %SD_UI_BIND_PORT% --host %SD_UI_BIND_IP% --log-level error
|
|
||||||
|
|
||||||
|
|
||||||
@pause
|
@pause
|
||||||
|
@ -4,6 +4,7 @@ cp sd-ui-files/scripts/functions.sh scripts/
|
|||||||
cp sd-ui-files/scripts/on_env_start.sh scripts/
|
cp sd-ui-files/scripts/on_env_start.sh scripts/
|
||||||
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
||||||
cp sd-ui-files/scripts/check_modules.py scripts/
|
cp sd-ui-files/scripts/check_modules.py scripts/
|
||||||
|
cp sd-ui-files/scripts/get_config.py scripts/
|
||||||
|
|
||||||
source ./scripts/functions.sh
|
source ./scripts/functions.sh
|
||||||
|
|
||||||
@ -18,11 +19,6 @@ if [ -e "open_dev_console.sh" ]; then
|
|||||||
rm "open_dev_console.sh"
|
rm "open_dev_console.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python -c "import os; import shutil; frm = 'sd-ui-files/ui/hotfix/9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'; dst = os.path.join(os.path.expanduser('~'), '.cache', 'huggingface', 'transformers', '9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'); shutil.copyfile(frm, dst) if os.path.exists(dst) else print(''); print('Hotfixed broken JSON file from OpenAI');"
|
|
||||||
|
|
||||||
# Caution, this file will make your eyes and brain bleed. It's such an unholy mess.
|
|
||||||
# Note to self: Please rewrite this in Python. For the sake of your own sanity.
|
|
||||||
|
|
||||||
# set the correct installer path (current vs legacy)
|
# set the correct installer path (current vs legacy)
|
||||||
if [ -e "installer_files/env" ]; then
|
if [ -e "installer_files/env" ]; then
|
||||||
export INSTALL_ENV_DIR="$(pwd)/installer_files/env"
|
export INSTALL_ENV_DIR="$(pwd)/installer_files/env"
|
||||||
@ -44,274 +40,14 @@ fi
|
|||||||
if [ -e "src" ]; then mv src src-old; fi
|
if [ -e "src" ]; then mv src src-old; fi
|
||||||
if [ -e "ldm" ]; then mv ldm ldm-old; fi
|
if [ -e "ldm" ]; then mv ldm ldm-old; fi
|
||||||
|
|
||||||
mkdir -p "../models/stable-diffusion"
|
# Download the required packages
|
||||||
mkdir -p "../models/gfpgan"
|
if ! python ../scripts/check_modules.py; then
|
||||||
mkdir -p "../models/realesrgan"
|
read -p "Press any key to continue"
|
||||||
mkdir -p "../models/vae"
|
exit 1
|
||||||
|
|
||||||
# migrate the legacy models to the correct path (if already downloaded)
|
|
||||||
if [ -e "sd-v1-4.ckpt" ]; then mv sd-v1-4.ckpt ../models/stable-diffusion/; fi
|
|
||||||
if [ -e "custom-model.ckpt" ]; then mv custom-model.ckpt ../models/stable-diffusion/; fi
|
|
||||||
if [ -e "GFPGANv1.3.pth" ]; then mv GFPGANv1.3.pth ../models/gfpgan/; fi
|
|
||||||
if [ -e "RealESRGAN_x4plus.pth" ]; then mv RealESRGAN_x4plus.pth ../models/realesrgan/; fi
|
|
||||||
if [ -e "RealESRGAN_x4plus_anime_6B.pth" ]; then mv RealESRGAN_x4plus_anime_6B.pth ../models/realesrgan/; fi
|
|
||||||
|
|
||||||
OS_NAME=$(uname -s)
|
|
||||||
case "${OS_NAME}" in
|
|
||||||
Linux*) OS_NAME="linux";;
|
|
||||||
Darwin*) OS_NAME="macos";;
|
|
||||||
*) echo "Unknown OS: $OS_NAME! This script runs only on Linux or Mac" && exit
|
|
||||||
esac
|
|
||||||
|
|
||||||
# install torch and torchvision
|
|
||||||
if python ../scripts/check_modules.py torch torchvision; then
|
|
||||||
# temp fix for installations that installed torch 2.0 by mistake
|
|
||||||
if [ "$OS_NAME" == "linux" ]; then
|
|
||||||
python -m pip install --upgrade torch==1.13.1+cu116 torchvision==0.14.1+cu116 --extra-index-url https://download.pytorch.org/whl/cu116 -q
|
|
||||||
elif [ "$OS_NAME" == "macos" ]; then
|
|
||||||
python -m pip install --upgrade torch==1.13.1 torchvision==0.14.1 -q
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "torch and torchvision have already been installed."
|
|
||||||
else
|
|
||||||
echo "Installing torch and torchvision.."
|
|
||||||
|
|
||||||
export PYTHONNOUSERSITE=1
|
|
||||||
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
|
|
||||||
|
|
||||||
if [ "$OS_NAME" == "linux" ]; then
|
|
||||||
if python -m pip install --upgrade torch==1.13.1+cu116 torchvision==0.14.1+cu116 --extra-index-url https://download.pytorch.org/whl/cu116 ; then
|
|
||||||
echo "Installed."
|
|
||||||
else
|
|
||||||
fail "torch install failed"
|
|
||||||
fi
|
|
||||||
elif [ "$OS_NAME" == "macos" ]; then
|
|
||||||
if python -m pip install --upgrade torch==1.13.1 torchvision==0.14.1 ; then
|
|
||||||
echo "Installed."
|
|
||||||
else
|
|
||||||
fail "torch install failed"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install/upgrade sdkit
|
if ! command -v uvicorn &> /dev/null; then
|
||||||
if python ../scripts/check_modules.py sdkit sdkit.models ldm transformers numpy antlr4 gfpgan realesrgan ; then
|
|
||||||
echo "sdkit is already installed."
|
|
||||||
|
|
||||||
# skip sdkit upgrade if in developer-mode
|
|
||||||
if [ ! -e "../src/sdkit" ]; then
|
|
||||||
export PYTHONNOUSERSITE=1
|
|
||||||
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
|
|
||||||
|
|
||||||
python -m pip install --upgrade sdkit==1.0.60 -q
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Installing sdkit: https://pypi.org/project/sdkit/"
|
|
||||||
|
|
||||||
export PYTHONNOUSERSITE=1
|
|
||||||
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
|
|
||||||
|
|
||||||
if python -m pip install sdkit==1.0.60 ; then
|
|
||||||
echo "Installed."
|
|
||||||
else
|
|
||||||
fail "sdkit install failed"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
python -c "from importlib.metadata import version; print('sdkit version:', version('sdkit'))"
|
|
||||||
|
|
||||||
# upgrade stable-diffusion-sdkit
|
|
||||||
python -m pip install --upgrade stable-diffusion-sdkit==2.1.4 -q
|
|
||||||
python -c "from importlib.metadata import version; print('stable-diffusion version:', version('stable-diffusion-sdkit'))"
|
|
||||||
|
|
||||||
# install rich
|
|
||||||
if python ../scripts/check_modules.py rich; then
|
|
||||||
echo "rich has already been installed."
|
|
||||||
else
|
|
||||||
echo "Installing rich.."
|
|
||||||
|
|
||||||
export PYTHONNOUSERSITE=1
|
|
||||||
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
|
|
||||||
|
|
||||||
if python -m pip install rich ; then
|
|
||||||
echo "Installed."
|
|
||||||
else
|
|
||||||
fail "Install failed for rich"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# install ruamel
|
|
||||||
if python ../scripts/check_modules.py ruamel.yaml; then
|
|
||||||
echo "ruamel.yaml has already been installed."
|
|
||||||
else
|
|
||||||
echo "Installing ruamel.yaml.."
|
|
||||||
|
|
||||||
export PYTHONNOUSERSITE=1
|
|
||||||
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
|
|
||||||
|
|
||||||
if python -m pip install ruamel.yaml==0.17.21 ; then
|
|
||||||
echo "Installed."
|
|
||||||
else
|
|
||||||
fail "Install failed for rich"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if python ../scripts/check_modules.py uvicorn fastapi ; then
|
|
||||||
echo "Packages necessary for Easy Diffusion were already installed"
|
|
||||||
else
|
|
||||||
printf "\n\nDownloading packages necessary for Easy Diffusion..\n\n"
|
|
||||||
|
|
||||||
export PYTHONNOUSERSITE=1
|
|
||||||
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
|
|
||||||
|
|
||||||
if conda install -c conda-forge -y uvicorn fastapi ; then
|
|
||||||
echo "Installed. Testing.."
|
|
||||||
else
|
|
||||||
fail "'conda install uvicorn' failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v uvicorn &> /dev/null; then
|
|
||||||
fail "UI packages not found!"
|
fail "UI packages not found!"
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "../models/stable-diffusion/sd-v1-4.ckpt" ]; then
|
|
||||||
model_size=`filesize "../models/stable-diffusion/sd-v1-4.ckpt"`
|
|
||||||
|
|
||||||
if [ "$model_size" -eq "4265380512" ] || [ "$model_size" -eq "7703807346" ] || [ "$model_size" -eq "7703810927" ]; then
|
|
||||||
echo "Data files (weights) necessary for Stable Diffusion were already downloaded"
|
|
||||||
else
|
|
||||||
printf "\n\nThe model file present at models/stable-diffusion/sd-v1-4.ckpt is invalid. It is only $model_size bytes in size. Re-downloading.."
|
|
||||||
rm ../models/stable-diffusion/sd-v1-4.ckpt
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "../models/stable-diffusion/sd-v1-4.ckpt" ]; then
|
|
||||||
echo "Downloading data files (weights) for Stable Diffusion.."
|
|
||||||
|
|
||||||
curl -L -k https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt > ../models/stable-diffusion/sd-v1-4.ckpt
|
|
||||||
|
|
||||||
if [ -f "../models/stable-diffusion/sd-v1-4.ckpt" ]; then
|
|
||||||
model_size=`filesize "../models/stable-diffusion/sd-v1-4.ckpt"`
|
|
||||||
if [ ! "$model_size" == "4265380512" ]; then
|
|
||||||
fail "The downloaded model file was invalid! Bytes downloaded: $model_size"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
fail "Error downloading the data files (weights) for Stable Diffusion"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [ -f "../models/gfpgan/GFPGANv1.3.pth" ]; then
|
|
||||||
model_size=`filesize "../models/gfpgan/GFPGANv1.3.pth"`
|
|
||||||
|
|
||||||
if [ "$model_size" -eq "348632874" ]; then
|
|
||||||
echo "Data files (weights) necessary for GFPGAN (Face Correction) were already downloaded"
|
|
||||||
else
|
|
||||||
printf "\n\nThe model file present at models/gfpgan/GFPGANv1.3.pth is invalid. It is only $model_size bytes in size. Re-downloading.."
|
|
||||||
rm ../models/gfpgan/GFPGANv1.3.pth
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "../models/gfpgan/GFPGANv1.3.pth" ]; then
|
|
||||||
echo "Downloading data files (weights) for GFPGAN (Face Correction).."
|
|
||||||
|
|
||||||
curl -L -k https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth > ../models/gfpgan/GFPGANv1.3.pth
|
|
||||||
|
|
||||||
if [ -f "../models/gfpgan/GFPGANv1.3.pth" ]; then
|
|
||||||
model_size=`filesize "../models/gfpgan/GFPGANv1.3.pth"`
|
|
||||||
if [ ! "$model_size" -eq "348632874" ]; then
|
|
||||||
fail "The downloaded GFPGAN model file was invalid! Bytes downloaded: $model_size"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
fail "Error downloading the data files (weights) for GFPGAN (Face Correction)."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [ -f "../models/realesrgan/RealESRGAN_x4plus.pth" ]; then
|
|
||||||
model_size=`filesize "../models/realesrgan/RealESRGAN_x4plus.pth"`
|
|
||||||
|
|
||||||
if [ "$model_size" -eq "67040989" ]; then
|
|
||||||
echo "Data files (weights) necessary for ESRGAN (Resolution Upscaling) x4plus were already downloaded"
|
|
||||||
else
|
|
||||||
printf "\n\nThe model file present at models/realesrgan/RealESRGAN_x4plus.pth is invalid. It is only $model_size bytes in size. Re-downloading.."
|
|
||||||
rm ../models/realesrgan/RealESRGAN_x4plus.pth
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "../models/realesrgan/RealESRGAN_x4plus.pth" ]; then
|
|
||||||
echo "Downloading data files (weights) for ESRGAN (Resolution Upscaling) x4plus.."
|
|
||||||
|
|
||||||
curl -L -k https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth > ../models/realesrgan/RealESRGAN_x4plus.pth
|
|
||||||
|
|
||||||
if [ -f "../models/realesrgan/RealESRGAN_x4plus.pth" ]; then
|
|
||||||
model_size=`filesize "../models/realesrgan/RealESRGAN_x4plus.pth"`
|
|
||||||
if [ ! "$model_size" -eq "67040989" ]; then
|
|
||||||
fail "The downloaded ESRGAN x4plus model file was invalid! Bytes downloaded: $model_size"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
fail "Error downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [ -f "../models/realesrgan/RealESRGAN_x4plus_anime_6B.pth" ]; then
|
|
||||||
model_size=`filesize "../models/realesrgan/RealESRGAN_x4plus_anime_6B.pth"`
|
|
||||||
|
|
||||||
if [ "$model_size" -eq "17938799" ]; then
|
|
||||||
echo "Data files (weights) necessary for ESRGAN (Resolution Upscaling) x4plus_anime were already downloaded"
|
|
||||||
else
|
|
||||||
printf "\n\nThe model file present at models/realesrgan/RealESRGAN_x4plus_anime_6B.pth is invalid. It is only $model_size bytes in size. Re-downloading.."
|
|
||||||
rm ../models/realesrgan/RealESRGAN_x4plus_anime_6B.pth
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "../models/realesrgan/RealESRGAN_x4plus_anime_6B.pth" ]; then
|
|
||||||
echo "Downloading data files (weights) for ESRGAN (Resolution Upscaling) x4plus_anime.."
|
|
||||||
|
|
||||||
curl -L -k https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth > ../models/realesrgan/RealESRGAN_x4plus_anime_6B.pth
|
|
||||||
|
|
||||||
if [ -f "../models/realesrgan/RealESRGAN_x4plus_anime_6B.pth" ]; then
|
|
||||||
model_size=`filesize "../models/realesrgan/RealESRGAN_x4plus_anime_6B.pth"`
|
|
||||||
if [ ! "$model_size" -eq "17938799" ]; then
|
|
||||||
fail "The downloaded ESRGAN x4plus_anime model file was invalid! Bytes downloaded: $model_size"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
fail "Error downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus_anime."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [ -f "../models/vae/vae-ft-mse-840000-ema-pruned.ckpt" ]; then
|
|
||||||
model_size=`filesize "../models/vae/vae-ft-mse-840000-ema-pruned.ckpt"`
|
|
||||||
|
|
||||||
if [ "$model_size" -eq "334695179" ]; then
|
|
||||||
echo "Data files (weights) necessary for the default VAE (sd-vae-ft-mse-original) were already downloaded"
|
|
||||||
else
|
|
||||||
printf "\n\nThe model file present at models/vae/vae-ft-mse-840000-ema-pruned.ckpt is invalid. It is only $model_size bytes in size. Re-downloading.."
|
|
||||||
rm ../models/vae/vae-ft-mse-840000-ema-pruned.ckpt
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "../models/vae/vae-ft-mse-840000-ema-pruned.ckpt" ]; then
|
|
||||||
echo "Downloading data files (weights) for the default VAE (sd-vae-ft-mse-original).."
|
|
||||||
|
|
||||||
curl -L -k https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.ckpt > ../models/vae/vae-ft-mse-840000-ema-pruned.ckpt
|
|
||||||
|
|
||||||
if [ -f "../models/vae/vae-ft-mse-840000-ema-pruned.ckpt" ]; then
|
|
||||||
model_size=`filesize "../models/vae/vae-ft-mse-840000-ema-pruned.ckpt"`
|
|
||||||
if [ ! "$model_size" -eq "334695179" ]; then
|
|
||||||
printf "\n\nError: The downloaded default VAE (sd-vae-ft-mse-original) file was invalid! Bytes downloaded: $model_size\n\n"
|
|
||||||
printf "\n\nError downloading the data files (weights) for the default VAE (sd-vae-ft-mse-original). Sorry about that, please try to:\n 1. Run this installer again.\n 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting\n 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB\n 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues\nThanks!\n\n"
|
|
||||||
read -p "Press any key to continue"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
printf "\n\nError downloading the data files (weights) for the default VAE (sd-vae-ft-mse-original). Sorry about that, please try to:\n 1. Run this installer again.\n 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting\n 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB\n 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues\nThanks!\n\n"
|
|
||||||
read -p "Press any key to continue"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ `grep -c sd_install_complete ../scripts/install_status.txt` -gt "0" ]; then
|
if [ `grep -c sd_install_complete ../scripts/install_status.txt` -gt "0" ]; then
|
||||||
@ -332,8 +68,17 @@ python --version
|
|||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
export SD_UI_PATH=`pwd`/ui
|
export SD_UI_PATH=`pwd`/ui
|
||||||
|
export ED_BIND_PORT="$( python scripts/get_config.py --default=9000 net listen_port )"
|
||||||
|
case "$( python scripts/get_config.py --default=False net listen_to_network )" in
|
||||||
|
"True")
|
||||||
|
export ED_BIND_IP=0.0.0.0
|
||||||
|
;;
|
||||||
|
"False")
|
||||||
|
export ED_BIND_IP=127.0.0.1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
cd stable-diffusion
|
cd stable-diffusion
|
||||||
|
|
||||||
uvicorn main:server_api --app-dir "$SD_UI_PATH" --port ${SD_UI_BIND_PORT:-9000} --host ${SD_UI_BIND_IP:-0.0.0.0} --log-level error
|
uvicorn main:server_api --app-dir "$SD_UI_PATH" --port "$ED_BIND_PORT" --host "$ED_BIND_IP" --log-level error
|
||||||
|
|
||||||
read -p "Press any key to continue"
|
read -p "Press any key to continue"
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import json
|
|
||||||
import traceback
|
import traceback
|
||||||
import logging
|
|
||||||
import shlex
|
import shlex
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
yaml = YAML()
|
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
from rich.logging import RichHandler
|
import warnings
|
||||||
|
|
||||||
from sdkit.utils import log as sdkit_log # hack, so we can overwrite the log config
|
|
||||||
|
|
||||||
from easydiffusion import task_manager
|
from easydiffusion import task_manager
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
from rich.logging import RichHandler
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.panel import Panel
|
||||||
|
from sdkit.utils import log as sdkit_log # hack, so we can overwrite the log config
|
||||||
|
|
||||||
|
yaml = YAML()
|
||||||
|
|
||||||
# Remove all handlers associated with the root logger object.
|
# Remove all handlers associated with the root logger object.
|
||||||
for handler in logging.root.handlers[:]:
|
for handler in logging.root.handlers[:]:
|
||||||
@ -58,54 +61,81 @@ APP_CONFIG_DEFAULTS = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
IMAGE_EXTENSIONS = [".png", ".apng", ".jpg", ".jpeg", ".jfif", ".pjpeg", ".pjp", ".jxl", ".gif", ".webp", ".avif", ".svg"]
|
IMAGE_EXTENSIONS = [
|
||||||
|
".png",
|
||||||
|
".apng",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".jfif",
|
||||||
|
".pjpeg",
|
||||||
|
".pjp",
|
||||||
|
".jxl",
|
||||||
|
".gif",
|
||||||
|
".webp",
|
||||||
|
".avif",
|
||||||
|
".svg",
|
||||||
|
]
|
||||||
CUSTOM_MODIFIERS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "modifiers"))
|
CUSTOM_MODIFIERS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "modifiers"))
|
||||||
CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS=[".portrait", "_portrait", " portrait", "-portrait"]
|
CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS = [
|
||||||
CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS=[".landscape", "_landscape", " landscape", "-landscape"]
|
".portrait",
|
||||||
|
"_portrait",
|
||||||
|
" portrait",
|
||||||
|
"-portrait",
|
||||||
|
]
|
||||||
|
CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS = [
|
||||||
|
".landscape",
|
||||||
|
"_landscape",
|
||||||
|
" landscape",
|
||||||
|
"-landscape",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
os.makedirs(USER_UI_PLUGINS_DIR, exist_ok=True)
|
os.makedirs(USER_UI_PLUGINS_DIR, exist_ok=True)
|
||||||
os.makedirs(USER_SERVER_PLUGINS_DIR, exist_ok=True)
|
os.makedirs(USER_SERVER_PLUGINS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# https://pytorch.org/docs/stable/storage.html
|
||||||
|
warnings.filterwarnings("ignore", category=UserWarning, message="TypedStorage is deprecated")
|
||||||
|
|
||||||
load_server_plugins()
|
load_server_plugins()
|
||||||
|
|
||||||
update_render_threads()
|
update_render_threads()
|
||||||
|
|
||||||
|
|
||||||
def getConfig(default_val=APP_CONFIG_DEFAULTS):
|
def getConfig(default_val=APP_CONFIG_DEFAULTS):
|
||||||
config_yaml_path = os.path.join(CONFIG_DIR, 'config.yaml')
|
config_yaml_path = os.path.join(CONFIG_DIR, "config.yaml")
|
||||||
if os.path.isfile(config_yaml_path):
|
if os.path.isfile(config_yaml_path):
|
||||||
try:
|
try:
|
||||||
log.info('Loading config.yaml')
|
log.info("Loading config.yaml")
|
||||||
with open(config_yaml_path, 'r', encoding='utf-8') as f:
|
with open(config_yaml_path, "r", encoding="utf-8") as f:
|
||||||
config = yaml.load(f)
|
config = yaml.load(f)
|
||||||
if 'net' not in config:
|
if "net" not in config:
|
||||||
config['net'] = {}
|
config["net"] = {}
|
||||||
if os.getenv('SD_UI_BIND_PORT') is not None:
|
if os.getenv("SD_UI_BIND_PORT") is not None:
|
||||||
config['net']['listen_port'] = int(os.getenv('SD_UI_BIND_PORT'))
|
config["net"]["listen_port"] = int(os.getenv("SD_UI_BIND_PORT"))
|
||||||
else:
|
else:
|
||||||
config['net']['listen_port'] = 9000
|
config['net']['listen_port'] = 9000
|
||||||
if os.getenv('SD_UI_BIND_IP') is not None:
|
if os.getenv("SD_UI_BIND_IP") is not None:
|
||||||
config['net']['listen_to_network'] = (os.getenv('SD_UI_BIND_IP') == '0.0.0.0')
|
config["net"]["listen_to_network"] = os.getenv("SD_UI_BIND_IP") == "0.0.0.0"
|
||||||
else:
|
else:
|
||||||
config['net']['listen_to_network'] = True
|
config["net"]["listen_to_network"] = True
|
||||||
return config
|
return config
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warn(traceback.format_exc())
|
log.warn(traceback.format_exc())
|
||||||
return default_val
|
return default_val
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
config_json_path = os.path.join(CONFIG_DIR, 'config.json')
|
config_json_path = os.path.join(CONFIG_DIR, "config.json")
|
||||||
if not os.path.exists(config_json_path):
|
if not os.path.exists(config_json_path):
|
||||||
return default_val
|
return default_val
|
||||||
else:
|
else:
|
||||||
log.info('Converting old json config file to yaml')
|
log.info("Converting old json config file to yaml")
|
||||||
with open(config_json_path, 'r', encoding='utf-8') as f:
|
with open(config_json_path, "r", encoding="utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
# Save config in new format
|
# Save config in new format
|
||||||
setConfig(config)
|
setConfig(config)
|
||||||
os.rename(config_json_path, config_json_path + '.bak')
|
os.rename(config_json_path, config_json_path + ".bak")
|
||||||
log.info('Saved old config.json as config.json.bak')
|
log.info("Saved old config.json as config.json.bak")
|
||||||
return getConfig(default_val)
|
return getConfig(default_val)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warn(traceback.format_exc())
|
log.warn(traceback.format_exc())
|
||||||
@ -121,50 +151,6 @@ def setConfig(config):
|
|||||||
except:
|
except:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
|
|
||||||
try: # config.bat
|
|
||||||
config_bat_path = os.path.join(CONFIG_DIR, "config.bat")
|
|
||||||
config_bat = []
|
|
||||||
|
|
||||||
if "update_branch" in config:
|
|
||||||
config_bat.append(f"@set update_branch={config['update_branch']}")
|
|
||||||
|
|
||||||
config_bat.append(f"@set SD_UI_BIND_PORT={config['net']['listen_port']}")
|
|
||||||
bind_ip = "0.0.0.0" if config["net"]["listen_to_network"] else "127.0.0.1"
|
|
||||||
config_bat.append(f"@set SD_UI_BIND_IP={bind_ip}")
|
|
||||||
|
|
||||||
# Preserve these variables if they are set
|
|
||||||
for var in PRESERVE_CONFIG_VARS:
|
|
||||||
if os.getenv(var) is not None:
|
|
||||||
config_bat.append(f"@set {var}={os.getenv(var)}")
|
|
||||||
|
|
||||||
if len(config_bat) > 0:
|
|
||||||
with open(config_bat_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write("\n".join(config_bat))
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
|
|
||||||
try: # config.sh
|
|
||||||
config_sh_path = os.path.join(CONFIG_DIR, "config.sh")
|
|
||||||
config_sh = ["#!/bin/bash"]
|
|
||||||
|
|
||||||
if "update_branch" in config:
|
|
||||||
config_sh.append(f"export update_branch={config['update_branch']}")
|
|
||||||
|
|
||||||
config_sh.append(f"export SD_UI_BIND_PORT={config['net']['listen_port']}")
|
|
||||||
bind_ip = "0.0.0.0" if config["net"]["listen_to_network"] else "127.0.0.1"
|
|
||||||
config_sh.append(f"export SD_UI_BIND_IP={bind_ip}")
|
|
||||||
|
|
||||||
# Preserve these variables if they are set
|
|
||||||
for var in PRESERVE_CONFIG_VARS:
|
|
||||||
if os.getenv(var) is not None:
|
|
||||||
config_bat.append(f'export {var}="{shlex.quote(os.getenv(var))}"')
|
|
||||||
|
|
||||||
if len(config_sh) > 1:
|
|
||||||
with open(config_sh_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write("\n".join(config_sh))
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
|
|
||||||
|
|
||||||
def save_to_config(ckpt_model_name, vae_model_name, hypernetwork_model_name, vram_usage_level):
|
def save_to_config(ckpt_model_name, vae_model_name, hypernetwork_model_name, vram_usage_level):
|
||||||
config = getConfig()
|
config = getConfig()
|
||||||
@ -253,18 +239,56 @@ def getIPConfig():
|
|||||||
def open_browser():
|
def open_browser():
|
||||||
config = getConfig()
|
config = getConfig()
|
||||||
ui = config.get("ui", {})
|
ui = config.get("ui", {})
|
||||||
net = config.get("net", {"listen_port": 9000})
|
net = config.get("net", {})
|
||||||
port = net.get("listen_port", 9000)
|
port = net.get("listen_port", 9000)
|
||||||
|
|
||||||
if ui.get("open_browser_on_start", True):
|
if ui.get("open_browser_on_start", True):
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
webbrowser.open(f"http://localhost:{port}")
|
webbrowser.open(f"http://localhost:{port}")
|
||||||
|
|
||||||
|
Console().print(
|
||||||
|
Panel(
|
||||||
|
"\n"
|
||||||
|
+ "[white]Easy Diffusion is ready to serve requests.\n\n"
|
||||||
|
+ "A new browser tab should have been opened by now.\n"
|
||||||
|
+ f"If not, please open your web browser and navigate to [bold yellow underline]http://localhost:{port}/\n",
|
||||||
|
title="Easy Diffusion is ready",
|
||||||
|
style="bold yellow on blue",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fail_and_die(fail_type: str, data: str):
|
||||||
|
suggestions = [
|
||||||
|
"Run this installer again.",
|
||||||
|
"If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB",
|
||||||
|
"If that doesn't solve the problem, please file an issue at https://github.com/easydiffusion/easydiffusion/issues",
|
||||||
|
]
|
||||||
|
|
||||||
|
if fail_type == "model_download":
|
||||||
|
fail_label = f"Error downloading the {data} model"
|
||||||
|
suggestions.insert(
|
||||||
|
1,
|
||||||
|
"If that doesn't fix it, please try to download the file manually. The address to download from, and the destination to save to are printed above this message.",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
fail_label = "Error while installing Easy Diffusion"
|
||||||
|
|
||||||
|
msg = [f"{fail_label}. Sorry about that, please try to:"]
|
||||||
|
for i, suggestion in enumerate(suggestions):
|
||||||
|
msg.append(f"{i+1}. {suggestion}")
|
||||||
|
msg.append("Thanks!")
|
||||||
|
|
||||||
|
print("\n".join(msg))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def get_image_modifiers():
|
def get_image_modifiers():
|
||||||
modifiers_json_path = os.path.join(SD_UI_DIR, "modifiers.json")
|
modifiers_json_path = os.path.join(SD_UI_DIR, "modifiers.json")
|
||||||
|
|
||||||
modifier_categories = {}
|
modifier_categories = {}
|
||||||
original_category_order=[]
|
original_category_order = []
|
||||||
with open(modifiers_json_path, "r", encoding="utf-8") as f:
|
with open(modifiers_json_path, "r", encoding="utf-8") as f:
|
||||||
modifiers_file = json.load(f)
|
modifiers_file = json.load(f)
|
||||||
|
|
||||||
@ -274,14 +298,14 @@ def get_image_modifiers():
|
|||||||
|
|
||||||
# convert modifiers from a list of objects to a dict of dicts
|
# convert modifiers from a list of objects to a dict of dicts
|
||||||
for category_item in modifiers_file:
|
for category_item in modifiers_file:
|
||||||
category_name = category_item['category']
|
category_name = category_item["category"]
|
||||||
original_category_order.append(category_name)
|
original_category_order.append(category_name)
|
||||||
category = {}
|
category = {}
|
||||||
for modifier_item in category_item['modifiers']:
|
for modifier_item in category_item["modifiers"]:
|
||||||
modifier = {}
|
modifier = {}
|
||||||
for preview_item in modifier_item['previews']:
|
for preview_item in modifier_item["previews"]:
|
||||||
modifier[preview_item['name']] = preview_item['path']
|
modifier[preview_item["name"]] = preview_item["path"]
|
||||||
category[modifier_item['modifier']] = modifier
|
category[modifier_item["modifier"]] = modifier
|
||||||
modifier_categories[category_name] = category
|
modifier_categories[category_name] = category
|
||||||
|
|
||||||
def scan_directory(directory_path: str, category_name="Modifiers"):
|
def scan_directory(directory_path: str, category_name="Modifiers"):
|
||||||
@ -294,12 +318,27 @@ def get_image_modifiers():
|
|||||||
modifier_name = entry.name[: -len(file_extension[0])]
|
modifier_name = entry.name[: -len(file_extension[0])]
|
||||||
modifier_path = f"custom/{entry.path[len(CUSTOM_MODIFIERS_DIR) + 1:]}"
|
modifier_path = f"custom/{entry.path[len(CUSTOM_MODIFIERS_DIR) + 1:]}"
|
||||||
# URL encode path segments
|
# URL encode path segments
|
||||||
modifier_path = "/".join(map(lambda segment: urllib.parse.quote(segment), modifier_path.split("/")))
|
modifier_path = "/".join(
|
||||||
|
map(
|
||||||
|
lambda segment: urllib.parse.quote(segment),
|
||||||
|
modifier_path.split("/"),
|
||||||
|
)
|
||||||
|
)
|
||||||
is_portrait = True
|
is_portrait = True
|
||||||
is_landscape = True
|
is_landscape = True
|
||||||
|
|
||||||
portrait_extension = list(filter(lambda e: modifier_name.lower().endswith(e), CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS))
|
portrait_extension = list(
|
||||||
landscape_extension = list(filter(lambda e: modifier_name.lower().endswith(e), CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS))
|
filter(
|
||||||
|
lambda e: modifier_name.lower().endswith(e),
|
||||||
|
CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
landscape_extension = list(
|
||||||
|
filter(
|
||||||
|
lambda e: modifier_name.lower().endswith(e),
|
||||||
|
CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if len(portrait_extension) > 0:
|
if len(portrait_extension) > 0:
|
||||||
is_landscape = False
|
is_landscape = False
|
||||||
@ -308,23 +347,23 @@ def get_image_modifiers():
|
|||||||
is_portrait = False
|
is_portrait = False
|
||||||
modifier_name = modifier_name[: -len(landscape_extension[0])]
|
modifier_name = modifier_name[: -len(landscape_extension[0])]
|
||||||
|
|
||||||
if (category_name not in modifier_categories):
|
if category_name not in modifier_categories:
|
||||||
modifier_categories[category_name] = {}
|
modifier_categories[category_name] = {}
|
||||||
|
|
||||||
category = modifier_categories[category_name]
|
category = modifier_categories[category_name]
|
||||||
|
|
||||||
if (modifier_name not in category):
|
if modifier_name not in category:
|
||||||
category[modifier_name] = {}
|
category[modifier_name] = {}
|
||||||
|
|
||||||
if (is_portrait or "portrait" not in category[modifier_name]):
|
if is_portrait or "portrait" not in category[modifier_name]:
|
||||||
category[modifier_name]["portrait"] = modifier_path
|
category[modifier_name]["portrait"] = modifier_path
|
||||||
|
|
||||||
if (is_landscape or "landscape" not in category[modifier_name]):
|
if is_landscape or "landscape" not in category[modifier_name]:
|
||||||
category[modifier_name]["landscape"] = modifier_path
|
category[modifier_name]["landscape"] = modifier_path
|
||||||
elif entry.is_dir():
|
elif entry.is_dir():
|
||||||
scan_directory(
|
scan_directory(
|
||||||
entry.path,
|
entry.path,
|
||||||
entry.name if directory_path==CUSTOM_MODIFIERS_DIR else f"{category_name}/{entry.name}",
|
entry.name if directory_path == CUSTOM_MODIFIERS_DIR else f"{category_name}/{entry.name}",
|
||||||
)
|
)
|
||||||
|
|
||||||
scan_directory(CUSTOM_MODIFIERS_DIR)
|
scan_directory(CUSTOM_MODIFIERS_DIR)
|
||||||
@ -337,12 +376,12 @@ def get_image_modifiers():
|
|||||||
# convert the modifiers back into a list of objects
|
# convert the modifiers back into a list of objects
|
||||||
modifier_categories_list = []
|
modifier_categories_list = []
|
||||||
for category_name in [*original_category_order, *custom_categories]:
|
for category_name in [*original_category_order, *custom_categories]:
|
||||||
category = { 'category': category_name, 'modifiers': [] }
|
category = {"category": category_name, "modifiers": []}
|
||||||
for modifier_name in sorted(modifier_categories[category_name].keys(), key=str.casefold):
|
for modifier_name in sorted(modifier_categories[category_name].keys(), key=str.casefold):
|
||||||
modifier = { 'modifier': modifier_name, 'previews': [] }
|
modifier = {"modifier": modifier_name, "previews": []}
|
||||||
for preview_name, preview_path in modifier_categories[category_name][modifier_name].items():
|
for preview_name, preview_path in modifier_categories[category_name][modifier_name].items():
|
||||||
modifier['previews'].append({ 'name': preview_name, 'path': preview_path })
|
modifier["previews"].append({"name": preview_name, "path": preview_path})
|
||||||
category['modifiers'].append(modifier)
|
category["modifiers"].append(modifier)
|
||||||
modifier_categories_list.append(category)
|
modifier_categories_list.append(category)
|
||||||
|
|
||||||
return modifier_categories_list
|
return modifier_categories_list
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import torch
|
|
||||||
import traceback
|
|
||||||
import re
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import torch
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -118,7 +118,10 @@ def auto_pick_devices(currently_active_devices):
|
|||||||
# These already-running devices probably aren't terrible, since they were picked in the past.
|
# These already-running devices probably aren't terrible, since they were picked in the past.
|
||||||
# Worst case, the user can restart the program and that'll get rid of them.
|
# Worst case, the user can restart the program and that'll get rid of them.
|
||||||
devices = list(
|
devices = list(
|
||||||
filter((lambda x: x["mem_free"] > mem_free_threshold or x["device"] in currently_active_devices), devices)
|
filter(
|
||||||
|
(lambda x: x["mem_free"] > mem_free_threshold or x["device"] in currently_active_devices),
|
||||||
|
devices,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
devices = list(map(lambda x: x["device"], devices))
|
devices = list(map(lambda x: x["device"], devices))
|
||||||
return devices
|
return devices
|
||||||
@ -162,6 +165,7 @@ def needs_to_force_full_precision(context):
|
|||||||
and (
|
and (
|
||||||
" 1660" in device_name
|
" 1660" in device_name
|
||||||
or " 1650" in device_name
|
or " 1650" in device_name
|
||||||
|
or " 1630" in device_name
|
||||||
or " t400" in device_name
|
or " t400" in device_name
|
||||||
or " t550" in device_name
|
or " t550" in device_name
|
||||||
or " t600" in device_name
|
or " t600" in device_name
|
||||||
@ -221,9 +225,9 @@ def is_device_compatible(device):
|
|||||||
try:
|
try:
|
||||||
_, mem_total = torch.cuda.mem_get_info(device)
|
_, mem_total = torch.cuda.mem_get_info(device)
|
||||||
mem_total /= float(10**9)
|
mem_total /= float(10**9)
|
||||||
if mem_total < 3.0:
|
if mem_total < 1.9:
|
||||||
if is_device_compatible.history.get(device) == None:
|
if is_device_compatible.history.get(device) == None:
|
||||||
log.warn(f"GPU {device} with less than 3 GB of VRAM is not compatible with Stable Diffusion")
|
log.warn(f"GPU {device} with less than 2 GB of VRAM is not compatible with Stable Diffusion")
|
||||||
is_device_compatible.history[device] = 1
|
is_device_compatible.history[device] = 1
|
||||||
return False
|
return False
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
from glob import glob
|
||||||
|
import traceback
|
||||||
|
|
||||||
from easydiffusion import app
|
from easydiffusion import app
|
||||||
from easydiffusion.types import TaskData
|
from easydiffusion.types import TaskData
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
|
||||||
from sdkit import Context
|
from sdkit import Context
|
||||||
from sdkit.models import load_model, unload_model, scan_model
|
from sdkit.models import load_model, scan_model, unload_model, download_model, get_model_info_from_db
|
||||||
|
from sdkit.utils import hash_file_quick
|
||||||
|
|
||||||
KNOWN_MODEL_TYPES = ["stable-diffusion", "vae", "hypernetwork", "gfpgan", "realesrgan", "lora"]
|
KNOWN_MODEL_TYPES = [
|
||||||
|
"stable-diffusion",
|
||||||
|
"vae",
|
||||||
|
"hypernetwork",
|
||||||
|
"gfpgan",
|
||||||
|
"realesrgan",
|
||||||
|
"lora",
|
||||||
|
"codeformer",
|
||||||
|
]
|
||||||
MODEL_EXTENSIONS = {
|
MODEL_EXTENSIONS = {
|
||||||
"stable-diffusion": [".ckpt", ".safetensors"],
|
"stable-diffusion": [".ckpt", ".safetensors"],
|
||||||
"vae": [".vae.pt", ".ckpt", ".safetensors"],
|
"vae": [".vae.pt", ".ckpt", ".safetensors"],
|
||||||
@ -15,14 +26,22 @@ MODEL_EXTENSIONS = {
|
|||||||
"gfpgan": [".pth"],
|
"gfpgan": [".pth"],
|
||||||
"realesrgan": [".pth"],
|
"realesrgan": [".pth"],
|
||||||
"lora": [".ckpt", ".safetensors"],
|
"lora": [".ckpt", ".safetensors"],
|
||||||
|
"codeformer": [".pth"],
|
||||||
}
|
}
|
||||||
DEFAULT_MODELS = {
|
DEFAULT_MODELS = {
|
||||||
"stable-diffusion": [ # needed to support the legacy installations
|
"stable-diffusion": [
|
||||||
"custom-model", # only one custom model file was supported initially, creatively named 'custom-model'
|
{"file_name": "sd-v1-4.ckpt", "model_id": "1.4"},
|
||||||
"sd-v1-4", # Default fallback.
|
],
|
||||||
|
"gfpgan": [
|
||||||
|
{"file_name": "GFPGANv1.4.pth", "model_id": "1.4"},
|
||||||
|
],
|
||||||
|
"realesrgan": [
|
||||||
|
{"file_name": "RealESRGAN_x4plus.pth", "model_id": "x4plus"},
|
||||||
|
{"file_name": "RealESRGAN_x4plus_anime_6B.pth", "model_id": "x4plus_anime_6"},
|
||||||
|
],
|
||||||
|
"vae": [
|
||||||
|
{"file_name": "vae-ft-mse-840000-ema-pruned.ckpt", "model_id": "vae-ft-mse-840000-ema-pruned"},
|
||||||
],
|
],
|
||||||
"gfpgan": ["GFPGANv1.3"],
|
|
||||||
"realesrgan": ["RealESRGAN_x4plus"],
|
|
||||||
}
|
}
|
||||||
MODELS_TO_LOAD_ON_START = ["stable-diffusion", "vae", "hypernetwork", "lora"]
|
MODELS_TO_LOAD_ON_START = ["stable-diffusion", "vae", "hypernetwork", "lora"]
|
||||||
|
|
||||||
@ -31,6 +50,8 @@ known_models = {}
|
|||||||
|
|
||||||
def init():
|
def init():
|
||||||
make_model_folders()
|
make_model_folders()
|
||||||
|
migrate_legacy_model_location() # if necessary
|
||||||
|
download_default_models_if_necessary()
|
||||||
getModels() # run this once, to cache the picklescan results
|
getModels() # run this once, to cache the picklescan results
|
||||||
|
|
||||||
|
|
||||||
@ -39,26 +60,42 @@ def load_default_models(context: Context):
|
|||||||
|
|
||||||
# init default model paths
|
# init default model paths
|
||||||
for model_type in MODELS_TO_LOAD_ON_START:
|
for model_type in MODELS_TO_LOAD_ON_START:
|
||||||
context.model_paths[model_type] = resolve_model_to_use(model_type=model_type)
|
context.model_paths[model_type] = resolve_model_to_use(model_type=model_type, fail_if_not_found=False)
|
||||||
try:
|
try:
|
||||||
load_model(context, model_type)
|
load_model(
|
||||||
|
context,
|
||||||
|
model_type,
|
||||||
|
scan_model=context.model_paths[model_type] != None
|
||||||
|
and not context.model_paths[model_type].endswith(".safetensors"),
|
||||||
|
)
|
||||||
|
if model_type in context.model_load_errors:
|
||||||
|
del context.model_load_errors[model_type]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"[red]Error while loading {model_type} model: {context.model_paths[model_type]}[/red]")
|
log.error(f"[red]Error while loading {model_type} model: {context.model_paths[model_type]}[/red]")
|
||||||
log.error(f"[red]Error: {e}[/red]")
|
if "DefaultCPUAllocator: not enough memory" in str(e):
|
||||||
log.error(f"[red]Consider removing the model from the model folder.[red]")
|
log.error(
|
||||||
|
f"[red]Your PC is low on system RAM. Please add some virtual memory (or swap space) by following the instructions at this link: https://www.ibm.com/docs/en/opw/8.2.0?topic=tuning-optional-increasing-paging-file-size-windows-computers[/red]"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.exception(e)
|
||||||
|
del context.model_paths[model_type]
|
||||||
|
|
||||||
|
context.model_load_errors[model_type] = str(e) # storing the entire Exception can lead to memory leaks
|
||||||
|
|
||||||
|
|
||||||
def unload_all(context: Context):
|
def unload_all(context: Context):
|
||||||
for model_type in KNOWN_MODEL_TYPES:
|
for model_type in KNOWN_MODEL_TYPES:
|
||||||
unload_model(context, model_type)
|
unload_model(context, model_type)
|
||||||
|
if model_type in context.model_load_errors:
|
||||||
|
del context.model_load_errors[model_type]
|
||||||
|
|
||||||
|
|
||||||
def resolve_model_to_use(model_name: str = None, model_type: str = None):
|
def resolve_model_to_use(model_name: str = None, model_type: str = None, fail_if_not_found: bool = True):
|
||||||
model_extensions = MODEL_EXTENSIONS.get(model_type, [])
|
model_extensions = MODEL_EXTENSIONS.get(model_type, [])
|
||||||
default_models = DEFAULT_MODELS.get(model_type, [])
|
default_models = DEFAULT_MODELS.get(model_type, [])
|
||||||
config = app.getConfig()
|
config = app.getConfig()
|
||||||
|
|
||||||
model_dirs = [os.path.join(app.MODELS_DIR, model_type), app.SD_DIR]
|
model_dir = os.path.join(app.MODELS_DIR, model_type)
|
||||||
if not model_name: # When None try user configured model.
|
if not model_name: # When None try user configured model.
|
||||||
# config = getConfig()
|
# config = getConfig()
|
||||||
if "model" in config and model_type in config["model"]:
|
if "model" in config and model_type in config["model"]:
|
||||||
@ -66,42 +103,42 @@ def resolve_model_to_use(model_name: str = None, model_type: str = None):
|
|||||||
|
|
||||||
if model_name:
|
if model_name:
|
||||||
# Check models directory
|
# Check models directory
|
||||||
models_dir_path = os.path.join(app.MODELS_DIR, model_type, model_name)
|
model_path = os.path.join(model_dir, model_name)
|
||||||
|
if os.path.exists(model_path):
|
||||||
|
return model_path
|
||||||
for model_extension in model_extensions:
|
for model_extension in model_extensions:
|
||||||
if os.path.exists(models_dir_path + model_extension):
|
if os.path.exists(model_path + model_extension):
|
||||||
return models_dir_path + model_extension
|
return model_path + model_extension
|
||||||
if os.path.exists(model_name + model_extension):
|
if os.path.exists(model_name + model_extension):
|
||||||
return os.path.abspath(model_name + model_extension)
|
return os.path.abspath(model_name + model_extension)
|
||||||
|
|
||||||
# Default locations
|
|
||||||
if model_name in default_models:
|
|
||||||
default_model_path = os.path.join(app.SD_DIR, model_name)
|
|
||||||
for model_extension in model_extensions:
|
|
||||||
if os.path.exists(default_model_path + model_extension):
|
|
||||||
return default_model_path + model_extension
|
|
||||||
|
|
||||||
# Can't find requested model, check the default paths.
|
# Can't find requested model, check the default paths.
|
||||||
|
if model_type == "stable-diffusion" and not fail_if_not_found:
|
||||||
for default_model in default_models:
|
for default_model in default_models:
|
||||||
for model_dir in model_dirs:
|
default_model_path = os.path.join(model_dir, default_model["file_name"])
|
||||||
default_model_path = os.path.join(model_dir, default_model)
|
if os.path.exists(default_model_path):
|
||||||
for model_extension in model_extensions:
|
|
||||||
if os.path.exists(default_model_path + model_extension):
|
|
||||||
if model_name is not None:
|
if model_name is not None:
|
||||||
log.warn(
|
log.warn(
|
||||||
f"Could not find the configured custom model {model_name}{model_extension}. Using the default one: {default_model_path}{model_extension}"
|
f"Could not find the configured custom model {model_name}. Using the default one: {default_model_path}"
|
||||||
)
|
)
|
||||||
return default_model_path + model_extension
|
return default_model_path
|
||||||
|
|
||||||
return None
|
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?")
|
||||||
|
|
||||||
|
|
||||||
def reload_models_if_necessary(context: Context, task_data: TaskData):
|
def reload_models_if_necessary(context: Context, task_data: TaskData):
|
||||||
|
face_fix_lower = task_data.use_face_correction.lower() if task_data.use_face_correction else ""
|
||||||
|
upscale_lower = task_data.use_upscale.lower() if task_data.use_upscale else ""
|
||||||
|
|
||||||
model_paths_in_req = {
|
model_paths_in_req = {
|
||||||
"stable-diffusion": task_data.use_stable_diffusion_model,
|
"stable-diffusion": task_data.use_stable_diffusion_model,
|
||||||
"vae": task_data.use_vae_model,
|
"vae": task_data.use_vae_model,
|
||||||
"hypernetwork": task_data.use_hypernetwork_model,
|
"hypernetwork": task_data.use_hypernetwork_model,
|
||||||
"gfpgan": task_data.use_face_correction,
|
"codeformer": task_data.use_face_correction if "codeformer" in face_fix_lower else None,
|
||||||
"realesrgan": task_data.use_upscale,
|
"gfpgan": task_data.use_face_correction if "gfpgan" in face_fix_lower else None,
|
||||||
|
"realesrgan": task_data.use_upscale if "realesrgan" in upscale_lower else None,
|
||||||
|
"latent_upscaler": True if "latent_upscaler" in upscale_lower else None,
|
||||||
"nsfw_checker": True if task_data.block_nsfw else None,
|
"nsfw_checker": True if task_data.block_nsfw else None,
|
||||||
"lora": task_data.use_lora_model,
|
"lora": task_data.use_lora_model,
|
||||||
}
|
}
|
||||||
@ -111,14 +148,28 @@ def reload_models_if_necessary(context: Context, task_data: TaskData):
|
|||||||
if context.model_paths.get(model_type) != path
|
if context.model_paths.get(model_type) != path
|
||||||
}
|
}
|
||||||
|
|
||||||
if set_vram_optimizations(context): # reload SD
|
if task_data.codeformer_upscale_faces:
|
||||||
|
if "realesrgan" not in models_to_reload and "realesrgan" not in context.models:
|
||||||
|
default_realesrgan = DEFAULT_MODELS["realesrgan"][0]["file_name"]
|
||||||
|
models_to_reload["realesrgan"] = resolve_model_to_use(default_realesrgan, "realesrgan")
|
||||||
|
elif "realesrgan" in models_to_reload and models_to_reload["realesrgan"] is None:
|
||||||
|
del models_to_reload["realesrgan"] # don't unload realesrgan
|
||||||
|
|
||||||
|
if set_vram_optimizations(context) or set_clip_skip(context, task_data): # reload SD
|
||||||
models_to_reload["stable-diffusion"] = model_paths_in_req["stable-diffusion"]
|
models_to_reload["stable-diffusion"] = model_paths_in_req["stable-diffusion"]
|
||||||
|
|
||||||
for model_type, model_path_in_req in models_to_reload.items():
|
for model_type, model_path_in_req in models_to_reload.items():
|
||||||
context.model_paths[model_type] = model_path_in_req
|
context.model_paths[model_type] = model_path_in_req
|
||||||
|
|
||||||
action_fn = unload_model if context.model_paths[model_type] is None else load_model
|
action_fn = unload_model if context.model_paths[model_type] is None else load_model
|
||||||
|
try:
|
||||||
action_fn(context, model_type, scan_model=False) # we've scanned them already
|
action_fn(context, model_type, scan_model=False) # we've scanned them already
|
||||||
|
if model_type in context.model_load_errors:
|
||||||
|
del context.model_load_errors[model_type]
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
if action_fn == load_model:
|
||||||
|
context.model_load_errors[model_type] = str(e) # storing the entire Exception can lead to memory leaks
|
||||||
|
|
||||||
|
|
||||||
def resolve_model_paths(task_data: TaskData):
|
def resolve_model_paths(task_data: TaskData):
|
||||||
@ -130,11 +181,49 @@ def resolve_model_paths(task_data: TaskData):
|
|||||||
task_data.use_lora_model = resolve_model_to_use(task_data.use_lora_model, model_type="lora")
|
task_data.use_lora_model = resolve_model_to_use(task_data.use_lora_model, model_type="lora")
|
||||||
|
|
||||||
if task_data.use_face_correction:
|
if task_data.use_face_correction:
|
||||||
task_data.use_face_correction = resolve_model_to_use(task_data.use_face_correction, "gfpgan")
|
if "gfpgan" in task_data.use_face_correction.lower():
|
||||||
if task_data.use_upscale:
|
model_type = "gfpgan"
|
||||||
|
elif "codeformer" in task_data.use_face_correction.lower():
|
||||||
|
model_type = "codeformer"
|
||||||
|
download_if_necessary("codeformer", "codeformer.pth", "codeformer-0.1.0")
|
||||||
|
|
||||||
|
task_data.use_face_correction = resolve_model_to_use(task_data.use_face_correction, model_type)
|
||||||
|
if task_data.use_upscale and "realesrgan" in task_data.use_upscale.lower():
|
||||||
task_data.use_upscale = resolve_model_to_use(task_data.use_upscale, "realesrgan")
|
task_data.use_upscale = resolve_model_to_use(task_data.use_upscale, "realesrgan")
|
||||||
|
|
||||||
|
|
||||||
|
def fail_if_models_did_not_load(context: Context):
|
||||||
|
for model_type in KNOWN_MODEL_TYPES:
|
||||||
|
if model_type in context.model_load_errors:
|
||||||
|
e = context.model_load_errors[model_type]
|
||||||
|
raise Exception(f"Could not load the {model_type} model! Reason: " + e)
|
||||||
|
|
||||||
|
|
||||||
|
def download_default_models_if_necessary():
|
||||||
|
for model_type, models in DEFAULT_MODELS.items():
|
||||||
|
for model in models:
|
||||||
|
try:
|
||||||
|
download_if_necessary(model_type, model["file_name"], model["model_id"])
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
app.fail_and_die(fail_type="model_download", data=model_type)
|
||||||
|
|
||||||
|
print(model_type, "model(s) found.")
|
||||||
|
|
||||||
|
|
||||||
|
def download_if_necessary(model_type: str, file_name: str, model_id: str):
|
||||||
|
model_path = os.path.join(app.MODELS_DIR, model_type, file_name)
|
||||||
|
expected_hash = get_model_info_from_db(model_type=model_type, model_id=model_id)["quick_hash"]
|
||||||
|
|
||||||
|
other_models_exist = any_model_exists(model_type)
|
||||||
|
known_model_exists = os.path.exists(model_path)
|
||||||
|
known_model_is_corrupt = known_model_exists and hash_file_quick(model_path) != expected_hash
|
||||||
|
|
||||||
|
if known_model_is_corrupt or (not other_models_exist and not known_model_exists):
|
||||||
|
print("> download", model_type, model_id)
|
||||||
|
download_model(model_type, model_id, download_base_dir=app.MODELS_DIR)
|
||||||
|
|
||||||
|
|
||||||
def set_vram_optimizations(context: Context):
|
def set_vram_optimizations(context: Context):
|
||||||
config = app.getConfig()
|
config = app.getConfig()
|
||||||
vram_usage_level = config.get("vram_usage_level", "balanced")
|
vram_usage_level = config.get("vram_usage_level", "balanced")
|
||||||
@ -146,6 +235,36 @@ def set_vram_optimizations(context: Context):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_legacy_model_location():
|
||||||
|
'Move the models inside the legacy "stable-diffusion" folder, to their respective folders'
|
||||||
|
|
||||||
|
for model_type, models in DEFAULT_MODELS.items():
|
||||||
|
for model in models:
|
||||||
|
file_name = model["file_name"]
|
||||||
|
legacy_path = os.path.join(app.SD_DIR, file_name)
|
||||||
|
if os.path.exists(legacy_path):
|
||||||
|
shutil.move(legacy_path, os.path.join(app.MODELS_DIR, model_type, file_name))
|
||||||
|
|
||||||
|
|
||||||
|
def any_model_exists(model_type: str) -> bool:
|
||||||
|
extensions = MODEL_EXTENSIONS.get(model_type, [])
|
||||||
|
for ext in extensions:
|
||||||
|
if any(glob(f"{app.MODELS_DIR}/{model_type}/**/*{ext}", recursive=True)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def set_clip_skip(context: Context, task_data: TaskData):
|
||||||
|
clip_skip = task_data.clip_skip
|
||||||
|
|
||||||
|
if clip_skip != context.clip_skip:
|
||||||
|
context.clip_skip = clip_skip
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def make_model_folders():
|
def make_model_folders():
|
||||||
for model_type in KNOWN_MODEL_TYPES:
|
for model_type in KNOWN_MODEL_TYPES:
|
||||||
model_dir_path = os.path.join(app.MODELS_DIR, model_type)
|
model_dir_path = os.path.join(app.MODELS_DIR, model_type)
|
||||||
@ -167,13 +286,23 @@ def is_malicious_model(file_path):
|
|||||||
if scan_result.issues_count > 0 or scan_result.infected_files > 0:
|
if scan_result.issues_count > 0 or scan_result.infected_files > 0:
|
||||||
log.warn(
|
log.warn(
|
||||||
":warning: [bold red]Scan %s: %d scanned, %d issue, %d infected.[/bold red]"
|
":warning: [bold red]Scan %s: %d scanned, %d issue, %d infected.[/bold red]"
|
||||||
% (file_path, scan_result.scanned_files, scan_result.issues_count, scan_result.infected_files)
|
% (
|
||||||
|
file_path,
|
||||||
|
scan_result.scanned_files,
|
||||||
|
scan_result.issues_count,
|
||||||
|
scan_result.infected_files,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
log.debug(
|
log.debug(
|
||||||
"Scan %s: [green]%d scanned, %d issue, %d infected.[/green]"
|
"Scan %s: [green]%d scanned, %d issue, %d infected.[/green]"
|
||||||
% (file_path, scan_result.scanned_files, scan_result.issues_count, scan_result.infected_files)
|
% (
|
||||||
|
file_path,
|
||||||
|
scan_result.scanned_files,
|
||||||
|
scan_result.issues_count,
|
||||||
|
scan_result.infected_files,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -183,17 +312,12 @@ def is_malicious_model(file_path):
|
|||||||
|
|
||||||
def getModels():
|
def getModels():
|
||||||
models = {
|
models = {
|
||||||
"active": {
|
|
||||||
"stable-diffusion": "sd-v1-4",
|
|
||||||
"vae": "",
|
|
||||||
"hypernetwork": "",
|
|
||||||
"lora": "",
|
|
||||||
},
|
|
||||||
"options": {
|
"options": {
|
||||||
"stable-diffusion": ["sd-v1-4"],
|
"stable-diffusion": ["sd-v1-4"],
|
||||||
"vae": [],
|
"vae": [],
|
||||||
"hypernetwork": [],
|
"hypernetwork": [],
|
||||||
"lora": [],
|
"lora": [],
|
||||||
|
"codeformer": ["codeformer"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,13 +325,13 @@ def getModels():
|
|||||||
|
|
||||||
class MaliciousModelException(Exception):
|
class MaliciousModelException(Exception):
|
||||||
"Raised when picklescan reports a problem with a model"
|
"Raised when picklescan reports a problem with a model"
|
||||||
pass
|
|
||||||
|
|
||||||
def scan_directory(directory, suffixes, directoriesFirst: bool = True):
|
def scan_directory(directory, suffixes, directoriesFirst: bool = True):
|
||||||
nonlocal models_scanned
|
nonlocal models_scanned
|
||||||
tree = []
|
tree = []
|
||||||
for entry in sorted(
|
for entry in sorted(
|
||||||
os.scandir(directory), key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower())
|
os.scandir(directory),
|
||||||
|
key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower()),
|
||||||
):
|
):
|
||||||
if entry.is_file():
|
if entry.is_file():
|
||||||
matching_suffix = list(filter(lambda s: entry.name.endswith(s), suffixes))
|
matching_suffix = list(filter(lambda s: entry.name.endswith(s), suffixes))
|
||||||
@ -243,6 +367,7 @@ def getModels():
|
|||||||
except MaliciousModelException as e:
|
except MaliciousModelException as e:
|
||||||
models["scan-error"] = e
|
models["scan-error"] = e
|
||||||
|
|
||||||
|
log.info(f"[green]Scanning all model folders for models...[/]")
|
||||||
# custom models
|
# custom models
|
||||||
listModels(model_type="stable-diffusion")
|
listModels(model_type="stable-diffusion")
|
||||||
listModels(model_type="vae")
|
listModels(model_type="vae")
|
||||||
@ -253,9 +378,4 @@ def getModels():
|
|||||||
if models_scanned > 0:
|
if models_scanned > 0:
|
||||||
log.info(f"[green]Scanned {models_scanned} models. Nothing infected[/]")
|
log.info(f"[green]Scanned {models_scanned} models. Nothing infected[/]")
|
||||||
|
|
||||||
# legacy
|
|
||||||
custom_weight_path = os.path.join(app.SD_DIR, "custom-model.ckpt")
|
|
||||||
if os.path.exists(custom_weight_path):
|
|
||||||
models["options"]["stable-diffusion"].append("custom-model")
|
|
||||||
|
|
||||||
return models
|
return models
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
import queue
|
|
||||||
import time
|
|
||||||
import json
|
import json
|
||||||
import pprint
|
import pprint
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
|
||||||
from easydiffusion import device_manager
|
from easydiffusion import device_manager
|
||||||
from easydiffusion.types import TaskData, Response, Image as ResponseImage, UserInitiatedStop, GenerateImageRequest
|
from easydiffusion.types import GenerateImageRequest
|
||||||
from easydiffusion.utils import get_printable_request, save_images_to_disk, log
|
from easydiffusion.types import Image as ResponseImage
|
||||||
|
from easydiffusion.types import Response, TaskData, UserInitiatedStop
|
||||||
|
from easydiffusion.model_manager import DEFAULT_MODELS, resolve_model_to_use
|
||||||
|
from easydiffusion.utils import get_printable_request, log, save_images_to_disk
|
||||||
from sdkit import Context
|
from sdkit import Context
|
||||||
from sdkit.generate import generate_images
|
|
||||||
from sdkit.filter import apply_filters
|
from sdkit.filter import apply_filters
|
||||||
from sdkit.utils import img_to_buffer, img_to_base64_str, latent_samples_to_images, diffusers_latent_samples_to_images
|
from sdkit.generate import generate_images
|
||||||
|
from sdkit.models import load_model
|
||||||
|
from sdkit.utils import (
|
||||||
|
diffusers_latent_samples_to_images,
|
||||||
|
gc,
|
||||||
|
img_to_base64_str,
|
||||||
|
img_to_buffer,
|
||||||
|
latent_samples_to_images,
|
||||||
|
get_device_usage,
|
||||||
|
)
|
||||||
|
|
||||||
context = Context() # thread-local
|
context = Context() # thread-local
|
||||||
"""
|
"""
|
||||||
@ -25,24 +35,39 @@ def init(device):
|
|||||||
context.stop_processing = False
|
context.stop_processing = False
|
||||||
context.temp_images = {}
|
context.temp_images = {}
|
||||||
context.partial_x_samples = None
|
context.partial_x_samples = None
|
||||||
|
context.model_load_errors = {}
|
||||||
|
context.enable_codeformer = True
|
||||||
|
|
||||||
from easydiffusion import app
|
from easydiffusion import app
|
||||||
|
|
||||||
app_config = app.getConfig()
|
app_config = app.getConfig()
|
||||||
context.test_diffusers = app_config.get("test_diffusers", False)
|
context.test_diffusers = (
|
||||||
|
app_config.get("test_diffusers", False) and app_config.get("update_branch", "main") != "main"
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info("Device usage during initialization:")
|
||||||
|
get_device_usage(device, log_info=True, process_usage_only=False)
|
||||||
|
|
||||||
device_manager.device_init(context, device)
|
device_manager.device_init(context, device)
|
||||||
|
|
||||||
|
|
||||||
def make_images(
|
def make_images(
|
||||||
req: GenerateImageRequest, task_data: TaskData, data_queue: queue.Queue, task_temp_images: list, step_callback
|
req: GenerateImageRequest,
|
||||||
|
task_data: TaskData,
|
||||||
|
data_queue: queue.Queue,
|
||||||
|
task_temp_images: list,
|
||||||
|
step_callback,
|
||||||
):
|
):
|
||||||
context.stop_processing = False
|
context.stop_processing = False
|
||||||
print_task_info(req, task_data)
|
print_task_info(req, task_data)
|
||||||
|
|
||||||
images, seeds = make_images_internal(req, task_data, data_queue, task_temp_images, step_callback)
|
images, seeds = make_images_internal(req, task_data, data_queue, task_temp_images, step_callback)
|
||||||
|
|
||||||
res = Response(req, task_data, images=construct_response(images, seeds, task_data, base_seed=req.seed))
|
res = Response(
|
||||||
|
req,
|
||||||
|
task_data,
|
||||||
|
images=construct_response(images, seeds, task_data, base_seed=req.seed),
|
||||||
|
)
|
||||||
res = res.json()
|
res = res.json()
|
||||||
data_queue.put(json.dumps(res))
|
data_queue.put(json.dumps(res))
|
||||||
log.info("Task completed")
|
log.info("Task completed")
|
||||||
@ -51,16 +76,19 @@ def make_images(
|
|||||||
|
|
||||||
|
|
||||||
def print_task_info(req: GenerateImageRequest, task_data: TaskData):
|
def print_task_info(req: GenerateImageRequest, task_data: TaskData):
|
||||||
req_str = pprint.pformat(get_printable_request(req)).replace("[", "\[")
|
req_str = pprint.pformat(get_printable_request(req, task_data)).replace("[", "\[")
|
||||||
task_str = pprint.pformat(task_data.dict()).replace("[", "\[")
|
task_str = pprint.pformat(task_data.dict()).replace("[", "\[")
|
||||||
log.info(f"request: {req_str}")
|
log.info(f"request: {req_str}")
|
||||||
log.info(f"task data: {task_str}")
|
log.info(f"task data: {task_str}")
|
||||||
|
|
||||||
|
|
||||||
def make_images_internal(
|
def make_images_internal(
|
||||||
req: GenerateImageRequest, task_data: TaskData, data_queue: queue.Queue, task_temp_images: list, step_callback
|
req: GenerateImageRequest,
|
||||||
|
task_data: TaskData,
|
||||||
|
data_queue: queue.Queue,
|
||||||
|
task_temp_images: list,
|
||||||
|
step_callback,
|
||||||
):
|
):
|
||||||
|
|
||||||
images, user_stopped = generate_images_internal(
|
images, user_stopped = generate_images_internal(
|
||||||
req,
|
req,
|
||||||
task_data,
|
task_data,
|
||||||
@ -70,7 +98,8 @@ def make_images_internal(
|
|||||||
task_data.stream_image_progress,
|
task_data.stream_image_progress,
|
||||||
task_data.stream_image_progress_interval,
|
task_data.stream_image_progress_interval,
|
||||||
)
|
)
|
||||||
filtered_images = filter_images(task_data, images, user_stopped)
|
gc(context)
|
||||||
|
filtered_images = filter_images(req, task_data, images, user_stopped)
|
||||||
|
|
||||||
if task_data.save_to_disk_path is not None:
|
if task_data.save_to_disk_path is not None:
|
||||||
save_images_to_disk(images, filtered_images, req, task_data)
|
save_images_to_disk(images, filtered_images, req, task_data)
|
||||||
@ -126,28 +155,66 @@ def generate_images_internal(
|
|||||||
return images, user_stopped
|
return images, user_stopped
|
||||||
|
|
||||||
|
|
||||||
def filter_images(task_data: TaskData, images: list, user_stopped):
|
def filter_images(req: GenerateImageRequest, task_data: TaskData, images: list, user_stopped):
|
||||||
if user_stopped:
|
if user_stopped:
|
||||||
return images
|
return images
|
||||||
|
|
||||||
filters_to_apply = []
|
|
||||||
if task_data.block_nsfw:
|
if task_data.block_nsfw:
|
||||||
filters_to_apply.append("nsfw_checker")
|
images = apply_filters(context, "nsfw_checker", images)
|
||||||
if task_data.use_face_correction and "gfpgan" in task_data.use_face_correction.lower():
|
|
||||||
filters_to_apply.append("gfpgan")
|
if task_data.use_face_correction and "codeformer" in task_data.use_face_correction.lower():
|
||||||
if task_data.use_upscale and "realesrgan" in task_data.use_upscale.lower():
|
default_realesrgan = DEFAULT_MODELS["realesrgan"][0]["file_name"]
|
||||||
filters_to_apply.append("realesrgan")
|
prev_realesrgan_path = None
|
||||||
|
if task_data.codeformer_upscale_faces and default_realesrgan not in context.model_paths["realesrgan"]:
|
||||||
|
prev_realesrgan_path = context.model_paths["realesrgan"]
|
||||||
|
context.model_paths["realesrgan"] = resolve_model_to_use(default_realesrgan, "realesrgan")
|
||||||
|
load_model(context, "realesrgan")
|
||||||
|
|
||||||
|
try:
|
||||||
|
images = apply_filters(
|
||||||
|
context,
|
||||||
|
"codeformer",
|
||||||
|
images,
|
||||||
|
upscale_faces=task_data.codeformer_upscale_faces,
|
||||||
|
codeformer_fidelity=task_data.codeformer_fidelity,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if prev_realesrgan_path:
|
||||||
|
context.model_paths["realesrgan"] = prev_realesrgan_path
|
||||||
|
load_model(context, "realesrgan")
|
||||||
|
elif task_data.use_face_correction and "gfpgan" in task_data.use_face_correction.lower():
|
||||||
|
images = apply_filters(context, "gfpgan", images)
|
||||||
|
|
||||||
|
if task_data.use_upscale:
|
||||||
|
if "realesrgan" in task_data.use_upscale.lower():
|
||||||
|
images = apply_filters(context, "realesrgan", images, scale=task_data.upscale_amount)
|
||||||
|
elif task_data.use_upscale == "latent_upscaler":
|
||||||
|
images = apply_filters(
|
||||||
|
context,
|
||||||
|
"latent_upscaler",
|
||||||
|
images,
|
||||||
|
scale=task_data.upscale_amount,
|
||||||
|
latent_upscaler_options={
|
||||||
|
"prompt": req.prompt,
|
||||||
|
"negative_prompt": req.negative_prompt,
|
||||||
|
"seed": req.seed,
|
||||||
|
"num_inference_steps": task_data.latent_upscaler_steps,
|
||||||
|
"guidance_scale": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if len(filters_to_apply) == 0:
|
|
||||||
return images
|
return images
|
||||||
|
|
||||||
return apply_filters(context, filters_to_apply, images, scale=task_data.upscale_amount)
|
|
||||||
|
|
||||||
|
|
||||||
def construct_response(images: list, seeds: list, task_data: TaskData, base_seed: int):
|
def construct_response(images: list, seeds: list, task_data: TaskData, base_seed: int):
|
||||||
return [
|
return [
|
||||||
ResponseImage(
|
ResponseImage(
|
||||||
data=img_to_base64_str(img, task_data.output_format, task_data.output_quality, task_data.output_lossless),
|
data=img_to_base64_str(
|
||||||
|
img,
|
||||||
|
task_data.output_format,
|
||||||
|
task_data.output_quality,
|
||||||
|
task_data.output_lossless,
|
||||||
|
),
|
||||||
seed=seed,
|
seed=seed,
|
||||||
)
|
)
|
||||||
for img, seed in zip(images, seeds)
|
for img, seed in zip(images, seeds)
|
||||||
|
@ -2,28 +2,31 @@
|
|||||||
Notes:
|
Notes:
|
||||||
async endpoints always run on the main thread. Without they run on the thread pool.
|
async endpoints always run on the main thread. Without they run on the thread pool.
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
import datetime
|
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
||||||
|
from easydiffusion import app, model_manager, task_manager
|
||||||
|
from easydiffusion.types import GenerateImageRequest, MergeRequest, TaskData
|
||||||
|
from easydiffusion.utils import log
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from pydantic import BaseModel, Extra
|
||||||
from starlette.responses import FileResponse, JSONResponse, StreamingResponse
|
from starlette.responses import FileResponse, JSONResponse, StreamingResponse
|
||||||
from pydantic import BaseModel
|
from pycloudflared import try_cloudflare
|
||||||
|
|
||||||
from easydiffusion import app, model_manager, task_manager
|
|
||||||
from easydiffusion.types import TaskData, GenerateImageRequest, MergeRequest
|
|
||||||
from easydiffusion.utils import log
|
|
||||||
|
|
||||||
import mimetypes
|
|
||||||
|
|
||||||
log.info(f"started in {app.SD_DIR}")
|
log.info(f"started in {app.SD_DIR}")
|
||||||
log.info(f"started at {datetime.datetime.now():%x %X}")
|
log.info(f"started at {datetime.datetime.now():%x %X}")
|
||||||
|
|
||||||
server_api = FastAPI()
|
server_api = FastAPI()
|
||||||
|
|
||||||
NOCACHE_HEADERS = {"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
NOCACHE_HEADERS = {
|
||||||
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
"Expires": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class NoCacheStaticFiles(StaticFiles):
|
class NoCacheStaticFiles(StaticFiles):
|
||||||
@ -44,7 +47,7 @@ class NoCacheStaticFiles(StaticFiles):
|
|||||||
return super().is_not_modified(response_headers, request_headers)
|
return super().is_not_modified(response_headers, request_headers)
|
||||||
|
|
||||||
|
|
||||||
class SetAppConfigRequest(BaseModel):
|
class SetAppConfigRequest(BaseModel, extra=Extra.allow):
|
||||||
update_branch: str = None
|
update_branch: str = None
|
||||||
render_devices: Union[List[str], List[int], str, int] = None
|
render_devices: Union[List[str], List[int], str, int] = None
|
||||||
model_vae: str = None
|
model_vae: str = None
|
||||||
@ -65,11 +68,17 @@ def init():
|
|||||||
name="custom-thumbnails",
|
name="custom-thumbnails",
|
||||||
)
|
)
|
||||||
|
|
||||||
server_api.mount("/media", NoCacheStaticFiles(directory=os.path.join(app.SD_UI_DIR, "media")), name="media")
|
server_api.mount(
|
||||||
|
"/media",
|
||||||
|
NoCacheStaticFiles(directory=os.path.join(app.SD_UI_DIR, "media")),
|
||||||
|
name="media",
|
||||||
|
)
|
||||||
|
|
||||||
for plugins_dir, dir_prefix in app.UI_PLUGINS_SOURCES:
|
for plugins_dir, dir_prefix in app.UI_PLUGINS_SOURCES:
|
||||||
server_api.mount(
|
server_api.mount(
|
||||||
f"/plugins/{dir_prefix}", NoCacheStaticFiles(directory=plugins_dir), name=f"plugins-{dir_prefix}"
|
f"/plugins/{dir_prefix}",
|
||||||
|
NoCacheStaticFiles(directory=plugins_dir),
|
||||||
|
name=f"plugins-{dir_prefix}",
|
||||||
)
|
)
|
||||||
|
|
||||||
@server_api.post("/app_config")
|
@server_api.post("/app_config")
|
||||||
@ -105,6 +114,14 @@ def init():
|
|||||||
def get_image(task_id: int, img_id: int):
|
def get_image(task_id: int, img_id: int):
|
||||||
return get_image_internal(task_id, img_id)
|
return get_image_internal(task_id, img_id)
|
||||||
|
|
||||||
|
@server_api.post("/tunnel/cloudflare/start")
|
||||||
|
def start_cloudflare_tunnel(req: dict):
|
||||||
|
return start_cloudflare_tunnel_internal(req)
|
||||||
|
|
||||||
|
@server_api.post("/tunnel/cloudflare/stop")
|
||||||
|
def stop_cloudflare_tunnel(req: dict):
|
||||||
|
return stop_cloudflare_tunnel_internal(req)
|
||||||
|
|
||||||
@server_api.get("/")
|
@server_api.get("/")
|
||||||
def read_root():
|
def read_root():
|
||||||
return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS)
|
return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS)
|
||||||
@ -136,6 +153,10 @@ def set_app_config_internal(req: SetAppConfigRequest):
|
|||||||
|
|
||||||
config["test_diffusers"] = req.test_diffusers
|
config["test_diffusers"] = req.test_diffusers
|
||||||
|
|
||||||
|
for property, property_value in req.dict().items():
|
||||||
|
if property_value is not None and property not in req.__fields__:
|
||||||
|
config[property] = property_value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app.setConfig(config)
|
app.setConfig(config)
|
||||||
|
|
||||||
@ -199,6 +220,8 @@ def ping_internal(session_id: str = None):
|
|||||||
session = task_manager.get_cached_session(session_id, update_ttl=True)
|
session = task_manager.get_cached_session(session_id, update_ttl=True)
|
||||||
response["tasks"] = {id(t): t.status for t in session.tasks}
|
response["tasks"] = {id(t): t.status for t in session.tasks}
|
||||||
response["devices"] = task_manager.get_devices()
|
response["devices"] = task_manager.get_devices()
|
||||||
|
if cloudflare.address != None:
|
||||||
|
response["cloudflare"] = cloudflare.address
|
||||||
return JSONResponse(response, headers=NOCACHE_HEADERS)
|
return JSONResponse(response, headers=NOCACHE_HEADERS)
|
||||||
|
|
||||||
|
|
||||||
@ -242,8 +265,8 @@ def render_internal(req: dict):
|
|||||||
|
|
||||||
def model_merge_internal(req: dict):
|
def model_merge_internal(req: dict):
|
||||||
try:
|
try:
|
||||||
from sdkit.train import merge_models
|
|
||||||
from easydiffusion.utils.save_utils import filename_regex
|
from easydiffusion.utils.save_utils import filename_regex
|
||||||
|
from sdkit.train import merge_models
|
||||||
|
|
||||||
mergeReq: MergeRequest = MergeRequest.parse_obj(req)
|
mergeReq: MergeRequest = MergeRequest.parse_obj(req)
|
||||||
|
|
||||||
@ -251,7 +274,11 @@ def model_merge_internal(req: dict):
|
|||||||
model_manager.resolve_model_to_use(mergeReq.model0, "stable-diffusion"),
|
model_manager.resolve_model_to_use(mergeReq.model0, "stable-diffusion"),
|
||||||
model_manager.resolve_model_to_use(mergeReq.model1, "stable-diffusion"),
|
model_manager.resolve_model_to_use(mergeReq.model1, "stable-diffusion"),
|
||||||
mergeReq.ratio,
|
mergeReq.ratio,
|
||||||
os.path.join(app.MODELS_DIR, "stable-diffusion", filename_regex.sub("_", mergeReq.out_path)),
|
os.path.join(
|
||||||
|
app.MODELS_DIR,
|
||||||
|
"stable-diffusion",
|
||||||
|
filename_regex.sub("_", mergeReq.out_path),
|
||||||
|
),
|
||||||
mergeReq.use_fp16,
|
mergeReq.use_fp16,
|
||||||
)
|
)
|
||||||
return JSONResponse({"status": "OK"}, headers=NOCACHE_HEADERS)
|
return JSONResponse({"status": "OK"}, headers=NOCACHE_HEADERS)
|
||||||
@ -306,3 +333,47 @@ def get_image_internal(task_id: int, img_id: int):
|
|||||||
return StreamingResponse(img_data, media_type="image/jpeg")
|
return StreamingResponse(img_data, media_type="image/jpeg")
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
#---- Cloudflare Tunnel ----
|
||||||
|
class CloudflareTunnel:
|
||||||
|
def __init__(self):
|
||||||
|
config = app.getConfig()
|
||||||
|
self.urls = None
|
||||||
|
self.port = config.get("net", {}).get("listen_port")
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
if self.port:
|
||||||
|
self.urls = try_cloudflare(self.port)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.urls:
|
||||||
|
try_cloudflare.terminate(self.port)
|
||||||
|
self.urls = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def address(self):
|
||||||
|
if self.urls:
|
||||||
|
return self.urls.tunnel
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cloudflare = CloudflareTunnel()
|
||||||
|
|
||||||
|
def start_cloudflare_tunnel_internal(req: dict):
|
||||||
|
try:
|
||||||
|
cloudflare.start()
|
||||||
|
log.info(f"- Started cloudflare tunnel. Using address: {cloudflare.address}")
|
||||||
|
return JSONResponse({"address":cloudflare.address})
|
||||||
|
except Exception as e:
|
||||||
|
log.error(str(e))
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
return HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
def stop_cloudflare_tunnel_internal(req: dict):
|
||||||
|
try:
|
||||||
|
cloudflare.stop()
|
||||||
|
except Exception as e:
|
||||||
|
log.error(str(e))
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
return HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@ -7,16 +7,18 @@ Notes:
|
|||||||
import json
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
TASK_TTL = 15 * 60 # seconds, Discard last session's task timeout
|
TASK_TTL = 30 * 60 # seconds, Discard last session's task timeout
|
||||||
|
|
||||||
import torch
|
import queue
|
||||||
import queue, threading, time, weakref
|
import threading
|
||||||
|
import time
|
||||||
|
import weakref
|
||||||
from typing import Any, Hashable
|
from typing import Any, Hashable
|
||||||
|
|
||||||
|
import torch
|
||||||
from easydiffusion import device_manager
|
from easydiffusion import device_manager
|
||||||
from easydiffusion.types import TaskData, GenerateImageRequest
|
from easydiffusion.types import GenerateImageRequest, TaskData
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
|
||||||
from sdkit.utils import gc
|
from sdkit.utils import gc
|
||||||
|
|
||||||
THREAD_NAME_PREFIX = ""
|
THREAD_NAME_PREFIX = ""
|
||||||
@ -167,7 +169,7 @@ class DataCache:
|
|||||||
raise Exception("DataCache.put" + ERR_LOCK_FAILED)
|
raise Exception("DataCache.put" + ERR_LOCK_FAILED)
|
||||||
try:
|
try:
|
||||||
self._base[key] = (self._get_ttl_time(ttl), value)
|
self._base[key] = (self._get_ttl_time(ttl), value)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -264,7 +266,7 @@ def thread_get_next_task():
|
|||||||
def thread_render(device):
|
def thread_render(device):
|
||||||
global current_state, current_state_error
|
global current_state, current_state_error
|
||||||
|
|
||||||
from easydiffusion import renderer, model_manager
|
from easydiffusion import model_manager, renderer
|
||||||
|
|
||||||
try:
|
try:
|
||||||
renderer.init(device)
|
renderer.init(device)
|
||||||
@ -317,6 +319,9 @@ def thread_render(device):
|
|||||||
def step_callback():
|
def step_callback():
|
||||||
global current_state_error
|
global current_state_error
|
||||||
|
|
||||||
|
task_cache.keep(id(task), TASK_TTL)
|
||||||
|
session_cache.keep(task.task_data.session_id, TASK_TTL)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isinstance(current_state_error, SystemExit)
|
isinstance(current_state_error, SystemExit)
|
||||||
or isinstance(current_state_error, StopAsyncIteration)
|
or isinstance(current_state_error, StopAsyncIteration)
|
||||||
@ -331,10 +336,15 @@ def thread_render(device):
|
|||||||
current_state = ServerStates.LoadingModel
|
current_state = ServerStates.LoadingModel
|
||||||
model_manager.resolve_model_paths(task.task_data)
|
model_manager.resolve_model_paths(task.task_data)
|
||||||
model_manager.reload_models_if_necessary(renderer.context, task.task_data)
|
model_manager.reload_models_if_necessary(renderer.context, task.task_data)
|
||||||
|
model_manager.fail_if_models_did_not_load(renderer.context)
|
||||||
|
|
||||||
current_state = ServerStates.Rendering
|
current_state = ServerStates.Rendering
|
||||||
task.response = renderer.make_images(
|
task.response = renderer.make_images(
|
||||||
task.render_request, task.task_data, task.buffer_queue, task.temp_images, step_callback
|
task.render_request,
|
||||||
|
task.task_data,
|
||||||
|
task.buffer_queue,
|
||||||
|
task.temp_images,
|
||||||
|
step_callback,
|
||||||
)
|
)
|
||||||
# Before looping back to the generator, mark cache as still alive.
|
# Before looping back to the generator, mark cache as still alive.
|
||||||
task_cache.keep(id(task), TASK_TTL)
|
task_cache.keep(id(task), TASK_TTL)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class GenerateImageRequest(BaseModel):
|
class GenerateImageRequest(BaseModel):
|
||||||
prompt: str = ""
|
prompt: str = ""
|
||||||
@ -22,6 +23,7 @@ class GenerateImageRequest(BaseModel):
|
|||||||
sampler_name: str = None # "ddim", "plms", "heun", "euler", "euler_a", "dpm2", "dpm2_a", "lms"
|
sampler_name: str = None # "ddim", "plms", "heun", "euler", "euler_a", "dpm2", "dpm2_a", "lms"
|
||||||
hypernetwork_strength: float = 0
|
hypernetwork_strength: float = 0
|
||||||
lora_alpha: float = 0
|
lora_alpha: float = 0
|
||||||
|
tiling: str = "none" # "none", "x", "y", "xy"
|
||||||
|
|
||||||
|
|
||||||
class TaskData(BaseModel):
|
class TaskData(BaseModel):
|
||||||
@ -31,8 +33,9 @@ class TaskData(BaseModel):
|
|||||||
vram_usage_level: str = "balanced" # or "low" or "medium"
|
vram_usage_level: str = "balanced" # or "low" or "medium"
|
||||||
|
|
||||||
use_face_correction: str = None # or "GFPGANv1.3"
|
use_face_correction: str = None # or "GFPGANv1.3"
|
||||||
use_upscale: str = None # or "RealESRGAN_x4plus" or "RealESRGAN_x4plus_anime_6B"
|
use_upscale: str = None # or "RealESRGAN_x4plus" or "RealESRGAN_x4plus_anime_6B" or "latent_upscaler"
|
||||||
upscale_amount: int = 4 # or 2
|
upscale_amount: int = 4 # or 2
|
||||||
|
latent_upscaler_steps: int = 10
|
||||||
use_stable_diffusion_model: str = "sd-v1-4"
|
use_stable_diffusion_model: str = "sd-v1-4"
|
||||||
# use_stable_diffusion_config: str = "v1-inference"
|
# use_stable_diffusion_config: str = "v1-inference"
|
||||||
use_vae_model: str = None
|
use_vae_model: str = None
|
||||||
@ -47,6 +50,9 @@ class TaskData(BaseModel):
|
|||||||
metadata_output_format: str = "txt" # or "json"
|
metadata_output_format: str = "txt" # or "json"
|
||||||
stream_image_progress: bool = False
|
stream_image_progress: bool = False
|
||||||
stream_image_progress_interval: int = 5
|
stream_image_progress_interval: int = 5
|
||||||
|
clip_skip: bool = False
|
||||||
|
codeformer_upscale_faces: bool = False
|
||||||
|
codeformer_fidelity: float = 0.5
|
||||||
|
|
||||||
|
|
||||||
class MergeRequest(BaseModel):
|
class MergeRequest(BaseModel):
|
||||||
|
@ -1,42 +1,130 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from easydiffusion.types import TaskData, GenerateImageRequest
|
from easydiffusion import app
|
||||||
|
from easydiffusion.types import GenerateImageRequest, TaskData
|
||||||
from sdkit.utils import save_images, save_dicts
|
|
||||||
from numpy import base_repr
|
from numpy import base_repr
|
||||||
|
from sdkit.utils import save_dicts, save_images
|
||||||
|
|
||||||
filename_regex = re.compile("[^a-zA-Z0-9._-]")
|
filename_regex = re.compile("[^a-zA-Z0-9._-]")
|
||||||
|
img_number_regex = re.compile("([0-9]{5,})")
|
||||||
|
|
||||||
# keep in sync with `ui/media/js/dnd.js`
|
# keep in sync with `ui/media/js/dnd.js`
|
||||||
TASK_TEXT_MAPPING = {
|
TASK_TEXT_MAPPING = {
|
||||||
"prompt": "Prompt",
|
"prompt": "Prompt",
|
||||||
|
"negative_prompt": "Negative Prompt",
|
||||||
|
"seed": "Seed",
|
||||||
|
"use_stable_diffusion_model": "Stable Diffusion model",
|
||||||
|
"clip_skip": "Clip Skip",
|
||||||
|
"use_vae_model": "VAE model",
|
||||||
|
"sampler_name": "Sampler",
|
||||||
"width": "Width",
|
"width": "Width",
|
||||||
"height": "Height",
|
"height": "Height",
|
||||||
"seed": "Seed",
|
|
||||||
"num_inference_steps": "Steps",
|
"num_inference_steps": "Steps",
|
||||||
"guidance_scale": "Guidance Scale",
|
"guidance_scale": "Guidance Scale",
|
||||||
"prompt_strength": "Prompt Strength",
|
"prompt_strength": "Prompt Strength",
|
||||||
|
"use_lora_model": "LoRA model",
|
||||||
|
"lora_alpha": "LoRA Strength",
|
||||||
|
"use_hypernetwork_model": "Hypernetwork model",
|
||||||
|
"hypernetwork_strength": "Hypernetwork Strength",
|
||||||
|
"tiling": "Seamless Tiling",
|
||||||
"use_face_correction": "Use Face Correction",
|
"use_face_correction": "Use Face Correction",
|
||||||
"use_upscale": "Use Upscaling",
|
"use_upscale": "Use Upscaling",
|
||||||
"upscale_amount": "Upscale By",
|
"upscale_amount": "Upscale By",
|
||||||
"sampler_name": "Sampler",
|
"latent_upscaler_steps": "Latent Upscaler Steps"
|
||||||
"negative_prompt": "Negative Prompt",
|
|
||||||
"use_stable_diffusion_model": "Stable Diffusion model",
|
|
||||||
"use_vae_model": "VAE model",
|
|
||||||
"use_hypernetwork_model": "Hypernetwork model",
|
|
||||||
"hypernetwork_strength": "Hypernetwork Strength",
|
|
||||||
"use_lora_model": "LoRA model",
|
|
||||||
# "lora_alpha": "LoRA Strength",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time_placeholders = {
|
||||||
|
"$yyyy": "%Y",
|
||||||
|
"$MM": "%m",
|
||||||
|
"$dd": "%d",
|
||||||
|
"$HH": "%H",
|
||||||
|
"$mm": "%M",
|
||||||
|
"$ss": "%S",
|
||||||
|
}
|
||||||
|
|
||||||
|
other_placeholders = {
|
||||||
|
"$id": lambda req, task_data: filename_regex.sub("_", task_data.session_id),
|
||||||
|
"$p": lambda req, task_data: filename_regex.sub("_", req.prompt)[:50],
|
||||||
|
"$s": lambda req, task_data: str(req.seed),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ImageNumber:
|
||||||
|
_factory = None
|
||||||
|
_evaluated = False
|
||||||
|
|
||||||
|
def __init__(self, factory):
|
||||||
|
self._factory = factory
|
||||||
|
self._evaluated = None
|
||||||
|
|
||||||
|
def __call__(self) -> int:
|
||||||
|
if self._evaluated is None:
|
||||||
|
self._evaluated = self._factory()
|
||||||
|
return self._evaluated
|
||||||
|
|
||||||
|
|
||||||
|
def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskData, now=None):
|
||||||
|
if now is None:
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
for placeholder, time_format in time_placeholders.items():
|
||||||
|
if placeholder in format:
|
||||||
|
format = format.replace(placeholder, datetime.fromtimestamp(now).strftime(time_format))
|
||||||
|
for placeholder, replace_func in other_placeholders.items():
|
||||||
|
if placeholder in format:
|
||||||
|
format = format.replace(placeholder, replace_func(req, task_data))
|
||||||
|
|
||||||
|
return format
|
||||||
|
|
||||||
|
|
||||||
|
def format_folder_name(format: str, req: GenerateImageRequest, task_data: TaskData):
|
||||||
|
format = format_placeholders(format, req, task_data)
|
||||||
|
return filename_regex.sub("_", format)
|
||||||
|
|
||||||
|
|
||||||
|
def format_file_name(
|
||||||
|
format: str,
|
||||||
|
req: GenerateImageRequest,
|
||||||
|
task_data: TaskData,
|
||||||
|
now: float,
|
||||||
|
batch_file_number: int,
|
||||||
|
folder_img_number: ImageNumber,
|
||||||
|
):
|
||||||
|
format = format_placeholders(format, req, task_data, now)
|
||||||
|
|
||||||
|
if "$n" in format:
|
||||||
|
format = format.replace("$n", f"{folder_img_number():05}")
|
||||||
|
|
||||||
|
if "$tsb64" in format:
|
||||||
|
img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(
|
||||||
|
int(batch_file_number), 36
|
||||||
|
) # Base 36 conversion, 0-9, A-Z
|
||||||
|
format = format.replace("$tsb64", img_id)
|
||||||
|
|
||||||
|
if "$ts" in format:
|
||||||
|
format = format.replace("$ts", str(int(now * 1000) + batch_file_number))
|
||||||
|
|
||||||
|
return filename_regex.sub("_", format)
|
||||||
|
|
||||||
|
|
||||||
def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageRequest, task_data: TaskData):
|
def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageRequest, task_data: TaskData):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
save_dir_path = os.path.join(task_data.save_to_disk_path, filename_regex.sub("_", task_data.session_id))
|
app_config = app.getConfig()
|
||||||
|
folder_format = app_config.get("folder_format", "$id")
|
||||||
|
save_dir_path = os.path.join(task_data.save_to_disk_path, format_folder_name(folder_format, req, task_data))
|
||||||
metadata_entries = get_metadata_entries_for_request(req, task_data)
|
metadata_entries = get_metadata_entries_for_request(req, task_data)
|
||||||
make_filename = make_filename_callback(req, now=now)
|
file_number = calculate_img_number(save_dir_path, task_data)
|
||||||
|
make_filename = make_filename_callback(
|
||||||
|
app_config.get("filename_format", "$p_$tsb64"),
|
||||||
|
req,
|
||||||
|
task_data,
|
||||||
|
file_number,
|
||||||
|
now=now,
|
||||||
|
)
|
||||||
|
|
||||||
if task_data.show_only_filtered_image or filtered_images is images:
|
if task_data.show_only_filtered_image or filtered_images is images:
|
||||||
save_images(
|
save_images(
|
||||||
@ -47,16 +135,25 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR
|
|||||||
output_quality=task_data.output_quality,
|
output_quality=task_data.output_quality,
|
||||||
output_lossless=task_data.output_lossless,
|
output_lossless=task_data.output_lossless,
|
||||||
)
|
)
|
||||||
if task_data.metadata_output_format.lower() in ["json", "txt", "embed"]:
|
if task_data.metadata_output_format:
|
||||||
|
for metadata_output_format in task_data.metadata_output_format.split(","):
|
||||||
|
if metadata_output_format.lower() in ["json", "txt", "embed"]:
|
||||||
save_dicts(
|
save_dicts(
|
||||||
metadata_entries,
|
metadata_entries,
|
||||||
save_dir_path,
|
save_dir_path,
|
||||||
file_name=make_filename,
|
file_name=make_filename,
|
||||||
output_format=task_data.metadata_output_format,
|
output_format=metadata_output_format,
|
||||||
file_format=task_data.output_format,
|
file_format=task_data.output_format,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
make_filter_filename = make_filename_callback(req, now=now, suffix="filtered")
|
make_filter_filename = make_filename_callback(
|
||||||
|
app_config.get("filename_format", "$p_$tsb64"),
|
||||||
|
req,
|
||||||
|
task_data,
|
||||||
|
file_number,
|
||||||
|
now=now,
|
||||||
|
suffix="filtered",
|
||||||
|
)
|
||||||
|
|
||||||
save_images(
|
save_images(
|
||||||
images,
|
images,
|
||||||
@ -74,7 +171,9 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR
|
|||||||
output_quality=task_data.output_quality,
|
output_quality=task_data.output_quality,
|
||||||
output_lossless=task_data.output_lossless,
|
output_lossless=task_data.output_lossless,
|
||||||
)
|
)
|
||||||
if task_data.metadata_output_format.lower() in ["json", "txt", "embed"]:
|
if task_data.metadata_output_format:
|
||||||
|
for metadata_output_format in task_data.metadata_output_format.split(","):
|
||||||
|
if metadata_output_format.lower() in ["json", "txt", "embed"]:
|
||||||
save_dicts(
|
save_dicts(
|
||||||
metadata_entries,
|
metadata_entries,
|
||||||
save_dir_path,
|
save_dir_path,
|
||||||
@ -85,33 +184,10 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR
|
|||||||
|
|
||||||
|
|
||||||
def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskData):
|
def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskData):
|
||||||
metadata = get_printable_request(req)
|
metadata = get_printable_request(req, task_data)
|
||||||
metadata.update(
|
|
||||||
{
|
|
||||||
"use_stable_diffusion_model": task_data.use_stable_diffusion_model,
|
|
||||||
"use_vae_model": task_data.use_vae_model,
|
|
||||||
"use_hypernetwork_model": task_data.use_hypernetwork_model,
|
|
||||||
"use_lora_model": task_data.use_lora_model,
|
|
||||||
"use_face_correction": task_data.use_face_correction,
|
|
||||||
"use_upscale": task_data.use_upscale,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if metadata["use_upscale"] is not None:
|
|
||||||
metadata["upscale_amount"] = task_data.upscale_amount
|
|
||||||
if task_data.use_hypernetwork_model is None:
|
|
||||||
del metadata["hypernetwork_strength"]
|
|
||||||
if task_data.use_lora_model is None:
|
|
||||||
if "lora_alpha" in metadata:
|
|
||||||
del metadata["lora_alpha"]
|
|
||||||
|
|
||||||
from easydiffusion import app
|
|
||||||
|
|
||||||
app_config = app.getConfig()
|
|
||||||
if not app_config.get("test_diffusers", False) and "use_lora_model" in metadata:
|
|
||||||
del metadata["use_lora_model"]
|
|
||||||
|
|
||||||
# if text, format it in the text format expected by the UI
|
# if text, format it in the text format expected by the UI
|
||||||
is_txt_format = task_data.metadata_output_format.lower() == "txt"
|
is_txt_format = task_data.metadata_output_format and "txt" in task_data.metadata_output_format.lower().split(",")
|
||||||
if is_txt_format:
|
if is_txt_format:
|
||||||
metadata = {TASK_TEXT_MAPPING[key]: val for key, val in metadata.items() if key in TASK_TEXT_MAPPING}
|
metadata = {TASK_TEXT_MAPPING[key]: val for key, val in metadata.items() if key in TASK_TEXT_MAPPING}
|
||||||
|
|
||||||
@ -122,25 +198,101 @@ def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskD
|
|||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
||||||
def get_printable_request(req: GenerateImageRequest):
|
def get_printable_request(req: GenerateImageRequest, task_data: TaskData):
|
||||||
metadata = req.dict()
|
req_metadata = req.dict()
|
||||||
del metadata["init_image"]
|
task_data_metadata = task_data.dict()
|
||||||
del metadata["init_image_mask"]
|
|
||||||
if req.init_image is None:
|
# Save the metadata in the order defined in TASK_TEXT_MAPPING
|
||||||
|
metadata = {}
|
||||||
|
for key in TASK_TEXT_MAPPING.keys():
|
||||||
|
if key in req_metadata:
|
||||||
|
metadata[key] = req_metadata[key]
|
||||||
|
elif key in task_data_metadata:
|
||||||
|
metadata[key] = task_data_metadata[key]
|
||||||
|
|
||||||
|
# Clean up the metadata
|
||||||
|
if req.init_image is None and "prompt_strength" in metadata:
|
||||||
del metadata["prompt_strength"]
|
del metadata["prompt_strength"]
|
||||||
|
if task_data.use_upscale is None and "upscale_amount" in metadata:
|
||||||
|
del metadata["upscale_amount"]
|
||||||
|
if task_data.use_hypernetwork_model is None and "hypernetwork_strength" in metadata:
|
||||||
|
del metadata["hypernetwork_strength"]
|
||||||
|
if task_data.use_lora_model is None and "lora_alpha" in metadata:
|
||||||
|
del metadata["lora_alpha"]
|
||||||
|
if task_data.use_upscale != "latent_upscaler" and "latent_upscaler_steps" in metadata:
|
||||||
|
del metadata["latent_upscaler_steps"]
|
||||||
|
|
||||||
|
app_config = app.getConfig()
|
||||||
|
if not app_config.get("test_diffusers", False):
|
||||||
|
for key in (x for x in ["use_lora_model", "lora_alpha", "clip_skip", "tiling", "latent_upscaler_steps"] if x in metadata):
|
||||||
|
del metadata[key]
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
def make_filename_callback(req: GenerateImageRequest, suffix=None, now=None):
|
def make_filename_callback(
|
||||||
|
filename_format: str,
|
||||||
|
req: GenerateImageRequest,
|
||||||
|
task_data: TaskData,
|
||||||
|
folder_img_number: int,
|
||||||
|
suffix=None,
|
||||||
|
now=None,
|
||||||
|
):
|
||||||
if now is None:
|
if now is None:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
def make_filename(i):
|
def make_filename(i):
|
||||||
img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(int(i),36) # Base 36 conversion, 0-9, A-Z
|
name = format_file_name(filename_format, req, task_data, now, i, folder_img_number)
|
||||||
|
|
||||||
prompt_flattened = filename_regex.sub("_", req.prompt)[:50]
|
|
||||||
name = f"{prompt_flattened}_{img_id}"
|
|
||||||
name = name if suffix is None else f"{name}_{suffix}"
|
name = name if suffix is None else f"{name}_{suffix}"
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
return make_filename
|
return make_filename
|
||||||
|
|
||||||
|
|
||||||
|
def _calculate_img_number(save_dir_path: str, task_data: TaskData):
|
||||||
|
def get_highest_img_number(accumulator: int, file: os.DirEntry) -> int:
|
||||||
|
if not file.is_file:
|
||||||
|
return accumulator
|
||||||
|
|
||||||
|
if len(list(filter(lambda e: file.name.endswith(e), app.IMAGE_EXTENSIONS))) == 0:
|
||||||
|
return accumulator
|
||||||
|
|
||||||
|
get_highest_img_number.number_of_images = get_highest_img_number.number_of_images + 1
|
||||||
|
|
||||||
|
number_match = img_number_regex.match(file.name)
|
||||||
|
if not number_match:
|
||||||
|
return accumulator
|
||||||
|
|
||||||
|
file_number = number_match.group().lstrip("0")
|
||||||
|
|
||||||
|
# Handle 00000
|
||||||
|
return int(file_number) if file_number else 0
|
||||||
|
|
||||||
|
get_highest_img_number.number_of_images = 0
|
||||||
|
|
||||||
|
highest_file_number = -1
|
||||||
|
|
||||||
|
if os.path.isdir(save_dir_path):
|
||||||
|
existing_files = list(os.scandir(save_dir_path))
|
||||||
|
highest_file_number = reduce(get_highest_img_number, existing_files, -1)
|
||||||
|
|
||||||
|
calculated_img_number = max(highest_file_number, get_highest_img_number.number_of_images - 1)
|
||||||
|
|
||||||
|
if task_data.session_id in _calculate_img_number.session_img_numbers:
|
||||||
|
calculated_img_number = max(
|
||||||
|
_calculate_img_number.session_img_numbers[task_data.session_id],
|
||||||
|
calculated_img_number,
|
||||||
|
)
|
||||||
|
|
||||||
|
calculated_img_number = calculated_img_number + 1
|
||||||
|
|
||||||
|
_calculate_img_number.session_img_numbers[task_data.session_id] = calculated_img_number
|
||||||
|
return calculated_img_number
|
||||||
|
|
||||||
|
|
||||||
|
_calculate_img_number.session_img_numbers = {}
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_img_number(save_dir_path: str, task_data: TaskData):
|
||||||
|
return ImageNumber(lambda: _calculate_img_number(save_dir_path, task_data))
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
{
|
|
||||||
"_name_or_path": "clip-vit-large-patch14/",
|
|
||||||
"architectures": [
|
|
||||||
"CLIPModel"
|
|
||||||
],
|
|
||||||
"initializer_factor": 1.0,
|
|
||||||
"logit_scale_init_value": 2.6592,
|
|
||||||
"model_type": "clip",
|
|
||||||
"projection_dim": 768,
|
|
||||||
"text_config": {
|
|
||||||
"_name_or_path": "",
|
|
||||||
"add_cross_attention": false,
|
|
||||||
"architectures": null,
|
|
||||||
"attention_dropout": 0.0,
|
|
||||||
"bad_words_ids": null,
|
|
||||||
"bos_token_id": 0,
|
|
||||||
"chunk_size_feed_forward": 0,
|
|
||||||
"cross_attention_hidden_size": null,
|
|
||||||
"decoder_start_token_id": null,
|
|
||||||
"diversity_penalty": 0.0,
|
|
||||||
"do_sample": false,
|
|
||||||
"dropout": 0.0,
|
|
||||||
"early_stopping": false,
|
|
||||||
"encoder_no_repeat_ngram_size": 0,
|
|
||||||
"eos_token_id": 2,
|
|
||||||
"finetuning_task": null,
|
|
||||||
"forced_bos_token_id": null,
|
|
||||||
"forced_eos_token_id": null,
|
|
||||||
"hidden_act": "quick_gelu",
|
|
||||||
"hidden_size": 768,
|
|
||||||
"id2label": {
|
|
||||||
"0": "LABEL_0",
|
|
||||||
"1": "LABEL_1"
|
|
||||||
},
|
|
||||||
"initializer_factor": 1.0,
|
|
||||||
"initializer_range": 0.02,
|
|
||||||
"intermediate_size": 3072,
|
|
||||||
"is_decoder": false,
|
|
||||||
"is_encoder_decoder": false,
|
|
||||||
"label2id": {
|
|
||||||
"LABEL_0": 0,
|
|
||||||
"LABEL_1": 1
|
|
||||||
},
|
|
||||||
"layer_norm_eps": 1e-05,
|
|
||||||
"length_penalty": 1.0,
|
|
||||||
"max_length": 20,
|
|
||||||
"max_position_embeddings": 77,
|
|
||||||
"min_length": 0,
|
|
||||||
"model_type": "clip_text_model",
|
|
||||||
"no_repeat_ngram_size": 0,
|
|
||||||
"num_attention_heads": 12,
|
|
||||||
"num_beam_groups": 1,
|
|
||||||
"num_beams": 1,
|
|
||||||
"num_hidden_layers": 12,
|
|
||||||
"num_return_sequences": 1,
|
|
||||||
"output_attentions": false,
|
|
||||||
"output_hidden_states": false,
|
|
||||||
"output_scores": false,
|
|
||||||
"pad_token_id": 1,
|
|
||||||
"prefix": null,
|
|
||||||
"problem_type": null,
|
|
||||||
"projection_dim" : 768,
|
|
||||||
"pruned_heads": {},
|
|
||||||
"remove_invalid_values": false,
|
|
||||||
"repetition_penalty": 1.0,
|
|
||||||
"return_dict": true,
|
|
||||||
"return_dict_in_generate": false,
|
|
||||||
"sep_token_id": null,
|
|
||||||
"task_specific_params": null,
|
|
||||||
"temperature": 1.0,
|
|
||||||
"tie_encoder_decoder": false,
|
|
||||||
"tie_word_embeddings": true,
|
|
||||||
"tokenizer_class": null,
|
|
||||||
"top_k": 50,
|
|
||||||
"top_p": 1.0,
|
|
||||||
"torch_dtype": null,
|
|
||||||
"torchscript": false,
|
|
||||||
"transformers_version": "4.16.0.dev0",
|
|
||||||
"use_bfloat16": false,
|
|
||||||
"vocab_size": 49408
|
|
||||||
},
|
|
||||||
"text_config_dict": {
|
|
||||||
"hidden_size": 768,
|
|
||||||
"intermediate_size": 3072,
|
|
||||||
"num_attention_heads": 12,
|
|
||||||
"num_hidden_layers": 12,
|
|
||||||
"projection_dim": 768
|
|
||||||
},
|
|
||||||
"torch_dtype": "float32",
|
|
||||||
"transformers_version": null,
|
|
||||||
"vision_config": {
|
|
||||||
"_name_or_path": "",
|
|
||||||
"add_cross_attention": false,
|
|
||||||
"architectures": null,
|
|
||||||
"attention_dropout": 0.0,
|
|
||||||
"bad_words_ids": null,
|
|
||||||
"bos_token_id": null,
|
|
||||||
"chunk_size_feed_forward": 0,
|
|
||||||
"cross_attention_hidden_size": null,
|
|
||||||
"decoder_start_token_id": null,
|
|
||||||
"diversity_penalty": 0.0,
|
|
||||||
"do_sample": false,
|
|
||||||
"dropout": 0.0,
|
|
||||||
"early_stopping": false,
|
|
||||||
"encoder_no_repeat_ngram_size": 0,
|
|
||||||
"eos_token_id": null,
|
|
||||||
"finetuning_task": null,
|
|
||||||
"forced_bos_token_id": null,
|
|
||||||
"forced_eos_token_id": null,
|
|
||||||
"hidden_act": "quick_gelu",
|
|
||||||
"hidden_size": 1024,
|
|
||||||
"id2label": {
|
|
||||||
"0": "LABEL_0",
|
|
||||||
"1": "LABEL_1"
|
|
||||||
},
|
|
||||||
"image_size": 224,
|
|
||||||
"initializer_factor": 1.0,
|
|
||||||
"initializer_range": 0.02,
|
|
||||||
"intermediate_size": 4096,
|
|
||||||
"is_decoder": false,
|
|
||||||
"is_encoder_decoder": false,
|
|
||||||
"label2id": {
|
|
||||||
"LABEL_0": 0,
|
|
||||||
"LABEL_1": 1
|
|
||||||
},
|
|
||||||
"layer_norm_eps": 1e-05,
|
|
||||||
"length_penalty": 1.0,
|
|
||||||
"max_length": 20,
|
|
||||||
"min_length": 0,
|
|
||||||
"model_type": "clip_vision_model",
|
|
||||||
"no_repeat_ngram_size": 0,
|
|
||||||
"num_attention_heads": 16,
|
|
||||||
"num_beam_groups": 1,
|
|
||||||
"num_beams": 1,
|
|
||||||
"num_hidden_layers": 24,
|
|
||||||
"num_return_sequences": 1,
|
|
||||||
"output_attentions": false,
|
|
||||||
"output_hidden_states": false,
|
|
||||||
"output_scores": false,
|
|
||||||
"pad_token_id": null,
|
|
||||||
"patch_size": 14,
|
|
||||||
"prefix": null,
|
|
||||||
"problem_type": null,
|
|
||||||
"projection_dim" : 768,
|
|
||||||
"pruned_heads": {},
|
|
||||||
"remove_invalid_values": false,
|
|
||||||
"repetition_penalty": 1.0,
|
|
||||||
"return_dict": true,
|
|
||||||
"return_dict_in_generate": false,
|
|
||||||
"sep_token_id": null,
|
|
||||||
"task_specific_params": null,
|
|
||||||
"temperature": 1.0,
|
|
||||||
"tie_encoder_decoder": false,
|
|
||||||
"tie_word_embeddings": true,
|
|
||||||
"tokenizer_class": null,
|
|
||||||
"top_k": 50,
|
|
||||||
"top_p": 1.0,
|
|
||||||
"torch_dtype": null,
|
|
||||||
"torchscript": false,
|
|
||||||
"transformers_version": "4.16.0.dev0",
|
|
||||||
"use_bfloat16": false
|
|
||||||
},
|
|
||||||
"vision_config_dict": {
|
|
||||||
"hidden_size": 1024,
|
|
||||||
"intermediate_size": 4096,
|
|
||||||
"num_attention_heads": 16,
|
|
||||||
"num_hidden_layers": 24,
|
|
||||||
"patch_size": 14,
|
|
||||||
"projection_dim": 768
|
|
||||||
}
|
|
||||||
}
|
|
247
ui/index.html
247
ui/index.html
@ -16,6 +16,7 @@
|
|||||||
<link rel="stylesheet" href="/media/css/image-editor.css">
|
<link rel="stylesheet" href="/media/css/image-editor.css">
|
||||||
<link rel="stylesheet" href="/media/css/searchable-models.css">
|
<link rel="stylesheet" href="/media/css/searchable-models.css">
|
||||||
<link rel="stylesheet" href="/media/css/image-modal.css">
|
<link rel="stylesheet" href="/media/css/image-modal.css">
|
||||||
|
<link rel="stylesheet" href="/media/css/plugins.css">
|
||||||
<link rel="manifest" href="/media/manifest.webmanifest">
|
<link rel="manifest" href="/media/manifest.webmanifest">
|
||||||
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
||||||
<script src="/media/js/jquery-confirm.min.js"></script>
|
<script src="/media/js/jquery-confirm.min.js"></script>
|
||||||
@ -30,7 +31,7 @@
|
|||||||
<h1>
|
<h1>
|
||||||
<img id="logo_img" src="/media/images/icon-512x512.png" >
|
<img id="logo_img" src="/media/images/icon-512x512.png" >
|
||||||
Easy Diffusion
|
Easy Diffusion
|
||||||
<small>v2.5.30 <span id="updateBranchLabel"></span></small>
|
<small><span id="version">v2.5.41</span> <span id="updateBranchLabel"></span></small>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div id="server-status">
|
<div id="server-status">
|
||||||
@ -60,12 +61,42 @@
|
|||||||
<input id="prompt_from_file" name="prompt_from_file" type="file" /> <!-- hidden -->
|
<input id="prompt_from_file" name="prompt_from_file" type="file" /> <!-- hidden -->
|
||||||
<label for="negative_prompt" class="collapsible" id="negative_prompt_handle">
|
<label for="negative_prompt" class="collapsible" id="negative_prompt_handle">
|
||||||
Negative Prompt
|
Negative Prompt
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/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>
|
<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>
|
||||||
|
<label for="image-modifiers" data-active="false" id="image-modifier-dropdown">
|
||||||
|
Image Modifiers
|
||||||
<small>(optional)</small>
|
<small>(optional)</small>
|
||||||
</label>
|
</label>
|
||||||
<div class="collapsible-content">
|
<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>
|
<textarea id="negative_prompt" name="negative_prompt" placeholder="list the things to remove from the image (e.g. fog, green)"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="editor-modifiers">
|
||||||
|
<div id="editor-modifiers-header">
|
||||||
|
<div id="modifiers-header-left">
|
||||||
|
<h4>Image Modifiers</h4>
|
||||||
|
<span>(drawing style, camera, etc.)</span>
|
||||||
|
</div>
|
||||||
|
<div id="modifiers-header-right">
|
||||||
|
<i id="modifier-settings-btn" class="fa-solid fa-gear section-button">
|
||||||
|
<span class="simple-tooltip left">
|
||||||
|
Add Custom Modifiers
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<i id="modifiers-container-size-btn" class="fa-solid fa-expand"></i>
|
||||||
|
<i id="modifiers-close-button" class="fa-solid fa-xmark fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="editor-modifiers-subheader">
|
||||||
|
<div id="modifiers-action-collapsibles-btn">
|
||||||
|
<i class="modifiers-action-icon fa-solid fa-square-plus"></i>
|
||||||
|
<span class="modifiers-action-text">
|
||||||
|
Expand Categories
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="editor-modifiers-entries" class="collapsible-content"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="editor-inputs-init-image" class="row">
|
<div id="editor-inputs-init-image" class="row">
|
||||||
@ -102,7 +133,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="editor-inputs-tags-container" class="row">
|
<div id="editor-inputs-tags-container" class="row">
|
||||||
<label>Image Modifiers <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">click an Image Modifier to remove it, right-click to temporarily disable it, use Ctrl+Mouse Wheel to adjust its weight</span></i></label>
|
<label>Image Modifiers <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">Click an Image Modifier to remove it, right-click to temporarily disable it, use Ctrl+Mouse Wheel to adjust its weight</span></i></label>
|
||||||
<div id="editor-inputs-tags-list"></div>
|
<div id="editor-inputs-tags-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -133,15 +164,18 @@
|
|||||||
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td class="model-input">
|
<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="" />
|
<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>
|
<button id="reload-models" class="secondaryButton reloadModels"><i class='fa-solid fa-rotate'></i></button>
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about custom models</span></i></a>
|
<a href="https://github.com/easydiffusion/easydiffusion/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about custom models</span></i></a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<!-- <tr id="modelConfigSelection" class="pl-5"><td><label for="model_config">Model Config:</i></label></td><td>
|
<tr class="pl-5 displayNone" id="clip_skip_config">
|
||||||
<select id="model_config" name="model_config">
|
<td><label for="clip_skip">Clip Skip:</label></td>
|
||||||
</select>
|
<td>
|
||||||
</td></tr> -->
|
<input id="clip_skip" name="clip_skip" type="checkbox">
|
||||||
<tr class="pl-5"><td><label for="vae_model">Custom VAE:</i></label></td><td>
|
<a href="https://github.com/easydiffusion/easydiffusion/wiki/Clip-Skip" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about Clip Skip</span></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="pl-5"><td><label for="vae_model">Custom VAE:</label></td><td>
|
||||||
<input id="vae_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
<input id="vae_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about VAEs</span></i></a>
|
<a href="https://github.com/easydiffusion/easydiffusion/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about VAEs</span></i></a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr id="samplerSelection" class="pl-5"><td><label for="sampler_name">Sampler:</label></td><td>
|
<tr id="samplerSelection" class="pl-5"><td><label for="sampler_name">Sampler:</label></td><td>
|
||||||
<select id="sampler_name" name="sampler_name">
|
<select id="sampler_name" name="sampler_name">
|
||||||
@ -154,18 +188,20 @@
|
|||||||
<option value="dpm2_a">DPM2 Ancestral</option>
|
<option value="dpm2_a">DPM2 Ancestral</option>
|
||||||
<option value="lms">LMS</option>
|
<option value="lms">LMS</option>
|
||||||
<option value="dpm_solver_stability">DPM Solver (Stability AI)</option>
|
<option value="dpm_solver_stability">DPM Solver (Stability AI)</option>
|
||||||
<option value="dpmpp_2s_a">DPM++ 2s Ancestral (Karras)</option>
|
<option value="dpmpp_2s_a" class="k_diffusion-only">DPM++ 2s Ancestral (Karras)</option>
|
||||||
<option value="dpmpp_2m">DPM++ 2m (Karras)</option>
|
<option value="dpmpp_2m">DPM++ 2m (Karras)</option>
|
||||||
<option value="dpmpp_sde">DPM++ SDE (Karras)</option>
|
<option value="dpmpp_sde" class="k_diffusion-only">DPM++ SDE (Karras)</option>
|
||||||
<option value="dpm_fast">DPM Fast (Karras)</option>
|
<option value="dpm_fast" class="k_diffusion-only">DPM Fast (Karras)</option>
|
||||||
<option value="dpm_adaptive">DPM Adaptive (Karras)</option>
|
<option value="dpm_adaptive" class="k_diffusion-only">DPM Adaptive (Karras)</option>
|
||||||
<option value="unipc_snr">UniPC SNR</option>
|
<option value="ddpm" class="diffusers-only">DDPM</option>
|
||||||
|
<option value="deis" class="diffusers-only">DEIS</option>
|
||||||
|
<option value="unipc_snr" class="k_diffusion-only">UniPC SNR</option>
|
||||||
<option value="unipc_tu">UniPC TU</option>
|
<option value="unipc_tu">UniPC TU</option>
|
||||||
<option value="unipc_snr_2">UniPC SNR 2</option>
|
<option value="unipc_snr_2" class="k_diffusion-only">UniPC SNR 2</option>
|
||||||
<option value="unipc_tu_2">UniPC TU 2</option>
|
<option value="unipc_tu_2" class="k_diffusion-only">UniPC TU 2</option>
|
||||||
<option value="unipc_tq">UniPC TQ</option>
|
<option value="unipc_tq" class="k_diffusion-only">UniPC TQ</option>
|
||||||
</select>
|
</select>
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/How-to-Use#samplers" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about samplers</span></i></a>
|
<a href="https://github.com/easydiffusion/easydiffusion/wiki/How-to-Use#samplers" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about samplers</span></i></a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr class="pl-5"><td><label>Image Size: </label></td><td>
|
<tr class="pl-5"><td><label>Image Size: </label></td><td>
|
||||||
<select id="width" name="width" value="512">
|
<select id="width" name="width" value="512">
|
||||||
@ -217,28 +253,40 @@
|
|||||||
<tr class="pl-5"><td><label for="num_inference_steps">Inference Steps:</label></td><td> <input id="num_inference_steps" name="num_inference_steps" size="4" value="25" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
<tr class="pl-5"><td><label for="num_inference_steps">Inference Steps:</label></td><td> <input id="num_inference_steps" name="num_inference_steps" size="4" value="25" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||||
<tr class="pl-5"><td><label for="guidance_scale_slider">Guidance Scale:</label></td><td> <input id="guidance_scale_slider" name="guidance_scale_slider" class="editor-slider" value="75" type="range" min="11" max="500"> <input id="guidance_scale" name="guidance_scale" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
<tr class="pl-5"><td><label for="guidance_scale_slider">Guidance Scale:</label></td><td> <input id="guidance_scale_slider" name="guidance_scale_slider" class="editor-slider" value="75" type="range" min="11" max="500"> <input id="guidance_scale" name="guidance_scale" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||||
<tr id="prompt_strength_container" class="pl-5"><td><label for="prompt_strength_slider">Prompt Strength:</label></td><td> <input id="prompt_strength_slider" name="prompt_strength_slider" class="editor-slider" value="80" type="range" min="0" max="99"> <input id="prompt_strength" name="prompt_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td></tr>
|
<tr id="prompt_strength_container" class="pl-5"><td><label for="prompt_strength_slider">Prompt Strength:</label></td><td> <input id="prompt_strength_slider" name="prompt_strength_slider" class="editor-slider" value="80" type="range" min="0" max="99"> <input id="prompt_strength" name="prompt_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td></tr>
|
||||||
<tr id="lora_model_container" class="pl-5"><td><label for="lora_model">LoRA:</i></label></td><td>
|
<tr id="lora_model_container" class="pl-5"><td><label for="lora_model">LoRA:</label></td><td>
|
||||||
<input id="lora_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
<input id="lora_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr id="lora_alpha_container" class="pl-5">
|
<tr id="lora_alpha_container" class="pl-5">
|
||||||
<td><label for="lora_alpha_slider">LoRA strength:</label></td>
|
<td><label for="lora_alpha_slider">LoRA Strength:</label></td>
|
||||||
<td> <input id="lora_alpha_slider" name="lora_alpha_slider" class="editor-slider" value="100" type="range" min="0" max="100"> <input id="lora_alpha" name="lora_alpha" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
|
<td>
|
||||||
|
<small>-2</small> <input id="lora_alpha_slider" name="lora_alpha_slider" class="editor-slider" value="50" type="range" min="-200" max="200"> <small>2</small>
|
||||||
|
<input id="lora_alpha" name="lora_alpha" size="4" pattern="^-?[0-9]*\.?[0-9]*$" onkeypress="preventNonNumericalInput(event)"><br/>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</i></label></td><td>
|
<tr 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="" />
|
<input id="hypernetwork_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr id="hypernetwork_strength_container" class="pl-5">
|
<tr id="hypernetwork_strength_container" class="pl-5">
|
||||||
<td><label for="hypernetwork_strength_slider">Hypernetwork Strength:</label></td>
|
<td><label for="hypernetwork_strength_slider">Hypernetwork Strength:</label></td>
|
||||||
<td> <input id="hypernetwork_strength_slider" name="hypernetwork_strength_slider" class="editor-slider" value="100" type="range" min="0" max="100"> <input id="hypernetwork_strength" name="hypernetwork_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
|
<td> <input id="hypernetwork_strength_slider" name="hypernetwork_strength_slider" class="editor-slider" value="100" type="range" min="0" max="100"> <input id="hypernetwork_strength" name="hypernetwork_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr id="tiling_container" class="pl-5"><td><label for="tiling">Seamless Tiling:</label></td><td>
|
||||||
|
<select id="tiling" name="tiling">
|
||||||
|
<option value="none" selected>None</option>
|
||||||
|
<option value="x">Horizontal</option>
|
||||||
|
<option value="y">Vertical</option>
|
||||||
|
<option value="xy">Both</option>
|
||||||
|
</select>
|
||||||
|
<a href="https://github.com/easydiffusion/easydiffusion/wiki/Seamless-Tiling" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about Seamless Tiling</span></i></a>
|
||||||
|
</td></tr>
|
||||||
<tr class="pl-5"><td><label for="output_format">Output Format:</label></td><td>
|
<tr class="pl-5"><td><label for="output_format">Output Format:</label></td><td>
|
||||||
<select id="output_format" name="output_format">
|
<select id="output_format" name="output_format">
|
||||||
<option value="jpeg" selected>jpeg</option>
|
<option value="jpeg" selected>jpeg</option>
|
||||||
<option value="png">png</option>
|
<option value="png">png</option>
|
||||||
<option value="webp">webp</option>
|
<option value="webp">webp</option>
|
||||||
</select>
|
</select>
|
||||||
<span id="output_lossless_container">
|
<span id="output_lossless_container" class="displayNone">
|
||||||
<input id="output_lossless" name="output_lossless" type="checkbox"><label for="output_lossless">Lossless</label></td></tr>
|
<input id="output_lossless" name="output_lossless" type="checkbox"><label for="output_lossless">Lossless</label>
|
||||||
</span>
|
</span>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr class="pl-5" id="output_quality_row"><td><label for="output_quality">Image Quality:</label></td><td>
|
<tr class="pl-5" id="output_quality_row"><td><label for="output_quality">Image Quality:</label></td><td>
|
||||||
@ -249,50 +297,43 @@
|
|||||||
<div><ul>
|
<div><ul>
|
||||||
<li><b class="settings-subheader">Render Settings</b></li>
|
<li><b class="settings-subheader">Render Settings</b></li>
|
||||||
<li class="pl-5"><input id="stream_image_progress" name="stream_image_progress" type="checkbox"> <label for="stream_image_progress">Show a live preview <small>(uses more VRAM, slower images)</small></label></li>
|
<li class="pl-5"><input id="stream_image_progress" name="stream_image_progress" type="checkbox"> <label for="stream_image_progress">Show a live preview <small>(uses more VRAM, slower images)</small></label></li>
|
||||||
<li class="pl-5"><input id="use_face_correction" name="use_face_correction" type="checkbox"> <label for="use_face_correction">Fix incorrect faces and eyes</label> <div style="display:inline-block;"><input id="gfpgan_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /></div></li>
|
<li class="pl-5" id="use_face_correction_container">
|
||||||
|
<input id="use_face_correction" name="use_face_correction" type="checkbox"> <label for="use_face_correction">Fix incorrect faces and eyes</label> <div style="display:inline-block;"><input id="gfpgan_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /></div>
|
||||||
|
<table id="codeformer_settings" class="displayNone sub-settings">
|
||||||
|
<tr class="pl-5"><td><label for="codeformer_fidelity_slider">Strength:</label></td><td><input id="codeformer_fidelity_slider" name="codeformer_fidelity_slider" class="editor-slider" value="5" type="range" min="0" max="10"> <input id="codeformer_fidelity" name="codeformer_fidelity" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||||
|
<tr class="pl-5"><td><label for="codeformer_upscale_faces">Upscale Faces:</label></td><td><input id="codeformer_upscale_faces" name="codeformer_upscale_faces" type="checkbox" checked> <label><small>(improves the resolution of faces)</small></label></td></tr>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
<li class="pl-5">
|
<li class="pl-5">
|
||||||
<input id="use_upscale" name="use_upscale" type="checkbox"> <label for="use_upscale">Scale up by</label>
|
<input id="use_upscale" name="use_upscale" type="checkbox"> <label for="use_upscale">Scale up by</label>
|
||||||
<select id="upscale_amount" name="upscale_amount">
|
<select id="upscale_amount" name="upscale_amount">
|
||||||
<option value="2">2x</option>
|
<option id="upscale_amount_2x" value="2">2x</option>
|
||||||
<option value="4" selected>4x</option>
|
<option id="upscale_amount_4x" value="4" selected>4x</option>
|
||||||
</select>
|
</select>
|
||||||
with
|
with
|
||||||
<select id="upscale_model" name="upscale_model">
|
<select id="upscale_model" name="upscale_model">
|
||||||
<option value="RealESRGAN_x4plus" selected>RealESRGAN_x4plus</option>
|
<option value="RealESRGAN_x4plus" selected>RealESRGAN_x4plus</option>
|
||||||
<option value="RealESRGAN_x4plus_anime_6B">RealESRGAN_x4plus_anime_6B</option>
|
<option value="RealESRGAN_x4plus_anime_6B">RealESRGAN_x4plus_anime_6B</option>
|
||||||
|
<option value="latent_upscaler">Latent Upscaler 2x</option>
|
||||||
</select>
|
</select>
|
||||||
|
<table id="latent_upscaler_settings" class="displayNone sub-settings">
|
||||||
|
<tr class="pl-5"><td><label for="latent_upscaler_steps_slider">Upscaling Steps:</label></td><td><input id="latent_upscaler_steps_slider" name="latent_upscaler_steps_slider" class="editor-slider" value="10" type="range" min="1" max="50"> <input id="latent_upscaler_steps" name="latent_upscaler_steps" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||||
|
</table>
|
||||||
</li>
|
</li>
|
||||||
<li class="pl-5"><input id="show_only_filtered_image" name="show_only_filtered_image" type="checkbox" checked> <label for="show_only_filtered_image">Show only the corrected/upscaled image</label></li>
|
<li class="pl-5"><input id="show_only_filtered_image" name="show_only_filtered_image" type="checkbox" checked> <label for="show_only_filtered_image">Show only the corrected/upscaled image</label></li>
|
||||||
</ul></div>
|
</ul></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="editor-modifiers" class="panel-box">
|
|
||||||
<h4 class="collapsible">
|
|
||||||
Image Modifiers (art styles, tags etc)
|
|
||||||
<i id="modifier-settings-btn" class="fa-solid fa-gear section-button">
|
|
||||||
<span class="simple-tooltip left">
|
|
||||||
Add Custom Modifiers
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
</h4>
|
|
||||||
<div id="editor-modifiers-entries" class="collapsible-content">
|
|
||||||
<div id="editor-modifiers-entries-toolbar">
|
|
||||||
<label for="preview-image">Image Style:</label>
|
|
||||||
<select id="preview-image" name="preview-image" value="portrait">
|
|
||||||
<option value="portrait" selected="">Face</option>
|
|
||||||
<option value="landscape">Landscape</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label for="modifier-card-size-slider">Thumbnail Size:</label>
|
|
||||||
<input id="modifier-card-size-slider" name="modifier-card-size-slider" value="0" type="range" min="-3" max="5">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="preview" class="col-free">
|
<div id="preview" class="col-free">
|
||||||
|
|
||||||
|
<div id="initial-text">
|
||||||
|
Type a prompt and press the "Make Image" button.<br/><br/>You can set an "Initial Image" if you want to guide the AI.<br/><br/>
|
||||||
|
You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section
|
||||||
|
and selecting the desired modifiers.<br/><br/>
|
||||||
|
Click "Image Settings" for additional settings like seed, image size, number of images to generate etc.<br/><br/>Enjoy! :)
|
||||||
|
</div>
|
||||||
<div id="preview-content">
|
<div id="preview-content">
|
||||||
<div id="preview-tools" class="displayNone">
|
<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 id="clear-all-previews" class="secondaryButton"><i class="fa-solid fa-trash-can icon"></i> Clear All</button>
|
||||||
@ -326,22 +367,22 @@
|
|||||||
<div class="clearfix" style="clear: both;"></div>
|
<div class="clearfix" style="clear: both;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="initial-text">
|
|
||||||
Type a prompt and press the "Make Image" button.<br/><br/>You can set an "Initial Image" if you want to guide the AI.<br/><br/>
|
|
||||||
You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section
|
|
||||||
and selecting the desired modifiers.<br/><br/>
|
|
||||||
Click "Image Settings" for additional settings like seed, image size, number of images to generate etc.<br/><br/>Enjoy! :)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-content-settings" class="tab-content">
|
<div id="tab-content-settings" class="tab-content">
|
||||||
<div id="system-settings" class="tab-content-inner">
|
<div id="system-settings" class="tab-content-inner">
|
||||||
<h1>System Settings</h1>
|
<h1>System Settings</h1>
|
||||||
<div class="parameters-table"></div>
|
<div class="parameters-table" id="system-settings-table"></div>
|
||||||
<br/>
|
<br/>
|
||||||
<button id="save-system-settings-btn" class="primaryButton">Save</button>
|
<button id="save-system-settings-btn" class="primaryButton">Save</button>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
<div id="share-easy-diffusion">
|
||||||
|
<h3><i class="fa fa-user-group"></i> Share Easy Diffusion</h3>
|
||||||
|
<div class="parameters-table" id="system-settings-network-table">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/><br/>
|
||||||
<div>
|
<div>
|
||||||
<h3><i class="fa fa-microchip icon"></i> System Info</h3>
|
<h3><i class="fa fa-microchip icon"></i> System Info</h3>
|
||||||
<div id="system-info">
|
<div id="system-info">
|
||||||
@ -365,23 +406,23 @@
|
|||||||
<ul id="help-links">
|
<ul id="help-links">
|
||||||
<li><span class="help-section">Using the software</span>
|
<li><span class="help-section">Using the software</span>
|
||||||
<ul>
|
<ul>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/How-To-Use" target="_blank"><i class="fa-solid fa-book fa-fw"></i> How to use</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/How-To-Use" target="_blank"><i class="fa-solid fa-book fa-fw"></i> How to use</a>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/UI-Overview" target="_blank"><i class="fa-solid fa-list fa-fw"></i> UI Overview</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/UI-Overview" target="_blank"><i class="fa-solid fa-list fa-fw"></i> UI Overview</a>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Writing-Prompts" target="_blank"><i class="fa-solid fa-pen-to-square fa-fw"></i> Writing prompts</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/Writing-Prompts" target="_blank"><i class="fa-solid fa-pen-to-square fa-fw"></i> Writing prompts</a>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Inpainting" target="_blank"><i class="fa-solid fa-paintbrush fa-fw"></i> Inpainting</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/Inpainting" target="_blank"><i class="fa-solid fa-paintbrush fa-fw"></i> Inpainting</a>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Run-on-Multiple-GPUs" target="_blank"><i class="fa-solid fa-paintbrush fa-fw"></i> Run on Multiple GPUs</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/Run-on-Multiple-GPUs" target="_blank"><i class="fa-solid fa-paintbrush fa-fw"></i> Run on Multiple GPUs</a>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<li><span class="help-section">Installation</span>
|
<li><span class="help-section">Installation</span>
|
||||||
<ul>
|
<ul>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" target="_blank"><i class="fa-solid fa-circle-question fa-fw"></i> Troubleshooting</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting" target="_blank"><i class="fa-solid fa-circle-question fa-fw"></i> Troubleshooting</a>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<li><span class="help-section">Downloadable Content</span>
|
<li><span class="help-section">Downloadable Content</span>
|
||||||
<ul>
|
<ul>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-images fa-fw"></i> Custom Models</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-images fa-fw"></i> Custom Models</a>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/UI-Plugins" target="_blank"><i class="fa-solid fa-puzzle-piece fa-fw"></i> UI Plugins</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/UI-Plugins" target="_blank"><i class="fa-solid fa-puzzle-piece fa-fw"></i> UI Plugins</a>
|
||||||
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-hand-sparkles fa-fw"></i> VAE Variational Auto Encoder</a>
|
<li> <a href="https://github.com/easydiffusion/easydiffusion/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-hand-sparkles fa-fw"></i> VAE Variational Auto Encoder</a>
|
||||||
</ul>
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -391,7 +432,7 @@
|
|||||||
<ul id="community-links">
|
<ul id="community-links">
|
||||||
<li><a href="https://discord.com/invite/u9yhsFmEkB" target="_blank"><i class="fa-brands fa-discord fa-fw"></i> Discord user community</a></li>
|
<li><a href="https://discord.com/invite/u9yhsFmEkB" target="_blank"><i class="fa-brands fa-discord fa-fw"></i> Discord user community</a></li>
|
||||||
<li><a href="https://www.reddit.com/r/StableDiffusionUI/" target="_blank"><i class="fa-brands fa-reddit fa-fw"></i> Reddit community</a></li>
|
<li><a href="https://www.reddit.com/r/StableDiffusionUI/" target="_blank"><i class="fa-brands fa-reddit fa-fw"></i> Reddit community</a></li>
|
||||||
<li><a href="https://github.com/cmdr2/stable-diffusion-ui" target="_blank"><i class="fa-brands fa-github fa-fw"></i> Source code on GitHub</a></li>
|
<li><a href="https://github.com/easydiffusion/easydiffusion" target="_blank"><i class="fa-brands fa-github fa-fw"></i> Source code on GitHub</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -399,6 +440,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="popup" id="splash-screen" data-version="1">
|
||||||
|
<div>
|
||||||
|
<i class="close-button fa-solid fa-xmark"></i>
|
||||||
|
<img class="splash-img" src="/media/images/icon-512x512.png" width="128" height="128">
|
||||||
|
<h1>Diffusers Tech Preview</h1>
|
||||||
|
<p>The Diffusers Tech Preview allows early access to the new features based on <a href="https://huggingface.co/docs/diffusers/index" target="_blank">Diffusers</a>.</p>
|
||||||
|
<p>This is under active development, and is missing a few features. It is experimental! Please report any bugs to the #beta channel in our <a href="https://discord.gg/QUcNZufQNZ" target="_blank">Discord</a> server!</p>
|
||||||
|
<h2>New upcoming features in our new engine</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://huggingface.co/blog/lora" target="_blank">LORA</a> support - Place LORA files in the <tt>models/lora</tt> folder.</li>
|
||||||
|
<li><a href="https://github.com/damian0815/compel/blob/main/Reference.md" target="_blank">Compel Prompt Parser</a> - New, more powerful parser. In short:
|
||||||
|
<ul>
|
||||||
|
<li> no limit to the length of prompts (i.e. long prompts are supported)</li>
|
||||||
|
<li> Use <tt>+</tt> and <tt>-</tt> to increase/decrease the weight. E.g. <tt>apple</tt>, <tt>apple+</tt>, <tt>apple++</tt>, <tt>apple+++</tt>,
|
||||||
|
or <tt>apple-</tt>, <tt>apple--</tt> for different weights.</li>
|
||||||
|
<li> Use exact weights - 0.0 to 1.0 reduces the weight, 1.0 to 2.0 increases the weight.
|
||||||
|
Think of it like a multiplier, like 1.5x or 0.5x: E.g. <tt>(apple)0.8 falling from a tree</tt>,
|
||||||
|
<tt>(apple)1.5 falling from a tree</tt>, <tt>(apple falling)1.4 from a tree</tt></li>
|
||||||
|
<li> You can group tokens together using parentheses/round-brackets. E.g. <tt>(apple falling)++
|
||||||
|
from a tree</tt>. Nested parentheses are supported.</li>
|
||||||
|
</ul>
|
||||||
|
This clarifies a few things:
|
||||||
|
<ul>
|
||||||
|
<li> colon (<tt>:</tt>) is NOT used for blending. Neither is it used for weights. It has no impact and
|
||||||
|
will be considered a part of the prompt.</li>
|
||||||
|
<li> <tt>(())</tt> and <tt>[]</tt> do not affect the prompt's weights.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li> More choices for img2img samplers</li>
|
||||||
|
<li> Support for official inpainting models</li>
|
||||||
|
<li> Generate images that tile seamlessly</li>
|
||||||
|
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Clip-Skip" target="_blank">Clip Skip</a> support allows to skip the last CLIP layer (recommended by some LORA models)</li>
|
||||||
|
<li> New samplers: DDPM and DEIS</li>
|
||||||
|
<li> Memory optimizations that allow the use of 2GB GPUs</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Known issues</h2>
|
||||||
|
<ul>
|
||||||
|
<li> Some LoRA consistently fail to load in EasyDiffusion</li>
|
||||||
|
<li> Some LoRA are far more sensitive to alpha (compared to a11)</li>
|
||||||
|
<li> Hangs sometimes on "compel is ready", while making the token.</li>
|
||||||
|
<li> Some custom inpainting models don't work</li>
|
||||||
|
<li> These samplers don't work yet: Unipc SNR, Unipc TQ, Unipc SNR2, DPM++ 2s Ancestral, DPM++ SDE, DPM Fast, DPM Adaptive, DPM2</li>
|
||||||
|
<li> Hypernetwork doesn't work</li>
|
||||||
|
<li> The time remaining in browser differs from the one in the console</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="popup" id="download-images-popup">
|
<div class="popup" id="download-images-popup">
|
||||||
<div>
|
<div>
|
||||||
@ -442,6 +530,16 @@
|
|||||||
<p>Set your custom modifiers (one per line)</p>
|
<p>Set your custom modifiers (one per line)</p>
|
||||||
<textarea id="custom-modifiers-input" placeholder="Enter your custom modifiers, one-per-line" spellcheck="false"></textarea>
|
<textarea id="custom-modifiers-input" placeholder="Enter your custom modifiers, one-per-line" spellcheck="false"></textarea>
|
||||||
<p><small><b>Tip:</b> You can include special characters like {} () [] and |. You can also put multiple comma-separated phrases in a single line, to make a single modifier that combines all of those.</small></p>
|
<p><small><b>Tip:</b> You can include special characters like {} () [] and |. You can also put multiple comma-separated phrases in a single line, to make a single modifier that combines all of those.</small></p>
|
||||||
|
<div id="editor-modifiers-entries-toolbar">
|
||||||
|
<label for="preview-image">Image Style:</label>
|
||||||
|
<select id="preview-image" name="preview-image" value="portrait">
|
||||||
|
<option value="portrait" selected="">Face</option>
|
||||||
|
<option value="landscape">Landscape</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="modifier-card-size-slider">Thumbnail Size:</label>
|
||||||
|
<input id="modifier-card-size-slider" name="modifier-card-size-slider" value="0" type="range" min="-2" max="3">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -481,10 +579,10 @@
|
|||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div class="line-separator"> </div>
|
<div class="line-separator"> </div>
|
||||||
<p>If you found this project useful and want to help keep it alive, please <a href="https://ko-fi.com/cmdr2_stablediffusion_ui" 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>If you found this project useful and want to help keep it alive, please <a href="https://ko-fi.com/cmdr2_stablediffusion_ui" 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/cmdr2/stable-diffusion-ui/issues" target="_blank">file an issue</a> if you have any problems or suggestions in using this interface.</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">
|
<div id="footer-legal">
|
||||||
<p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
|
<p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
|
||||||
<p>This license of this software forbids you from sharing any content that violates any laws, produce any harm to a person, disseminate any personal information that would be meant for harm, <br/>spread misinformation and target vulnerable groups. For the full list of restrictions please read <a href="https://github.com/cmdr2/stable-diffusion-ui/blob/main/LICENSE" target="_blank">the license</a>.</p>
|
<p>This license of this software forbids you from sharing any content that violates any laws, produce any harm to a person, disseminate any personal information that would be meant for harm, <br/>spread misinformation and target vulnerable groups. For the full list of restrictions please read <a href="https://github.com/easydiffusion/easydiffusion/blob/main/LICENSE" target="_blank">the license</a>.</p>
|
||||||
<p>By using this software, you consent to the terms and conditions of the license.</p>
|
<p>By using this software, you consent to the terms and conditions of the license.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -493,13 +591,13 @@
|
|||||||
<script src="media/js/utils.js"></script>
|
<script src="media/js/utils.js"></script>
|
||||||
<script src="media/js/engine.js"></script>
|
<script src="media/js/engine.js"></script>
|
||||||
<script src="media/js/parameters.js"></script>
|
<script src="media/js/parameters.js"></script>
|
||||||
<script src="media/js/plugins.js"></script>
|
|
||||||
|
|
||||||
<script src="media/js/image-modifiers.js"></script>
|
<script src="media/js/image-modifiers.js"></script>
|
||||||
<script src="media/js/auto-save.js"></script>
|
<script src="media/js/auto-save.js"></script>
|
||||||
|
|
||||||
<script src="media/js/searchable-models.js"></script>
|
<script src="media/js/searchable-models.js"></script>
|
||||||
<script src="media/js/main.js"></script>
|
<script src="media/js/main.js"></script>
|
||||||
|
<script src="media/js/plugins.js"></script>
|
||||||
<script src="media/js/themes.js"></script>
|
<script src="media/js/themes.js"></script>
|
||||||
<script src="media/js/dnd.js"></script>
|
<script src="media/js/dnd.js"></script>
|
||||||
<script src="media/js/image-editor.js"></script>
|
<script src="media/js/image-editor.js"></script>
|
||||||
@ -512,15 +610,18 @@ async function init() {
|
|||||||
await loadUIPlugins()
|
await loadUIPlugins()
|
||||||
await loadModifiers()
|
await loadModifiers()
|
||||||
await getSystemInfo()
|
await getSystemInfo()
|
||||||
|
await initPlugins()
|
||||||
|
|
||||||
SD.init({
|
SD.init({
|
||||||
events: {
|
events: {
|
||||||
statusChange: setServerStatus,
|
statusChange: setServerStatus,
|
||||||
idle: onIdle
|
idle: onIdle,
|
||||||
|
ping: tunnelUpdate
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
splashScreen()
|
||||||
|
|
||||||
playSound()
|
// playSound()
|
||||||
}
|
}
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
@ -69,11 +69,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.parameters-table > div:first-child {
|
.parameters-table > div:first-child {
|
||||||
border-radius: 12px 12px 0px 0px;
|
border-top-left-radius: 12px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.parameters-table > div:last-child {
|
.parameters-table > div:last-child {
|
||||||
border-radius: 0px 0px 12px 12px;
|
border-bottom-left-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.parameters-table .fa-fire {
|
.parameters-table .fa-fire {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
font-family: 'Work Sans';
|
font-family: 'Work Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local(''),
|
src: local('Work Sans'),
|
||||||
url('/media/fonts/work-sans-v18-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
url('/media/fonts/work-sans-v18-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
url('/media/fonts/work-sans-v18-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
url('/media/fonts/work-sans-v18-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@
|
|||||||
font-family: 'Work Sans';
|
font-family: 'Work Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: local(''),
|
src: local('Work Sans'),
|
||||||
url('/media/fonts/work-sans-v18-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
url('/media/fonts/work-sans-v18-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
url('/media/fonts/work-sans-v18-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
url('/media/fonts/work-sans-v18-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@
|
|||||||
font-family: 'Work Sans';
|
font-family: 'Work Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: local(''),
|
src: local('Work Sans'),
|
||||||
url('/media/fonts/work-sans-v18-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
url('/media/fonts/work-sans-v18-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
url('/media/fonts/work-sans-v18-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
url('/media/fonts/work-sans-v18-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@
|
|||||||
font-family: 'Work Sans';
|
font-family: 'Work Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
src: local(''),
|
src: local('Work Sans'),
|
||||||
url('/media/fonts/work-sans-v18-latin-800.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
url('/media/fonts/work-sans-v18-latin-800.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
url('/media/fonts/work-sans-v18-latin-800.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
url('/media/fonts/work-sans-v18-latin-800.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
.editor-controls-center {
|
.editor-controls-center {
|
||||||
/* background: var(--background-color2); */
|
/* background: var(--background-color2); */
|
||||||
flex: 1;
|
flex: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -105,6 +105,8 @@
|
|||||||
.editor-controls-center > div {
|
.editor-controls-center > div {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: black;
|
background: black;
|
||||||
|
margin: 20pt;
|
||||||
|
margin-top: 40pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-controls-center canvas {
|
.editor-controls-center canvas {
|
||||||
@ -164,8 +166,10 @@
|
|||||||
margin: var(--popup-margin);
|
margin: var(--popup-margin);
|
||||||
padding: var(--popup-padding);
|
padding: var(--popup-padding);
|
||||||
min-height: calc(99h - (2 * var(--popup-margin)));
|
min-height: calc(99h - (2 * var(--popup-margin)));
|
||||||
max-width: none;
|
max-width: fit-content;
|
||||||
min-width: fit-content;
|
min-width: fit-content;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-editor-popup h1 {
|
.image-editor-popup h1 {
|
||||||
|
@ -70,6 +70,14 @@
|
|||||||
max-height: calc(100vh - (var(--popup-padding) * 2) - 4px);
|
max-height: calc(100vh - (var(--popup-padding) * 2) - 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#viewFullSizeImgModal img:not(.natural-zoom) {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewFullSizeImgModal .grabbing img:not(.natural-zoom) {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
#viewFullSizeImgModal .content > div::-webkit-scrollbar-track, #viewFullSizeImgModal .content > div::-webkit-scrollbar-corner {
|
#viewFullSizeImgModal .content > div::-webkit-scrollbar-track, #viewFullSizeImgModal .content > div::-webkit-scrollbar-corner {
|
||||||
background: rgba(0, 0, 0, .5)
|
background: rgba(0, 0, 0, .5)
|
||||||
}
|
}
|
||||||
|
@ -211,10 +211,6 @@ code {
|
|||||||
#makeImage {
|
#makeImage {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
#editor-modifiers h5 {
|
|
||||||
padding: 5pt 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#makeImage {
|
#makeImage {
|
||||||
flex: 0 0 70px;
|
flex: 0 0 70px;
|
||||||
background: var(--accent-color);
|
background: var(--accent-color);
|
||||||
@ -238,6 +234,10 @@ code {
|
|||||||
#stopImage:hover {
|
#stopImage:hover {
|
||||||
background: rgb(177, 27, 0);
|
background: rgb(177, 27, 0);
|
||||||
}
|
}
|
||||||
|
#undo {
|
||||||
|
float: right;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
div#render-buttons {
|
div#render-buttons {
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
@ -280,14 +280,193 @@ button#resume {
|
|||||||
.collapsible:not(.active) ~ .collapsible-content {
|
.collapsible:not(.active) ~ .collapsible-content {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
#image-modifier-dropdown {
|
||||||
|
margin-left: 1em;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#image-modifier-dropdown[data-active="true"]::before {
|
||||||
|
content: "➖";
|
||||||
|
}
|
||||||
|
#image-modifier-dropdown[data-active="false"]::before {
|
||||||
|
content: "➕";
|
||||||
|
}
|
||||||
#editor-modifiers {
|
#editor-modifiers {
|
||||||
overflow-y: auto;
|
max-width: 75vw;
|
||||||
|
min-width: 50vw;
|
||||||
|
max-height: fit-content;
|
||||||
|
overflow-y: hidden;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
display: none;
|
||||||
|
background: var(--background-color1);
|
||||||
|
border: solid 1px var(--background-color3);
|
||||||
|
z-index: 999;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0px 0px 30px black;
|
||||||
|
border: 2px solid rgb(255 255 255 / 10%);
|
||||||
|
}
|
||||||
|
#editor-modifiers.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
left: 5vw;
|
||||||
|
}
|
||||||
|
.modifiers-maximized {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0 !important;
|
||||||
|
bottom: 0px !important;
|
||||||
|
left: 0px !important;
|
||||||
|
right: 0px !important;
|
||||||
|
max-width: unset !important;
|
||||||
|
max-height: unset !important;
|
||||||
|
border: 0px !important;
|
||||||
|
border-radius: 0px !important;
|
||||||
|
}
|
||||||
|
.modifiers-maximized #editor-modifiers-entries {
|
||||||
|
max-height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#editor-modifiers-header {
|
||||||
|
background-color: var(--background-color4);
|
||||||
|
padding: 0.5em;
|
||||||
|
border-bottom: 1px solid rgb(255 255 255 / 10%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
|
#editor-modifiers-subheader {
|
||||||
|
background-color: var(--background-color4);
|
||||||
|
padding: 0.5em;
|
||||||
|
border-bottom: 1px solid rgb(255 255 255 / 10%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
grid-gap: 0.8em;
|
||||||
|
flex-direction: row;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
}
|
||||||
|
#editor-modifiers-subheader::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.02);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#modifiers-header-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 0.1em;
|
||||||
|
}
|
||||||
|
#modifiers-header-left span {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: rgb(127 127 127);
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
#modifiers-header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
grid-gap: 0.8em;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-modifiers-subheader i,
|
||||||
|
#modifiers-header-right i {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#modifiers-header-right .section-button,
|
||||||
|
#editor-modifiers-subheader .section-button {
|
||||||
|
margin-top: 0.3em;
|
||||||
|
}
|
||||||
|
#modifiers-action-collapsibles-btn {
|
||||||
|
display: flex;
|
||||||
|
grid-gap: 0.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.modifiers-action-text {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: rgb(192 192 192);
|
||||||
|
}
|
||||||
|
#modifiers-expand-btn {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
#modifiers-expand-btn .simple-tooltip {
|
||||||
|
background-color: var(--background-color3);
|
||||||
|
border-radius: 50px;
|
||||||
|
}
|
||||||
|
.modifier-category .collapsible {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.modifier-category .collapsible::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.modifier-category:hover .collapsible::after {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#editor-modifiers-entries {
|
||||||
|
overflow: auto scroll;
|
||||||
|
max-height: 50vh;
|
||||||
|
height: fit-content;
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
#editor-modifiers-entries .collapsible {
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
#editor-modifiers-entries .modifier-category:nth-child(odd) .collapsible::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.02);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
#editor-modifiers .editor-modifiers-leaf {
|
#editor-modifiers .editor-modifiers-leaf {
|
||||||
padding-top: 10pt;
|
padding-top: 10pt;
|
||||||
padding-bottom: 10pt;
|
padding-bottom: 10pt;
|
||||||
}
|
}
|
||||||
|
#editor-modifiers h5 {
|
||||||
|
padding: 5pt 0;
|
||||||
|
margin: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: -2px;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: var(--background-color3);
|
||||||
|
border-bottom: 1px solid rgb(255 255 255 / 4%);
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
img {
|
img {
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
@ -306,6 +485,9 @@ div.img-preview img {
|
|||||||
margin-top: 5pt;
|
margin-top: 5pt;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#editor-inputs-tags-list {
|
||||||
|
max-height: 30em;
|
||||||
|
}
|
||||||
#server-status {
|
#server-status {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
@ -572,6 +754,11 @@ div.img-preview img {
|
|||||||
margin-bottom: 5pt;
|
margin-bottom: 5pt;
|
||||||
margin-top: 5pt;
|
margin-top: 5pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.taskConfigContainer {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
.img-batch {
|
.img-batch {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
@ -770,7 +957,6 @@ input::file-selector-button {
|
|||||||
height: 19px;
|
height: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.input-toggle {
|
.input-toggle {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -1074,6 +1260,7 @@ input::file-selector-button {
|
|||||||
/* POPUPS */
|
/* POPUPS */
|
||||||
.popup:not(.active) {
|
.popup:not(.active) {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
overflow-x: hidden; /* fix overflow from body */
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1283,6 +1470,49 @@ body.wait-pause {
|
|||||||
100% { border: solid 12px var(--accent-color); }
|
100% { border: solid 12px var(--accent-color); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#splash-screen div {
|
||||||
|
text-align: left;
|
||||||
|
max-width: 70vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-screen .splash-img {
|
||||||
|
float: right;
|
||||||
|
box-shadow: none;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-screen tt {
|
||||||
|
font-family: monospace;
|
||||||
|
background: var(--input-background-color);
|
||||||
|
padding: 1px 4px 1px 4px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-screen li {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-screen a
|
||||||
|
{
|
||||||
|
color: #ccf;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-screen a[href^="http"]::after,
|
||||||
|
#splash-screen a[href^="https://"]::after
|
||||||
|
{
|
||||||
|
content: "";
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
margin-left: 4px;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='lightblue' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.jconfirm.jconfirm-modern .jconfirm-box div.jconfirm-title-c {
|
.jconfirm.jconfirm-modern .jconfirm-box div.jconfirm-title-c {
|
||||||
color: var(--button-text-color);
|
color: var(--button-text-color);
|
||||||
}
|
}
|
||||||
@ -1293,3 +1523,83 @@ body.wait-pause {
|
|||||||
.displayNone {
|
.displayNone {
|
||||||
display:none !important;
|
display:none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sub-settings {
|
||||||
|
padding-top: 3pt;
|
||||||
|
padding-bottom: 3pt;
|
||||||
|
padding-left: 5pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cloudflare-address {
|
||||||
|
background-color: var(--background-color3);
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: var(--input-border-radius);
|
||||||
|
border: var(--input-border-size) solid var(--input-border-color);
|
||||||
|
margin-top: 0.2em;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#copy-cloudflare-address {
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandedSettingRow {
|
||||||
|
background: var(--background-color1);
|
||||||
|
width: 95%;
|
||||||
|
border-radius: 4pt;
|
||||||
|
margin-top: 5pt;
|
||||||
|
margin-bottom: 3pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOAST NOTIFICATIONS */
|
||||||
|
.toast-notification {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 10px;
|
||||||
|
right: -300px;
|
||||||
|
width: 300px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 9999;
|
||||||
|
animation: slideInRight 0.5s ease forwards;
|
||||||
|
transition: bottom 0.5s ease; /* Add a transition to smoothly reposition the toasts */
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-notification-error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
right: -300px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-notification.hide {
|
||||||
|
animation: slideOutRight 0.5s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
right: -300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
.modifier-card {
|
.modifier-card {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: content-box; /* fixes border misalignment */
|
||||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
margin: 3pt 3pt;
|
margin: 3pt 3pt;
|
||||||
float: left;
|
float: left;
|
||||||
width: 8em;
|
width: 6em;
|
||||||
height: 11.5em;
|
height: 9.5em;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: 8em 3.5em;
|
grid-template-rows: 6em 3.5em;
|
||||||
gap: 0px 0px;
|
gap: 0px 0px;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
@ -16,82 +18,71 @@
|
|||||||
"modifier-card-container";
|
"modifier-card-container";
|
||||||
border: 2px solid rgba(255, 255, 255, .05);
|
border: 2px solid rgba(255, 255, 255, .05);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
z-index: 2;
|
||||||
.modifier-card-size_5 {
|
|
||||||
width: 18em;
|
|
||||||
grid-template-rows: 18em 3.5em;
|
|
||||||
height: 21.5em;
|
|
||||||
}
|
|
||||||
.modifier-card-size_5 .modifier-card-image-overlay {
|
|
||||||
font-size: 8em;
|
|
||||||
}
|
|
||||||
.modifier-card-size_4 {
|
|
||||||
width: 14em;
|
|
||||||
grid-template-rows: 14em 3.5em;
|
|
||||||
height: 17.5em;
|
|
||||||
}
|
|
||||||
.modifier-card-size_4 .modifier-card-image-overlay {
|
|
||||||
font-size: 7em;
|
|
||||||
}
|
}
|
||||||
.modifier-card-size_3 {
|
.modifier-card-size_3 {
|
||||||
width: 11em;
|
|
||||||
grid-template-rows: 11em 3.5em;
|
|
||||||
height: 14.5em;
|
|
||||||
}
|
|
||||||
.modifier-card-size_3 .modifier-card-image-overlay {
|
|
||||||
font-size: 6em;
|
|
||||||
}
|
|
||||||
.modifier-card-size_2 {
|
|
||||||
width: 10em;
|
width: 10em;
|
||||||
grid-template-rows: 10em 3.5em;
|
grid-template-rows: 10em 3.5em;
|
||||||
height: 13.5em;
|
height: 13.5em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_2 .modifier-card-image-overlay {
|
.modifier-card-size_3 .modifier-card-image-overlay {
|
||||||
font-size: 6em;
|
font-size: 6em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_1 {
|
.modifier-card-size_3 .modifier-card-label {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.modifier-card-size_2 {
|
||||||
width: 9em;
|
width: 9em;
|
||||||
grid-template-rows: 9em 3.5em;
|
grid-template-rows: 9em 3.5em;
|
||||||
height: 12.5em;
|
height: 12.5em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_1 .modifier-card-image-overlay {
|
.modifier-card-size_2 .modifier-card-image-overlay {
|
||||||
font-size: 5em;
|
font-size: 5em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_-1 {
|
.modifier-card-size_2 .modifier-card-label {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.modifier-card-size_1 {
|
||||||
width: 7em;
|
width: 7em;
|
||||||
grid-template-rows: 7em 3.5em;
|
grid-template-rows: 7em 3.5em;
|
||||||
height: 10.5em;
|
height: 10.5em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_-1 .modifier-card-image-overlay {
|
.modifier-card-size_1 .modifier-card-image-overlay {
|
||||||
font-size: 4em;
|
font-size: 4em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_-2 {
|
.modifier-card-size_-1 {
|
||||||
width: 6em;
|
|
||||||
grid-template-rows: 6em 3.5em;
|
|
||||||
height: 9.5em;
|
|
||||||
}
|
|
||||||
.modifier-card-size_-2 .modifier-card-image-overlay {
|
|
||||||
font-size: 3em;
|
|
||||||
}
|
|
||||||
.modifier-card-size_-3 {
|
|
||||||
width: 5em;
|
width: 5em;
|
||||||
grid-template-rows: 5em 3.5em;
|
grid-template-rows: 5em 3.5em;
|
||||||
height: 8.5em;
|
height: 8.5em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_-3 .modifier-card-image-overlay {
|
.modifier-card-size_-1 .modifier-card-image-overlay {
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
}
|
}
|
||||||
.modifier-card-size_-3 .modifier-card-label {
|
.modifier-card-size_-1 .modifier-card-label {
|
||||||
font-size: 0.8em;
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.modifier-card-size_-2 {
|
||||||
|
width: 4em;
|
||||||
|
grid-template-rows: 3.5em 3em;
|
||||||
|
height: 6.5em;
|
||||||
|
}
|
||||||
|
.modifier-card-size_-2 .modifier-card-image-overlay {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
.modifier-card-size_-2 .modifier-card-label {
|
||||||
|
font-size: 0.7em;
|
||||||
}
|
}
|
||||||
.modifier-card-tiny {
|
.modifier-card-tiny {
|
||||||
width: 6em;
|
width: 5em;
|
||||||
height: 9.5em;
|
grid-template-rows: 5em 3.5em;
|
||||||
grid-template-rows: 6em 3.5em;
|
height: 8.5em;
|
||||||
}
|
}
|
||||||
.modifier-card-tiny .modifier-card-image-overlay {
|
.modifier-card-tiny .modifier-card-image-overlay {
|
||||||
font-size: 4em;
|
font-size: 4em;
|
||||||
}
|
}
|
||||||
|
.modifier-card-tiny .modifier-card-label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
.modifier-card:hover {
|
.modifier-card:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
box-shadow: 0 5px 16px 5px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 5px 16px 5px rgba(0, 0, 0, 0.25);
|
||||||
@ -115,6 +106,7 @@
|
|||||||
}
|
}
|
||||||
.modifier-card-image-container * {
|
.modifier-card-image-container * {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.modifier-card-container {
|
.modifier-card-container {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -131,6 +123,7 @@
|
|||||||
.modifier-card-label {
|
.modifier-card-label {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
.modifier-card-image-overlay {
|
.modifier-card-image-overlay {
|
||||||
width: inherit;
|
width: inherit;
|
||||||
@ -140,7 +133,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 5px 5px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
font-size: 5em;
|
font-size: 4em;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
color: rgb(255 255 255 / 50%);
|
color: rgb(255 255 255 / 50%);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -153,6 +146,9 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
.modifier-card-active .modifier-card-overlay {
|
||||||
|
background-color: rgb(169 78 241 / 40%);
|
||||||
|
}
|
||||||
.modifier-card:hover > .modifier-card-image-container .modifier-card-image-overlay {
|
.modifier-card:hover > .modifier-card-image-container .modifier-card-image-overlay {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@ -171,53 +167,24 @@
|
|||||||
border: 2px solid rgb(179 82 255 / 94%);
|
border: 2px solid rgb(179 82 255 / 94%);
|
||||||
box-shadow: 0 0px 10px 0 rgb(170 0 229 / 58%);
|
box-shadow: 0 0px 10px 0 rgb(170 0 229 / 58%);
|
||||||
}
|
}
|
||||||
.tooltip {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.tooltip .tooltip-text {
|
|
||||||
visibility: hidden;
|
|
||||||
width: 120px;
|
|
||||||
background: rgb(101,97,181);
|
|
||||||
background: linear-gradient(180deg, rgba(101,97,181,1) 0%, rgba(47,45,85,1) 100%);
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 5px;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
top: 105%;
|
|
||||||
left: 39%;
|
|
||||||
margin-left: -60px;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
border: 2px solid rgb(90 100 177 / 94%);
|
|
||||||
box-shadow: 0px 10px 20px 5px rgb(11 0 58 / 55%);
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
.tooltip .tooltip-text::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: -0.9em;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -5px;
|
|
||||||
border-width: 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent transparent rgb(90 100 177 / 94%) transparent;
|
|
||||||
}
|
|
||||||
.tooltip:hover .tooltip-text {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
#modifier-card-size-slider {
|
#modifier-card-size-slider {
|
||||||
width: 6em;
|
width: 6em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
#modifier-settings-btn {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
#modifier-settings-config textarea {
|
#modifier-settings-config textarea {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
.modifier-card .hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.support-long-label .modifier-card-overlay:hover ~ .modifier-card-container .modifier-card-label {
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
.support-long-label .modifier-card-overlay:hover ~ .modifier-card-container .long-label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.support-long-label .modifier-card-overlay:hover ~ .modifier-card-container .regular-label {
|
||||||
|
display: none;
|
||||||
|
}
|
288
ui/media/css/plugins.css
Normal file
288
ui/media/css/plugins.css
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
.plugins-table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table > div {
|
||||||
|
background: var(--background-color2);
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table > div > div {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table small {
|
||||||
|
color: rgb(153, 153, 153);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table > div > div:nth-child(1) {
|
||||||
|
font-size: 20px;
|
||||||
|
width: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table > div > div:nth-child(2) {
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: start;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table > div > div:nth-child(3) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table > div:first-child {
|
||||||
|
border-radius: 12px 12px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-table > div:last-child {
|
||||||
|
border-radius: 0px 0px 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table > div {
|
||||||
|
background: var(--background-color2);
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table > div > div {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table small {
|
||||||
|
color: rgb(153, 153, 153);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table > div > div:nth-child(1) {
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: start;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table > div > div:nth-child(2) {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table > div:first-child {
|
||||||
|
border-radius: 12px 12px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-table > div:last-child {
|
||||||
|
border-radius: 0px 0px 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIV.no-notification {
|
||||||
|
padding-top: 16px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-manager-intro {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#plugin-filter {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
margin: 4px 0 6px 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#refresh-plugins {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#refresh-plugins a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#refresh-plugins a:active {
|
||||||
|
transition-duration: 0.1s;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-installed-locally {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-source {
|
||||||
|
font-size: x-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-warning {
|
||||||
|
color: orange;
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-warning.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-warning ul {
|
||||||
|
list-style: square;
|
||||||
|
margin: 0 0 8px 16px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-warning li {
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MODAL DIALOG */
|
||||||
|
#pluginDialog-input-dialog {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(32, 33, 36, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-box {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 80%;
|
||||||
|
max-width: 600px;
|
||||||
|
background: var(--background-color2);
|
||||||
|
border: solid 1px var(--background-color3);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0px 0px 30px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-close-button {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-close-button:hover {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-content {
|
||||||
|
padding: 0 16px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-content textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: var(--input-border-radius);
|
||||||
|
padding: 4px;
|
||||||
|
accent-color: var(--accent-color);
|
||||||
|
background: var(--input-background-color);
|
||||||
|
border: var(--input-border-size) solid var(--input-border-color);
|
||||||
|
color: var(--input-text-color);
|
||||||
|
font-size: 9pt;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-buttons button {
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
/*background: var(--accent-color);*/
|
||||||
|
/*border: var(--primary-button-border);*/
|
||||||
|
/*color: rgb(255, 221, 255);*/
|
||||||
|
background-color: #3071a9;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pluginDialog-dialog-buttons button:hover {
|
||||||
|
/*background: hsl(var(--accent-hue), 100%, 50%);*/
|
||||||
|
background-color: #428bca;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOTIFICATION CENTER */
|
||||||
|
#plugin-notification-button {
|
||||||
|
float: right;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#plugin-notification-button:hover {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
#plugin-notification-button:active {
|
||||||
|
transition-duration: 0.1s;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-notification-pill {
|
||||||
|
background-color: red;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: white;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
position: relative;
|
||||||
|
right: -8px;
|
||||||
|
text-align: center;
|
||||||
|
top: -20px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
@ -58,7 +58,7 @@
|
|||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
transition: none;
|
transition: none;
|
||||||
transition:property: none;
|
transition-property: none;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
--input-height: 18px;
|
--input-height: 18px;
|
||||||
--tertiary-background-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (2 * var(--value-step))));
|
--tertiary-background-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (2 * var(--value-step))));
|
||||||
--tertiary-border-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (3 * var(--value-step))));
|
--tertiary-border-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (3 * var(--value-step))));
|
||||||
--tertiary-color: var(--input-text-color)
|
--tertiary-color: var(--input-text-color);
|
||||||
|
|
||||||
/* Main theme color, hex color fallback. */
|
/* Main theme color, hex color fallback. */
|
||||||
--theme-color-fallback: #673AB6;
|
--theme-color-fallback: #673AB6;
|
||||||
|
@ -13,6 +13,7 @@ const SETTINGS_IDS_LIST = [
|
|||||||
"num_outputs_total",
|
"num_outputs_total",
|
||||||
"num_outputs_parallel",
|
"num_outputs_parallel",
|
||||||
"stable_diffusion_model",
|
"stable_diffusion_model",
|
||||||
|
"clip_skip",
|
||||||
"vae_model",
|
"vae_model",
|
||||||
"hypernetwork_model",
|
"hypernetwork_model",
|
||||||
"lora_model",
|
"lora_model",
|
||||||
@ -24,6 +25,7 @@ const SETTINGS_IDS_LIST = [
|
|||||||
"prompt_strength",
|
"prompt_strength",
|
||||||
"hypernetwork_strength",
|
"hypernetwork_strength",
|
||||||
"lora_alpha",
|
"lora_alpha",
|
||||||
|
"tiling",
|
||||||
"output_format",
|
"output_format",
|
||||||
"output_quality",
|
"output_quality",
|
||||||
"output_lossless",
|
"output_lossless",
|
||||||
@ -33,6 +35,7 @@ const SETTINGS_IDS_LIST = [
|
|||||||
"gfpgan_model",
|
"gfpgan_model",
|
||||||
"use_upscale",
|
"use_upscale",
|
||||||
"upscale_amount",
|
"upscale_amount",
|
||||||
|
"latent_upscaler_steps",
|
||||||
"block_nsfw",
|
"block_nsfw",
|
||||||
"show_only_filtered_image",
|
"show_only_filtered_image",
|
||||||
"upscale_model",
|
"upscale_model",
|
||||||
@ -52,27 +55,27 @@ const SETTINGS_IDS_LIST = [
|
|||||||
"auto_scroll",
|
"auto_scroll",
|
||||||
"zip_toggle",
|
"zip_toggle",
|
||||||
"tree_toggle",
|
"tree_toggle",
|
||||||
"json_toggle"
|
"json_toggle",
|
||||||
]
|
]
|
||||||
|
|
||||||
const IGNORE_BY_DEFAULT = [
|
const IGNORE_BY_DEFAULT = ["prompt"]
|
||||||
"prompt"
|
|
||||||
]
|
|
||||||
|
|
||||||
const SETTINGS_SECTIONS = [ // gets the "keys" property filled in with an ordered list of settings in this section via initSettings
|
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" },
|
{ id: "editor-inputs", name: "Prompt" },
|
||||||
{ id: "editor-settings", name: "Image Settings" },
|
{ id: "editor-settings", name: "Image Settings" },
|
||||||
{ id: "system-settings", name: "System Settings" },
|
{ id: "system-settings", name: "System Settings" },
|
||||||
{ id: "container", name: "Other" }
|
{ id: "container", name: "Other" },
|
||||||
]
|
]
|
||||||
|
|
||||||
async function initSettings() {
|
async function initSettings() {
|
||||||
SETTINGS_IDS_LIST.forEach(id => {
|
SETTINGS_IDS_LIST.forEach((id) => {
|
||||||
var element = document.getElementById(id)
|
var element = document.getElementById(id)
|
||||||
if (!element) {
|
if (!element) {
|
||||||
console.error(`Missing settings element ${id}`)
|
console.error(`Missing settings element ${id}`)
|
||||||
}
|
}
|
||||||
if (id in SETTINGS) { // don't create it again
|
if (id in SETTINGS) {
|
||||||
|
// don't create it again
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
SETTINGS[id] = {
|
SETTINGS[id] = {
|
||||||
@ -81,28 +84,28 @@ async function initSettings() {
|
|||||||
label: getSettingLabel(element),
|
label: getSettingLabel(element),
|
||||||
default: getSetting(element),
|
default: getSetting(element),
|
||||||
value: getSetting(element),
|
value: getSetting(element),
|
||||||
ignore: IGNORE_BY_DEFAULT.includes(id)
|
ignore: IGNORE_BY_DEFAULT.includes(id),
|
||||||
}
|
}
|
||||||
element.addEventListener("input", settingChangeHandler)
|
element.addEventListener("input", settingChangeHandler)
|
||||||
element.addEventListener("change", settingChangeHandler)
|
element.addEventListener("change", settingChangeHandler)
|
||||||
})
|
})
|
||||||
var unsorted_settings_ids = [...SETTINGS_IDS_LIST]
|
var unsorted_settings_ids = [...SETTINGS_IDS_LIST]
|
||||||
SETTINGS_SECTIONS.forEach(section => {
|
SETTINGS_SECTIONS.forEach((section) => {
|
||||||
var name = section.name
|
var name = section.name
|
||||||
var element = document.getElementById(section.id)
|
var element = document.getElementById(section.id)
|
||||||
var unsorted_ids = unsorted_settings_ids.map(id => `#${id}`).join(",")
|
var unsorted_ids = unsorted_settings_ids.map((id) => `#${id}`).join(",")
|
||||||
var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids));
|
var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids))
|
||||||
section.keys = []
|
section.keys = []
|
||||||
children.forEach(e => {
|
children.forEach((e) => {
|
||||||
section.keys.push(e.id)
|
section.keys.push(e.id)
|
||||||
})
|
})
|
||||||
unsorted_settings_ids = unsorted_settings_ids.filter(id => children.find(e => e.id == id) == undefined)
|
unsorted_settings_ids = unsorted_settings_ids.filter((id) => children.find((e) => e.id == id) == undefined)
|
||||||
})
|
})
|
||||||
loadSettings()
|
loadSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSetting(element) {
|
function getSetting(element) {
|
||||||
if (element.dataset && 'path' in element.dataset) {
|
if (element.dataset && "path" in element.dataset) {
|
||||||
return element.dataset.path
|
return element.dataset.path
|
||||||
}
|
}
|
||||||
if (typeof element === "string" || element instanceof String) {
|
if (typeof element === "string" || element instanceof String) {
|
||||||
@ -114,7 +117,7 @@ function getSetting(element) {
|
|||||||
return element.value
|
return element.value
|
||||||
}
|
}
|
||||||
function setSetting(element, value) {
|
function setSetting(element, value) {
|
||||||
if (element.dataset && 'path' in element.dataset) {
|
if (element.dataset && "path" in element.dataset) {
|
||||||
element.dataset.path = value
|
element.dataset.path = value
|
||||||
return // no need to dispatch any event here because the models are not loaded yet
|
return // no need to dispatch any event here because the models are not loaded yet
|
||||||
}
|
}
|
||||||
@ -127,8 +130,7 @@ function setSetting(element, value) {
|
|||||||
}
|
}
|
||||||
if (element.type == "checkbox") {
|
if (element.type == "checkbox") {
|
||||||
element.checked = value
|
element.checked = value
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
element.value = value
|
element.value = value
|
||||||
}
|
}
|
||||||
element.dispatchEvent(new Event("input"))
|
element.dispatchEvent(new Event("input"))
|
||||||
@ -136,11 +138,11 @@ function setSetting(element, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
var saved_settings = Object.values(SETTINGS).map(setting => {
|
var saved_settings = Object.values(SETTINGS).map((setting) => {
|
||||||
return {
|
return {
|
||||||
key: setting.key,
|
key: setting.key,
|
||||||
value: setting.value,
|
value: setting.value,
|
||||||
ignore: setting.ignore
|
ignore: setting.ignore,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(saved_settings))
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(saved_settings))
|
||||||
@ -151,16 +153,16 @@ function loadSettings() {
|
|||||||
var saved_settings_text = localStorage.getItem(SETTINGS_KEY)
|
var saved_settings_text = localStorage.getItem(SETTINGS_KEY)
|
||||||
if (saved_settings_text) {
|
if (saved_settings_text) {
|
||||||
var saved_settings = JSON.parse(saved_settings_text)
|
var saved_settings = JSON.parse(saved_settings_text)
|
||||||
if (saved_settings.find(s => s.key == "auto_save_settings")?.value == false) {
|
if (saved_settings.find((s) => s.key == "auto_save_settings")?.value == false) {
|
||||||
setSetting("auto_save_settings", false)
|
setSetting("auto_save_settings", false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
CURRENTLY_LOADING_SETTINGS = true
|
CURRENTLY_LOADING_SETTINGS = true
|
||||||
saved_settings.forEach(saved_setting => {
|
saved_settings.forEach((saved_setting) => {
|
||||||
var setting = SETTINGS[saved_setting.key]
|
var setting = SETTINGS[saved_setting.key]
|
||||||
if (!setting) {
|
if (!setting) {
|
||||||
console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`);
|
console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`)
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
setting.ignore = saved_setting.ignore
|
setting.ignore = saved_setting.ignore
|
||||||
if (!setting.ignore) {
|
if (!setting.ignore) {
|
||||||
@ -169,10 +171,25 @@ function loadSettings() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
CURRENTLY_LOADING_SETTINGS = false
|
CURRENTLY_LOADING_SETTINGS = false
|
||||||
|
} else if (localStorage.length < 2) {
|
||||||
|
// localStorage is too short for OldSettings
|
||||||
|
// So this is likely the first time Easy Diffusion is running.
|
||||||
|
// Initialize vram_usage_level based on the available VRAM
|
||||||
|
function initGPUProfile(event) {
|
||||||
|
if ( "detail" in event
|
||||||
|
&& "active" in event.detail
|
||||||
|
&& "cuda:0" in event.detail.active
|
||||||
|
&& event.detail.active["cuda:0"].mem_total <4.5 )
|
||||||
|
{
|
||||||
|
vramUsageLevelField.value = "low"
|
||||||
|
vramUsageLevelField.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
else {
|
document.removeEventListener("system_info_update", initGPUProfile)
|
||||||
|
}
|
||||||
|
document.addEventListener("system_info_update", initGPUProfile)
|
||||||
|
} else {
|
||||||
CURRENTLY_LOADING_SETTINGS = true
|
CURRENTLY_LOADING_SETTINGS = true
|
||||||
tryLoadOldSettings();
|
tryLoadOldSettings()
|
||||||
CURRENTLY_LOADING_SETTINGS = false
|
CURRENTLY_LOADING_SETTINGS = false
|
||||||
saveSettings()
|
saveSettings()
|
||||||
}
|
}
|
||||||
@ -180,9 +197,9 @@ function loadSettings() {
|
|||||||
|
|
||||||
function loadDefaultSettingsSection(section_id) {
|
function loadDefaultSettingsSection(section_id) {
|
||||||
CURRENTLY_LOADING_SETTINGS = true
|
CURRENTLY_LOADING_SETTINGS = true
|
||||||
var section = SETTINGS_SECTIONS.find(s => s.id == section_id);
|
var section = SETTINGS_SECTIONS.find((s) => s.id == section_id)
|
||||||
section.keys.forEach(key => {
|
section.keys.forEach((key) => {
|
||||||
var setting = SETTINGS[key];
|
var setting = SETTINGS[key]
|
||||||
setting.value = setting.default
|
setting.value = setting.default
|
||||||
setSetting(setting.element, setting.value)
|
setSetting(setting.element, setting.value)
|
||||||
})
|
})
|
||||||
@ -218,10 +235,10 @@ function getSettingLabel(element) {
|
|||||||
|
|
||||||
function fillSaveSettingsConfigTable() {
|
function fillSaveSettingsConfigTable() {
|
||||||
saveSettingsConfigTable.textContent = ""
|
saveSettingsConfigTable.textContent = ""
|
||||||
SETTINGS_SECTIONS.forEach(section => {
|
SETTINGS_SECTIONS.forEach((section) => {
|
||||||
var section_row = `<tr><th>${section.name}</th><td></td></tr>`
|
var section_row = `<tr><th>${section.name}</th><td></td></tr>`
|
||||||
saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row)
|
saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row)
|
||||||
section.keys.forEach(key => {
|
section.keys.forEach((key) => {
|
||||||
var setting = SETTINGS[key]
|
var setting = SETTINGS[key]
|
||||||
var element = setting.element
|
var element = setting.element
|
||||||
var checkbox_id = `shouldsave_${element.id}`
|
var checkbox_id = `shouldsave_${element.id}`
|
||||||
@ -234,7 +251,7 @@ function fillSaveSettingsConfigTable() {
|
|||||||
var newrow = `<tr><td><label for="${checkbox_id}">${setting.label}</label></td><td><input id="${checkbox_id}" name="${checkbox_id}" ${is_checked} type="checkbox" ></td><td><small>(${value})</small></td></tr>`
|
var newrow = `<tr><td><label for="${checkbox_id}">${setting.label}</label></td><td><input id="${checkbox_id}" name="${checkbox_id}" ${is_checked} type="checkbox" ></td><td><small>(${value})</small></td></tr>`
|
||||||
saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow)
|
saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow)
|
||||||
var checkbox = document.getElementById(checkbox_id)
|
var checkbox = document.getElementById(checkbox_id)
|
||||||
checkbox.addEventListener("input", event => {
|
checkbox.addEventListener("input", (event) => {
|
||||||
setting.ignore = !checkbox.checked
|
setting.ignore = !checkbox.checked
|
||||||
saveSettings()
|
saveSettings()
|
||||||
})
|
})
|
||||||
@ -245,9 +262,6 @@ function fillSaveSettingsConfigTable() {
|
|||||||
|
|
||||||
// configureSettingsSaveBtn
|
// configureSettingsSaveBtn
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var autoSaveSettings = document.getElementById("auto_save_settings")
|
var autoSaveSettings = document.getElementById("auto_save_settings")
|
||||||
var configSettingsButton = document.createElement("button")
|
var configSettingsButton = document.createElement("button")
|
||||||
configSettingsButton.textContent = "Configure"
|
configSettingsButton.textContent = "Configure"
|
||||||
@ -256,33 +270,32 @@ autoSaveSettings.insertAdjacentElement("beforebegin", configSettingsButton)
|
|||||||
autoSaveSettings.addEventListener("change", () => {
|
autoSaveSettings.addEventListener("change", () => {
|
||||||
configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none"
|
configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none"
|
||||||
})
|
})
|
||||||
configSettingsButton.addEventListener('click', () => {
|
configSettingsButton.addEventListener("click", () => {
|
||||||
fillSaveSettingsConfigTable()
|
fillSaveSettingsConfigTable()
|
||||||
saveSettingsConfigOverlay.classList.add("active")
|
saveSettingsConfigOverlay.classList.add("active")
|
||||||
})
|
})
|
||||||
resetImageSettingsButton.addEventListener('click', event => {
|
resetImageSettingsButton.addEventListener("click", (event) => {
|
||||||
loadDefaultSettingsSection("editor-settings");
|
loadDefaultSettingsSection("editor-settings")
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
function tryLoadOldSettings() {
|
function tryLoadOldSettings() {
|
||||||
console.log("Loading old user settings")
|
console.log("Loading old user settings")
|
||||||
// load v1 auto-save.js settings
|
// load v1 auto-save.js settings
|
||||||
var old_map = {
|
var old_map = {
|
||||||
"guidance_scale_slider": "guidance_scale",
|
guidance_scale_slider: "guidance_scale",
|
||||||
"prompt_strength_slider": "prompt_strength"
|
prompt_strength_slider: "prompt_strength",
|
||||||
}
|
}
|
||||||
var settings_key_v1 = "user_settings"
|
var settings_key_v1 = "user_settings"
|
||||||
var saved_settings_text = localStorage.getItem(settings_key_v1)
|
var saved_settings_text = localStorage.getItem(settings_key_v1)
|
||||||
if (saved_settings_text) {
|
if (saved_settings_text) {
|
||||||
var saved_settings = JSON.parse(saved_settings_text)
|
var saved_settings = JSON.parse(saved_settings_text)
|
||||||
Object.keys(saved_settings.should_save).forEach(key => {
|
Object.keys(saved_settings.should_save).forEach((key) => {
|
||||||
key = key in old_map ? old_map[key] : key
|
key = key in old_map ? old_map[key] : key
|
||||||
if (!(key in SETTINGS)) return
|
if (!(key in SETTINGS)) return
|
||||||
SETTINGS[key].ignore = !saved_settings.should_save[key]
|
SETTINGS[key].ignore = !saved_settings.should_save[key]
|
||||||
});
|
})
|
||||||
Object.keys(saved_settings.values).forEach(key => {
|
Object.keys(saved_settings.values).forEach((key) => {
|
||||||
key = key in old_map ? old_map[key] : key
|
key = key in old_map ? old_map[key] : key
|
||||||
if (!(key in SETTINGS)) return
|
if (!(key in SETTINGS)) return
|
||||||
var setting = SETTINGS[key]
|
var setting = SETTINGS[key]
|
||||||
@ -290,38 +303,42 @@ function tryLoadOldSettings() {
|
|||||||
setting.value = saved_settings.values[key]
|
setting.value = saved_settings.values[key]
|
||||||
setSetting(setting.element, setting.value)
|
setSetting(setting.element, setting.value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
localStorage.removeItem(settings_key_v1)
|
localStorage.removeItem(settings_key_v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// load old individually stored items
|
// load old individually stored items
|
||||||
var individual_settings_map = { // maps old localStorage-key to new SETTINGS-key
|
var individual_settings_map = {
|
||||||
"soundEnabled": "sound_toggle",
|
// maps old localStorage-key to new SETTINGS-key
|
||||||
"saveToDisk": "save_to_disk",
|
soundEnabled: "sound_toggle",
|
||||||
"useCPU": "use_cpu",
|
saveToDisk: "save_to_disk",
|
||||||
"diskPath": "diskPath",
|
useCPU: "use_cpu",
|
||||||
"useFaceCorrection": "use_face_correction",
|
diskPath: "diskPath",
|
||||||
"useUpscaling": "use_upscale",
|
useFaceCorrection: "use_face_correction",
|
||||||
"showOnlyFilteredImage": "show_only_filtered_image",
|
useUpscaling: "use_upscale",
|
||||||
"streamImageProgress": "stream_image_progress",
|
showOnlyFilteredImage: "show_only_filtered_image",
|
||||||
"outputFormat": "output_format",
|
streamImageProgress: "stream_image_progress",
|
||||||
"autoSaveSettings": "auto_save_settings",
|
outputFormat: "output_format",
|
||||||
};
|
autoSaveSettings: "auto_save_settings",
|
||||||
Object.keys(individual_settings_map).forEach(localStorageKey => {
|
}
|
||||||
var localStorageValue = localStorage.getItem(localStorageKey);
|
Object.keys(individual_settings_map).forEach((localStorageKey) => {
|
||||||
|
var localStorageValue = localStorage.getItem(localStorageKey)
|
||||||
if (localStorageValue !== null) {
|
if (localStorageValue !== null) {
|
||||||
let key = individual_settings_map[localStorageKey]
|
let key = individual_settings_map[localStorageKey]
|
||||||
var setting = SETTINGS[key]
|
var setting = SETTINGS[key]
|
||||||
if (!setting) {
|
if (!setting) {
|
||||||
console.warn(`Attempted to map old setting ${key}, but no setting found`);
|
console.warn(`Attempted to map old setting ${key}, but no setting found`)
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
if (setting.element.type == "checkbox" && (typeof localStorageValue === "string" || localStorageValue instanceof String)) {
|
if (
|
||||||
|
setting.element.type == "checkbox" &&
|
||||||
|
(typeof localStorageValue === "string" || localStorageValue instanceof String)
|
||||||
|
) {
|
||||||
localStorageValue = localStorageValue == "true"
|
localStorageValue = localStorageValue == "true"
|
||||||
}
|
}
|
||||||
setting.value = localStorageValue
|
setting.value = localStorageValue
|
||||||
setSetting(setting.element, setting.value)
|
setSetting(setting.element, setting.value)
|
||||||
localStorage.removeItem(localStorageKey);
|
localStorage.removeItem(localStorageKey)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
"use strict" // Opt in to a restricted variant of JavaScript
|
"use strict" // Opt in to a restricted variant of JavaScript
|
||||||
|
|
||||||
const EXT_REGEX = /(?:\.([^.]+))?$/
|
const EXT_REGEX = /(?:\.([^.]+))?$/
|
||||||
const TEXT_EXTENSIONS = ['txt', 'json']
|
const TEXT_EXTENSIONS = ["txt", "json"]
|
||||||
const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'tga', 'webp']
|
const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "bmp", "tiff", "tif", "tga", "webp"]
|
||||||
|
|
||||||
function parseBoolean(stringValue) {
|
function parseBoolean(stringValue) {
|
||||||
if (typeof stringValue === 'boolean') {
|
if (typeof stringValue === "boolean") {
|
||||||
return stringValue
|
return stringValue
|
||||||
}
|
}
|
||||||
if (typeof stringValue === 'number') {
|
if (typeof stringValue === "number") {
|
||||||
return stringValue !== 0
|
return stringValue !== 0
|
||||||
}
|
}
|
||||||
if (typeof stringValue !== 'string') {
|
if (typeof stringValue !== "string") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch(stringValue?.toLowerCase()?.trim()) {
|
switch (stringValue?.toLowerCase()?.trim()) {
|
||||||
case "true":
|
case "true":
|
||||||
case "yes":
|
case "yes":
|
||||||
case "on":
|
case "on":
|
||||||
case "1":
|
case "1":
|
||||||
return true;
|
return true
|
||||||
|
|
||||||
case "false":
|
case "false":
|
||||||
case "no":
|
case "no":
|
||||||
@ -28,67 +28,77 @@ function parseBoolean(stringValue) {
|
|||||||
case "none":
|
case "none":
|
||||||
case null:
|
case null:
|
||||||
case undefined:
|
case undefined:
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Boolean(JSON.parse(stringValue));
|
return Boolean(JSON.parse(stringValue))
|
||||||
} catch {
|
} catch {
|
||||||
return Boolean(stringValue)
|
return Boolean(stringValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keep in sync with `ui/easydiffusion/utils/save_utils.py`
|
||||||
const TASK_MAPPING = {
|
const TASK_MAPPING = {
|
||||||
prompt: { name: 'Prompt',
|
prompt: {
|
||||||
|
name: "Prompt",
|
||||||
setUI: (prompt) => {
|
setUI: (prompt) => {
|
||||||
promptField.value = prompt
|
promptField.value = prompt
|
||||||
},
|
},
|
||||||
readUI: () => promptField.value,
|
readUI: () => promptField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
negative_prompt: { name: 'Negative Prompt',
|
negative_prompt: {
|
||||||
|
name: "Negative Prompt",
|
||||||
setUI: (negative_prompt) => {
|
setUI: (negative_prompt) => {
|
||||||
negativePromptField.value = negative_prompt
|
negativePromptField.value = negative_prompt
|
||||||
},
|
},
|
||||||
readUI: () => negativePromptField.value,
|
readUI: () => negativePromptField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
active_tags: { name: "Image Modifiers",
|
active_tags: {
|
||||||
|
name: "Image Modifiers",
|
||||||
setUI: (active_tags) => {
|
setUI: (active_tags) => {
|
||||||
refreshModifiersState(active_tags)
|
refreshModifiersState(active_tags)
|
||||||
},
|
},
|
||||||
readUI: () => activeTags.map(x => x.name),
|
readUI: () => activeTags.map((x) => x.name),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
inactive_tags: { name: "Inactive Image Modifiers",
|
inactive_tags: {
|
||||||
|
name: "Inactive Image Modifiers",
|
||||||
setUI: (inactive_tags) => {
|
setUI: (inactive_tags) => {
|
||||||
refreshInactiveTags(inactive_tags)
|
refreshInactiveTags(inactive_tags)
|
||||||
},
|
},
|
||||||
readUI: () => activeTags.filter(tag => tag.inactive === true).map(x => x.name),
|
readUI: () => activeTags.filter((tag) => tag.inactive === true).map((x) => x.name),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
width: { name: 'Width',
|
width: {
|
||||||
|
name: "Width",
|
||||||
setUI: (width) => {
|
setUI: (width) => {
|
||||||
const oldVal = widthField.value
|
const oldVal = widthField.value
|
||||||
widthField.value = width
|
widthField.value = width
|
||||||
if (!widthField.value) {
|
if (!widthField.value) {
|
||||||
widthField.value = oldVal
|
widthField.value = oldVal
|
||||||
}
|
}
|
||||||
|
widthField.dispatchEvent(new Event("change"))
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(widthField.value),
|
readUI: () => parseInt(widthField.value),
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
height: { name: 'Height',
|
height: {
|
||||||
|
name: "Height",
|
||||||
setUI: (height) => {
|
setUI: (height) => {
|
||||||
const oldVal = heightField.value
|
const oldVal = heightField.value
|
||||||
heightField.value = height
|
heightField.value = height
|
||||||
if (!heightField.value) {
|
if (!heightField.value) {
|
||||||
heightField.value = oldVal
|
heightField.value = oldVal
|
||||||
}
|
}
|
||||||
|
heightField.dispatchEvent(new Event("change"))
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(heightField.value),
|
readUI: () => parseInt(heightField.value),
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
seed: { name: 'Seed',
|
seed: {
|
||||||
|
name: "Seed",
|
||||||
setUI: (seed) => {
|
setUI: (seed) => {
|
||||||
if (!seed) {
|
if (!seed) {
|
||||||
randomSeedField.checked = true
|
randomSeedField.checked = true
|
||||||
@ -97,89 +107,108 @@ const TASK_MAPPING = {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
randomSeedField.checked = false
|
randomSeedField.checked = false
|
||||||
randomSeedField.dispatchEvent(new Event('change')) // let plugins know that the state of the random seed toggle changed
|
randomSeedField.dispatchEvent(new Event("change")) // let plugins know that the state of the random seed toggle changed
|
||||||
seedField.disabled = false
|
seedField.disabled = false
|
||||||
seedField.value = seed
|
seedField.value = seed
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI
|
readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
num_inference_steps: { name: 'Steps',
|
num_inference_steps: {
|
||||||
|
name: "Steps",
|
||||||
setUI: (num_inference_steps) => {
|
setUI: (num_inference_steps) => {
|
||||||
numInferenceStepsField.value = num_inference_steps
|
numInferenceStepsField.value = num_inference_steps
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(numInferenceStepsField.value),
|
readUI: () => parseInt(numInferenceStepsField.value),
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
guidance_scale: { name: 'Guidance Scale',
|
guidance_scale: {
|
||||||
|
name: "Guidance Scale",
|
||||||
setUI: (guidance_scale) => {
|
setUI: (guidance_scale) => {
|
||||||
guidanceScaleField.value = guidance_scale
|
guidanceScaleField.value = guidance_scale
|
||||||
updateGuidanceScaleSlider()
|
updateGuidanceScaleSlider()
|
||||||
},
|
},
|
||||||
readUI: () => parseFloat(guidanceScaleField.value),
|
readUI: () => parseFloat(guidanceScaleField.value),
|
||||||
parse: (val) => parseFloat(val)
|
parse: (val) => parseFloat(val),
|
||||||
},
|
},
|
||||||
prompt_strength: { name: 'Prompt Strength',
|
prompt_strength: {
|
||||||
|
name: "Prompt Strength",
|
||||||
setUI: (prompt_strength) => {
|
setUI: (prompt_strength) => {
|
||||||
promptStrengthField.value = prompt_strength
|
promptStrengthField.value = prompt_strength
|
||||||
updatePromptStrengthSlider()
|
updatePromptStrengthSlider()
|
||||||
},
|
},
|
||||||
readUI: () => parseFloat(promptStrengthField.value),
|
readUI: () => parseFloat(promptStrengthField.value),
|
||||||
parse: (val) => parseFloat(val)
|
parse: (val) => parseFloat(val),
|
||||||
},
|
},
|
||||||
|
|
||||||
init_image: { name: 'Initial Image',
|
init_image: {
|
||||||
|
name: "Initial Image",
|
||||||
setUI: (init_image) => {
|
setUI: (init_image) => {
|
||||||
initImagePreview.src = init_image
|
initImagePreview.src = init_image
|
||||||
},
|
},
|
||||||
readUI: () => initImagePreview.src,
|
readUI: () => initImagePreview.src,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
mask: { name: 'Mask',
|
mask: {
|
||||||
|
name: "Mask",
|
||||||
setUI: (mask) => {
|
setUI: (mask) => {
|
||||||
setTimeout(() => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
|
setTimeout(() => {
|
||||||
|
// add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
|
||||||
imageInpainter.setImg(mask)
|
imageInpainter.setImg(mask)
|
||||||
}, 250)
|
}, 250)
|
||||||
maskSetting.checked = Boolean(mask)
|
maskSetting.checked = Boolean(mask)
|
||||||
},
|
},
|
||||||
readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined),
|
readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
preserve_init_image_color_profile: { name: 'Preserve Color Profile',
|
preserve_init_image_color_profile: {
|
||||||
|
name: "Preserve Color Profile",
|
||||||
setUI: (preserve_init_image_color_profile) => {
|
setUI: (preserve_init_image_color_profile) => {
|
||||||
applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile)
|
applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile)
|
||||||
},
|
},
|
||||||
readUI: () => applyColorCorrectionField.checked,
|
readUI: () => applyColorCorrectionField.checked,
|
||||||
parse: (val) => parseBoolean(val)
|
parse: (val) => parseBoolean(val),
|
||||||
},
|
},
|
||||||
|
|
||||||
use_face_correction: { name: 'Use Face Correction',
|
use_face_correction: {
|
||||||
|
name: "Use Face Correction",
|
||||||
setUI: (use_face_correction) => {
|
setUI: (use_face_correction) => {
|
||||||
const oldVal = gfpganModelField.value
|
const oldVal = gfpganModelField.value
|
||||||
gfpganModelField.value = getModelPath(use_face_correction, ['.pth'])
|
console.log("use face correction", use_face_correction)
|
||||||
if (gfpganModelField.value) { // Is a valid value for the field.
|
if (use_face_correction == null || use_face_correction == "None") {
|
||||||
|
gfpganModelField.disabled = true
|
||||||
|
useFaceCorrectionField.checked = false
|
||||||
|
} else {
|
||||||
|
gfpganModelField.value = getModelPath(use_face_correction, [".pth"])
|
||||||
|
if (gfpganModelField.value) {
|
||||||
|
// Is a valid value for the field.
|
||||||
useFaceCorrectionField.checked = true
|
useFaceCorrectionField.checked = true
|
||||||
gfpganModelField.disabled = false
|
gfpganModelField.disabled = false
|
||||||
} else { // Not a valid value, restore the old value and disable the filter.
|
} else {
|
||||||
|
// Not a valid value, restore the old value and disable the filter.
|
||||||
gfpganModelField.disabled = true
|
gfpganModelField.disabled = true
|
||||||
gfpganModelField.value = oldVal
|
gfpganModelField.value = oldVal
|
||||||
useFaceCorrectionField.checked = false
|
useFaceCorrectionField.checked = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//useFaceCorrectionField.checked = parseBoolean(use_face_correction)
|
//useFaceCorrectionField.checked = parseBoolean(use_face_correction)
|
||||||
},
|
},
|
||||||
readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined),
|
readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_upscale: { name: 'Use Upscaling',
|
use_upscale: {
|
||||||
|
name: "Use Upscaling",
|
||||||
setUI: (use_upscale) => {
|
setUI: (use_upscale) => {
|
||||||
const oldVal = upscaleModelField.value
|
const oldVal = upscaleModelField.value
|
||||||
upscaleModelField.value = getModelPath(use_upscale, ['.pth'])
|
upscaleModelField.value = getModelPath(use_upscale, [".pth"])
|
||||||
if (upscaleModelField.value) { // Is a valid value for the field.
|
if (upscaleModelField.value) {
|
||||||
|
// Is a valid value for the field.
|
||||||
useUpscalingField.checked = true
|
useUpscalingField.checked = true
|
||||||
upscaleModelField.disabled = false
|
upscaleModelField.disabled = false
|
||||||
upscaleAmountField.disabled = false
|
upscaleAmountField.disabled = false
|
||||||
} else { // Not a valid value, restore the old value and disable the filter.
|
} else {
|
||||||
|
// Not a valid value, restore the old value and disable the filter.
|
||||||
upscaleModelField.disabled = true
|
upscaleModelField.disabled = true
|
||||||
upscaleAmountField.disabled = true
|
upscaleAmountField.disabled = true
|
||||||
upscaleModelField.value = oldVal
|
upscaleModelField.value = oldVal
|
||||||
@ -187,27 +216,38 @@ const TASK_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined),
|
readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
upscale_amount: { name: 'Upscale By',
|
upscale_amount: {
|
||||||
|
name: "Upscale By",
|
||||||
setUI: (upscale_amount) => {
|
setUI: (upscale_amount) => {
|
||||||
upscaleAmountField.value = upscale_amount
|
upscaleAmountField.value = upscale_amount
|
||||||
},
|
},
|
||||||
readUI: () => upscaleAmountField.value,
|
readUI: () => upscaleAmountField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
sampler_name: { name: 'Sampler',
|
latent_upscaler_steps: {
|
||||||
|
name: "Latent Upscaler Steps",
|
||||||
|
setUI: (latent_upscaler_steps) => {
|
||||||
|
latentUpscalerStepsField.value = latent_upscaler_steps
|
||||||
|
},
|
||||||
|
readUI: () => latentUpscalerStepsField.value,
|
||||||
|
parse: (val) => val,
|
||||||
|
},
|
||||||
|
sampler_name: {
|
||||||
|
name: "Sampler",
|
||||||
setUI: (sampler_name) => {
|
setUI: (sampler_name) => {
|
||||||
samplerField.value = sampler_name
|
samplerField.value = sampler_name
|
||||||
},
|
},
|
||||||
readUI: () => samplerField.value,
|
readUI: () => samplerField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_stable_diffusion_model: { name: 'Stable Diffusion model',
|
use_stable_diffusion_model: {
|
||||||
|
name: "Stable Diffusion model",
|
||||||
setUI: (use_stable_diffusion_model) => {
|
setUI: (use_stable_diffusion_model) => {
|
||||||
const oldVal = stableDiffusionModelField.value
|
const oldVal = stableDiffusionModelField.value
|
||||||
|
|
||||||
use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, ['.ckpt', '.safetensors'])
|
use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, [".ckpt", ".safetensors"])
|
||||||
stableDiffusionModelField.value = use_stable_diffusion_model
|
stableDiffusionModelField.value = use_stable_diffusion_model
|
||||||
|
|
||||||
if (!stableDiffusionModelField.value) {
|
if (!stableDiffusionModelField.value) {
|
||||||
@ -215,118 +255,162 @@ const TASK_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
readUI: () => stableDiffusionModelField.value,
|
readUI: () => stableDiffusionModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_vae_model: { name: 'VAE model',
|
clip_skip: {
|
||||||
|
name: "Clip Skip",
|
||||||
|
setUI: (value) => {
|
||||||
|
clip_skip.checked = value
|
||||||
|
},
|
||||||
|
readUI: () => clip_skip.checked,
|
||||||
|
parse: (val) => Boolean(val),
|
||||||
|
},
|
||||||
|
tiling: {
|
||||||
|
name: "Tiling",
|
||||||
|
setUI: (val) => {
|
||||||
|
tilingField.value = val
|
||||||
|
},
|
||||||
|
readUI: () => tilingField.value,
|
||||||
|
parse: (val) => val,
|
||||||
|
},
|
||||||
|
use_vae_model: {
|
||||||
|
name: "VAE model",
|
||||||
setUI: (use_vae_model) => {
|
setUI: (use_vae_model) => {
|
||||||
const oldVal = vaeModelField.value
|
const oldVal = vaeModelField.value
|
||||||
use_vae_model = (use_vae_model === undefined || use_vae_model === null || use_vae_model === 'None' ? '' : use_vae_model)
|
use_vae_model =
|
||||||
|
use_vae_model === undefined || use_vae_model === null || use_vae_model === "None" ? "" : use_vae_model
|
||||||
|
|
||||||
if (use_vae_model !== '') {
|
if (use_vae_model !== "") {
|
||||||
use_vae_model = getModelPath(use_vae_model, ['.vae.pt', '.ckpt'])
|
use_vae_model = getModelPath(use_vae_model, [".vae.pt", ".ckpt"])
|
||||||
use_vae_model = use_vae_model !== '' ? use_vae_model : oldVal
|
use_vae_model = use_vae_model !== "" ? use_vae_model : oldVal
|
||||||
}
|
}
|
||||||
vaeModelField.value = use_vae_model
|
vaeModelField.value = use_vae_model
|
||||||
},
|
},
|
||||||
readUI: () => vaeModelField.value,
|
readUI: () => vaeModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_lora_model: { name: 'LoRA model',
|
use_lora_model: {
|
||||||
|
name: "LoRA model",
|
||||||
setUI: (use_lora_model) => {
|
setUI: (use_lora_model) => {
|
||||||
const oldVal = loraModelField.value
|
const oldVal = loraModelField.value
|
||||||
use_lora_model = (use_lora_model === undefined || use_lora_model === null || use_lora_model === 'None' ? '' : use_lora_model)
|
use_lora_model =
|
||||||
|
use_lora_model === undefined || use_lora_model === null || use_lora_model === "None"
|
||||||
|
? ""
|
||||||
|
: use_lora_model
|
||||||
|
|
||||||
if (use_lora_model !== '') {
|
if (use_lora_model !== "") {
|
||||||
use_lora_model = getModelPath(use_lora_model, ['.ckpt', '.safetensors'])
|
use_lora_model = getModelPath(use_lora_model, [".ckpt", ".safetensors"])
|
||||||
use_lora_model = use_lora_model !== '' ? use_lora_model : oldVal
|
use_lora_model = use_lora_model !== "" ? use_lora_model : oldVal
|
||||||
}
|
}
|
||||||
loraModelField.value = use_lora_model
|
loraModelField.value = use_lora_model
|
||||||
},
|
},
|
||||||
readUI: () => loraModelField.value,
|
readUI: () => loraModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_hypernetwork_model: { name: 'Hypernetwork model',
|
lora_alpha: {
|
||||||
|
name: "LoRA Strength",
|
||||||
|
setUI: (lora_alpha) => {
|
||||||
|
loraAlphaField.value = lora_alpha
|
||||||
|
updateLoraAlphaSlider()
|
||||||
|
},
|
||||||
|
readUI: () => parseFloat(loraAlphaField.value),
|
||||||
|
parse: (val) => parseFloat(val),
|
||||||
|
},
|
||||||
|
use_hypernetwork_model: {
|
||||||
|
name: "Hypernetwork model",
|
||||||
setUI: (use_hypernetwork_model) => {
|
setUI: (use_hypernetwork_model) => {
|
||||||
const oldVal = hypernetworkModelField.value
|
const oldVal = hypernetworkModelField.value
|
||||||
use_hypernetwork_model = (use_hypernetwork_model === undefined || use_hypernetwork_model === null || use_hypernetwork_model === 'None' ? '' : use_hypernetwork_model)
|
use_hypernetwork_model =
|
||||||
|
use_hypernetwork_model === undefined ||
|
||||||
|
use_hypernetwork_model === null ||
|
||||||
|
use_hypernetwork_model === "None"
|
||||||
|
? ""
|
||||||
|
: use_hypernetwork_model
|
||||||
|
|
||||||
if (use_hypernetwork_model !== '') {
|
if (use_hypernetwork_model !== "") {
|
||||||
use_hypernetwork_model = getModelPath(use_hypernetwork_model, ['.pt'])
|
use_hypernetwork_model = getModelPath(use_hypernetwork_model, [".pt"])
|
||||||
use_hypernetwork_model = use_hypernetwork_model !== '' ? use_hypernetwork_model : oldVal
|
use_hypernetwork_model = use_hypernetwork_model !== "" ? use_hypernetwork_model : oldVal
|
||||||
}
|
}
|
||||||
hypernetworkModelField.value = use_hypernetwork_model
|
hypernetworkModelField.value = use_hypernetwork_model
|
||||||
hypernetworkModelField.dispatchEvent(new Event('change'))
|
hypernetworkModelField.dispatchEvent(new Event("change"))
|
||||||
},
|
},
|
||||||
readUI: () => hypernetworkModelField.value,
|
readUI: () => hypernetworkModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
hypernetwork_strength: { name: 'Hypernetwork Strength',
|
hypernetwork_strength: {
|
||||||
|
name: "Hypernetwork Strength",
|
||||||
setUI: (hypernetwork_strength) => {
|
setUI: (hypernetwork_strength) => {
|
||||||
hypernetworkStrengthField.value = hypernetwork_strength
|
hypernetworkStrengthField.value = hypernetwork_strength
|
||||||
updateHypernetworkStrengthSlider()
|
updateHypernetworkStrengthSlider()
|
||||||
},
|
},
|
||||||
readUI: () => parseFloat(hypernetworkStrengthField.value),
|
readUI: () => parseFloat(hypernetworkStrengthField.value),
|
||||||
parse: (val) => parseFloat(val)
|
parse: (val) => parseFloat(val),
|
||||||
},
|
},
|
||||||
|
|
||||||
num_outputs: { name: 'Parallel Images',
|
num_outputs: {
|
||||||
|
name: "Parallel Images",
|
||||||
setUI: (num_outputs) => {
|
setUI: (num_outputs) => {
|
||||||
numOutputsParallelField.value = num_outputs
|
numOutputsParallelField.value = num_outputs
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(numOutputsParallelField.value),
|
readUI: () => parseInt(numOutputsParallelField.value),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
|
|
||||||
use_cpu: { name: 'Use CPU',
|
use_cpu: {
|
||||||
|
name: "Use CPU",
|
||||||
setUI: (use_cpu) => {
|
setUI: (use_cpu) => {
|
||||||
useCPUField.checked = use_cpu
|
useCPUField.checked = use_cpu
|
||||||
},
|
},
|
||||||
readUI: () => useCPUField.checked,
|
readUI: () => useCPUField.checked,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
|
|
||||||
stream_image_progress: { name: 'Stream Image Progress',
|
stream_image_progress: {
|
||||||
|
name: "Stream Image Progress",
|
||||||
setUI: (stream_image_progress) => {
|
setUI: (stream_image_progress) => {
|
||||||
streamImageProgressField.checked = (parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress)
|
streamImageProgressField.checked = parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress
|
||||||
},
|
},
|
||||||
readUI: () => streamImageProgressField.checked,
|
readUI: () => streamImageProgressField.checked,
|
||||||
parse: (val) => Boolean(val)
|
parse: (val) => Boolean(val),
|
||||||
},
|
},
|
||||||
show_only_filtered_image: { name: 'Show only the corrected/upscaled image',
|
show_only_filtered_image: {
|
||||||
|
name: "Show only the corrected/upscaled image",
|
||||||
setUI: (show_only_filtered_image) => {
|
setUI: (show_only_filtered_image) => {
|
||||||
showOnlyFilteredImageField.checked = show_only_filtered_image
|
showOnlyFilteredImageField.checked = show_only_filtered_image
|
||||||
},
|
},
|
||||||
readUI: () => showOnlyFilteredImageField.checked,
|
readUI: () => showOnlyFilteredImageField.checked,
|
||||||
parse: (val) => Boolean(val)
|
parse: (val) => Boolean(val),
|
||||||
},
|
},
|
||||||
output_format: { name: 'Output Format',
|
output_format: {
|
||||||
|
name: "Output Format",
|
||||||
setUI: (output_format) => {
|
setUI: (output_format) => {
|
||||||
outputFormatField.value = output_format
|
outputFormatField.value = output_format
|
||||||
},
|
},
|
||||||
readUI: () => outputFormatField.value,
|
readUI: () => outputFormatField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
save_to_disk_path: { name: 'Save to disk path',
|
save_to_disk_path: {
|
||||||
|
name: "Save to disk path",
|
||||||
setUI: (save_to_disk_path) => {
|
setUI: (save_to_disk_path) => {
|
||||||
saveToDiskField.checked = Boolean(save_to_disk_path)
|
saveToDiskField.checked = Boolean(save_to_disk_path)
|
||||||
diskPathField.value = save_to_disk_path
|
diskPathField.value = save_to_disk_path
|
||||||
},
|
},
|
||||||
readUI: () => diskPathField.value,
|
readUI: () => diskPathField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreTaskToUI(task, fieldsToSkip) {
|
function restoreTaskToUI(task, fieldsToSkip) {
|
||||||
fieldsToSkip = fieldsToSkip || []
|
fieldsToSkip = fieldsToSkip || []
|
||||||
|
|
||||||
if ('numOutputsTotal' in task) {
|
if ("numOutputsTotal" in task) {
|
||||||
numOutputsTotalField.value = task.numOutputsTotal
|
numOutputsTotalField.value = task.numOutputsTotal
|
||||||
}
|
}
|
||||||
if ('seed' in task) {
|
if ("seed" in task) {
|
||||||
randomSeedField.checked = false
|
randomSeedField.checked = false
|
||||||
seedField.value = task.seed
|
seedField.value = task.seed
|
||||||
}
|
}
|
||||||
if (!('reqBody' in task)) {
|
if (!("reqBody" in task)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (const key in TASK_MAPPING) {
|
for (const key in TASK_MAPPING) {
|
||||||
@ -336,26 +420,32 @@ function restoreTaskToUI(task, fieldsToSkip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// properly reset fields not present in the task
|
// properly reset fields not present in the task
|
||||||
if (!('use_hypernetwork_model' in task.reqBody)) {
|
if (!("use_hypernetwork_model" in task.reqBody)) {
|
||||||
hypernetworkModelField.value = ""
|
hypernetworkModelField.value = ""
|
||||||
hypernetworkModelField.dispatchEvent(new Event("change"))
|
hypernetworkModelField.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d)
|
if (!("use_lora_model" in task.reqBody)) {
|
||||||
promptField.value = task.reqBody.original_prompt
|
loraModelField.value = ""
|
||||||
if (!('original_prompt' in task.reqBody)) {
|
loraModelField.dispatchEvent(new Event("change"))
|
||||||
promptField.value = task.reqBody.prompt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d)
|
||||||
|
promptField.value = task.reqBody.original_prompt
|
||||||
|
if (!("original_prompt" in task.reqBody)) {
|
||||||
|
promptField.value = task.reqBody.prompt
|
||||||
|
}
|
||||||
|
promptField.dispatchEvent(new Event("input"))
|
||||||
|
|
||||||
// properly reset checkboxes
|
// properly reset checkboxes
|
||||||
if (!('use_face_correction' in task.reqBody)) {
|
if (!("use_face_correction" in task.reqBody)) {
|
||||||
useFaceCorrectionField.checked = false
|
useFaceCorrectionField.checked = false
|
||||||
gfpganModelField.disabled = true
|
gfpganModelField.disabled = true
|
||||||
}
|
}
|
||||||
if (!('use_upscale' in task.reqBody)) {
|
if (!("use_upscale" in task.reqBody)) {
|
||||||
useUpscalingField.checked = false
|
useUpscalingField.checked = false
|
||||||
}
|
}
|
||||||
if (!('mask' in task.reqBody) && maskSetting.checked) {
|
if (!("mask" in task.reqBody) && maskSetting.checked) {
|
||||||
maskSetting.checked = false
|
maskSetting.checked = false
|
||||||
maskSetting.dispatchEvent(new Event("click"))
|
maskSetting.dispatchEvent(new Event("click"))
|
||||||
}
|
}
|
||||||
@ -366,15 +456,18 @@ function restoreTaskToUI(task, fieldsToSkip) {
|
|||||||
if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) {
|
if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) {
|
||||||
// hide source image
|
// hide source image
|
||||||
initImageClearBtn.dispatchEvent(new Event("click"))
|
initImageClearBtn.dispatchEvent(new Event("click"))
|
||||||
}
|
} else if (task.reqBody.init_image !== undefined) {
|
||||||
else if (task.reqBody.init_image !== undefined) {
|
|
||||||
// listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpainter)
|
// listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpainter)
|
||||||
initImagePreview.addEventListener('load', function() {
|
initImagePreview.addEventListener(
|
||||||
|
"load",
|
||||||
|
function() {
|
||||||
if (Boolean(task.reqBody.mask)) {
|
if (Boolean(task.reqBody.mask)) {
|
||||||
imageInpainter.setImg(task.reqBody.mask)
|
imageInpainter.setImg(task.reqBody.mask)
|
||||||
maskSetting.checked = true
|
maskSetting.checked = true
|
||||||
}
|
}
|
||||||
}, { once: true })
|
},
|
||||||
|
{ once: true }
|
||||||
|
)
|
||||||
initImagePreview.src = task.reqBody.init_image
|
initImagePreview.src = task.reqBody.init_image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -384,28 +477,26 @@ function readUI() {
|
|||||||
reqBody[key] = TASK_MAPPING[key].readUI()
|
reqBody[key] = TASK_MAPPING[key].readUI()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'numOutputsTotal': parseInt(numOutputsTotalField.value),
|
numOutputsTotal: parseInt(numOutputsTotalField.value),
|
||||||
'seed': TASK_MAPPING['seed'].readUI(),
|
seed: TASK_MAPPING["seed"].readUI(),
|
||||||
'reqBody': reqBody
|
reqBody: reqBody,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getModelPath(filename, extensions)
|
function getModelPath(filename, extensions) {
|
||||||
{
|
|
||||||
if (typeof filename !== "string") {
|
if (typeof filename !== "string") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let pathIdx
|
let pathIdx
|
||||||
if (filename.includes('/models/stable-diffusion/')) {
|
if (filename.includes("/models/stable-diffusion/")) {
|
||||||
pathIdx = filename.indexOf('/models/stable-diffusion/') + 25 // Linux, Mac paths
|
pathIdx = filename.indexOf("/models/stable-diffusion/") + 25 // Linux, Mac paths
|
||||||
}
|
} else if (filename.includes("\\models\\stable-diffusion\\")) {
|
||||||
else if (filename.includes('\\models\\stable-diffusion\\')) {
|
pathIdx = filename.indexOf("\\models\\stable-diffusion\\") + 25 // Linux, Mac paths
|
||||||
pathIdx = filename.indexOf('\\models\\stable-diffusion\\') + 25 // Linux, Mac paths
|
|
||||||
}
|
}
|
||||||
if (pathIdx >= 0) {
|
if (pathIdx >= 0) {
|
||||||
filename = filename.slice(pathIdx)
|
filename = filename.slice(pathIdx)
|
||||||
}
|
}
|
||||||
extensions.forEach(ext => {
|
extensions.forEach((ext) => {
|
||||||
if (filename.endsWith(ext)) {
|
if (filename.endsWith(ext)) {
|
||||||
filename = filename.slice(0, filename.length - ext.length)
|
filename = filename.slice(0, filename.length - ext.length)
|
||||||
}
|
}
|
||||||
@ -414,26 +505,26 @@ function getModelPath(filename, extensions)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TASK_TEXT_MAPPING = {
|
const TASK_TEXT_MAPPING = {
|
||||||
prompt: 'Prompt',
|
prompt: "Prompt",
|
||||||
width: 'Width',
|
width: "Width",
|
||||||
height: 'Height',
|
height: "Height",
|
||||||
seed: 'Seed',
|
seed: "Seed",
|
||||||
num_inference_steps: 'Steps',
|
num_inference_steps: "Steps",
|
||||||
guidance_scale: 'Guidance Scale',
|
guidance_scale: "Guidance Scale",
|
||||||
prompt_strength: 'Prompt Strength',
|
prompt_strength: "Prompt Strength",
|
||||||
use_face_correction: 'Use Face Correction',
|
use_face_correction: "Use Face Correction",
|
||||||
use_upscale: 'Use Upscaling',
|
use_upscale: "Use Upscaling",
|
||||||
upscale_amount: 'Upscale By',
|
upscale_amount: "Upscale By",
|
||||||
sampler_name: 'Sampler',
|
sampler_name: "Sampler",
|
||||||
negative_prompt: 'Negative Prompt',
|
negative_prompt: "Negative Prompt",
|
||||||
use_stable_diffusion_model: 'Stable Diffusion model',
|
use_stable_diffusion_model: "Stable Diffusion model",
|
||||||
use_hypernetwork_model: 'Hypernetwork model',
|
use_hypernetwork_model: "Hypernetwork model",
|
||||||
hypernetwork_strength: 'Hypernetwork Strength'
|
hypernetwork_strength: "Hypernetwork Strength",
|
||||||
}
|
}
|
||||||
function parseTaskFromText(str) {
|
function parseTaskFromText(str) {
|
||||||
const taskReqBody = {}
|
const taskReqBody = {}
|
||||||
|
|
||||||
const lines = str.split('\n')
|
const lines = str.split("\n")
|
||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -441,14 +532,14 @@ function parseTaskFromText(str) {
|
|||||||
// Prompt
|
// Prompt
|
||||||
let knownKeyOnFirstLine = false
|
let knownKeyOnFirstLine = false
|
||||||
for (let key in TASK_TEXT_MAPPING) {
|
for (let key in TASK_TEXT_MAPPING) {
|
||||||
if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ':')) {
|
if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ":")) {
|
||||||
knownKeyOnFirstLine = true
|
knownKeyOnFirstLine = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!knownKeyOnFirstLine) {
|
if (!knownKeyOnFirstLine) {
|
||||||
taskReqBody.prompt = lines[0]
|
taskReqBody.prompt = lines[0]
|
||||||
console.log('Prompt:', taskReqBody.prompt)
|
console.log("Prompt:", taskReqBody.prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in TASK_TEXT_MAPPING) {
|
for (const key in TASK_TEXT_MAPPING) {
|
||||||
@ -456,18 +547,18 @@ function parseTaskFromText(str) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = TASK_TEXT_MAPPING[key];
|
const name = TASK_TEXT_MAPPING[key]
|
||||||
let val = undefined
|
let val = undefined
|
||||||
|
|
||||||
const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, 'igm')
|
const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, "igm")
|
||||||
const match = reName.exec(str);
|
const match = reName.exec(str)
|
||||||
if (match) {
|
if (match) {
|
||||||
str = str.slice(0, match.index) + str.slice(match.index + match[0].length)
|
str = str.slice(0, match.index) + str.slice(match.index + match[0].length)
|
||||||
val = match[1]
|
val = match[1]
|
||||||
}
|
}
|
||||||
if (val !== undefined) {
|
if (val !== undefined) {
|
||||||
taskReqBody[key] = TASK_MAPPING[key].parse(val.trim())
|
taskReqBody[key] = TASK_MAPPING[key].parse(val.trim())
|
||||||
console.log(TASK_MAPPING[key].name + ':', taskReqBody[key])
|
console.log(TASK_MAPPING[key].name + ":", taskReqBody[key])
|
||||||
if (!str) {
|
if (!str) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -477,18 +568,19 @@ function parseTaskFromText(str) {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const task = { reqBody: taskReqBody }
|
const task = { reqBody: taskReqBody }
|
||||||
if ('seed' in taskReqBody) {
|
if ("seed" in taskReqBody) {
|
||||||
task.seed = taskReqBody.seed
|
task.seed = taskReqBody.seed
|
||||||
}
|
}
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseContent(text) {
|
async function parseContent(text) {
|
||||||
text = text.trim();
|
text = text.trim()
|
||||||
if (text.startsWith('{') && text.endsWith('}')) {
|
if (text.startsWith("{") && text.endsWith("}")) {
|
||||||
try {
|
try {
|
||||||
const task = JSON.parse(text)
|
const task = JSON.parse(text)
|
||||||
if (!('reqBody' in task)) { // support the format saved to the disk, by the UI
|
if (!("reqBody" in task)) {
|
||||||
|
// support the format saved to the disk, by the UI
|
||||||
task.reqBody = Object.assign({}, task)
|
task.reqBody = Object.assign({}, task)
|
||||||
}
|
}
|
||||||
restoreTaskToUI(task)
|
restoreTaskToUI(task)
|
||||||
@ -500,7 +592,8 @@ async function parseContent(text) {
|
|||||||
}
|
}
|
||||||
// Normal txt file.
|
// Normal txt file.
|
||||||
const task = parseTaskFromText(text)
|
const task = parseTaskFromText(text)
|
||||||
if (text.toLowerCase().includes('seed:') && task) { // only parse valid task content
|
if (text.toLowerCase().includes("seed:") && task) {
|
||||||
|
// only parse valid task content
|
||||||
restoreTaskToUI(task)
|
restoreTaskToUI(task)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@ -517,21 +610,25 @@ async function readFile(file, i) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dropHandler(ev) {
|
function dropHandler(ev) {
|
||||||
console.log('Content dropped...')
|
console.log("Content dropped...")
|
||||||
let items = []
|
let items = []
|
||||||
|
|
||||||
if (ev?.dataTransfer?.items) { // Use DataTransferItemList interface
|
if (ev?.dataTransfer?.items) {
|
||||||
|
// Use DataTransferItemList interface
|
||||||
items = Array.from(ev.dataTransfer.items)
|
items = Array.from(ev.dataTransfer.items)
|
||||||
items = items.filter(item => item.kind === 'file')
|
items = items.filter((item) => item.kind === "file")
|
||||||
items = items.map(item => item.getAsFile())
|
items = items.map((item) => item.getAsFile())
|
||||||
} else if (ev?.dataTransfer?.files) { // Use DataTransfer interface
|
} else if (ev?.dataTransfer?.files) {
|
||||||
|
// Use DataTransfer interface
|
||||||
items = Array.from(ev.dataTransfer.files)
|
items = Array.from(ev.dataTransfer.files)
|
||||||
}
|
}
|
||||||
|
|
||||||
items.forEach(item => {item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1]})
|
items.forEach((item) => {
|
||||||
|
item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1]
|
||||||
|
})
|
||||||
|
|
||||||
let text_items = items.filter(item => TEXT_EXTENSIONS.includes(item.file_ext))
|
let text_items = items.filter((item) => TEXT_EXTENSIONS.includes(item.file_ext))
|
||||||
let image_items = items.filter(item => IMAGE_EXTENSIONS.includes(item.file_ext))
|
let image_items = items.filter((item) => IMAGE_EXTENSIONS.includes(item.file_ext))
|
||||||
|
|
||||||
if (image_items.length > 0 && ev.target == initImageSelector) {
|
if (image_items.length > 0 && ev.target == initImageSelector) {
|
||||||
return // let the event bubble up, so that the Init Image filepicker can receive this
|
return // let the event bubble up, so that the Init Image filepicker can receive this
|
||||||
@ -541,7 +638,7 @@ function dropHandler(ev) {
|
|||||||
text_items.forEach(readFile)
|
text_items.forEach(readFile)
|
||||||
}
|
}
|
||||||
function dragOverHandler(ev) {
|
function dragOverHandler(ev) {
|
||||||
console.log('Content in drop zone')
|
console.log("Content in drop zone")
|
||||||
|
|
||||||
// Prevent default behavior (Prevent file/content from being opened)
|
// Prevent default behavior (Prevent file/content from being opened)
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
@ -549,73 +646,72 @@ function dragOverHandler(ev) {
|
|||||||
ev.dataTransfer.dropEffect = "copy"
|
ev.dataTransfer.dropEffect = "copy"
|
||||||
|
|
||||||
let img = new Image()
|
let img = new Image()
|
||||||
img.src = '//' + location.host + '/media/images/favicon-32x32.png'
|
img.src = "//" + location.host + "/media/images/favicon-32x32.png"
|
||||||
ev.dataTransfer.setDragImage(img, 16, 16)
|
ev.dataTransfer.setDragImage(img, 16, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("drop", dropHandler)
|
document.addEventListener("drop", dropHandler)
|
||||||
document.addEventListener("dragover", dragOverHandler)
|
document.addEventListener("dragover", dragOverHandler)
|
||||||
|
|
||||||
const TASK_REQ_NO_EXPORT = [
|
const TASK_REQ_NO_EXPORT = ["use_cpu", "save_to_disk_path"]
|
||||||
"use_cpu",
|
const resetSettings = document.getElementById("reset-image-settings")
|
||||||
"save_to_disk_path"
|
|
||||||
]
|
|
||||||
const resetSettings = document.getElementById('reset-image-settings')
|
|
||||||
|
|
||||||
function checkReadTextClipboardPermission (result) {
|
function checkReadTextClipboardPermission(result) {
|
||||||
if (result.state != "granted" && result.state != "prompt") {
|
if (result.state != "granted" && result.state != "prompt") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// PASTE ICON
|
// PASTE ICON
|
||||||
const pasteIcon = document.createElement('i')
|
const pasteIcon = document.createElement("i")
|
||||||
pasteIcon.className = 'fa-solid fa-paste section-button'
|
pasteIcon.className = "fa-solid fa-paste section-button"
|
||||||
pasteIcon.innerHTML = `<span class="simple-tooltip top-left">Paste Image Settings</span>`
|
pasteIcon.innerHTML = `<span class="simple-tooltip top-left">Paste Image Settings</span>`
|
||||||
pasteIcon.addEventListener('click', async (event) => {
|
pasteIcon.addEventListener("click", async (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
// Add css class 'active'
|
// Add css class 'active'
|
||||||
pasteIcon.classList.add('active')
|
pasteIcon.classList.add("active")
|
||||||
// In 350 ms remove the 'active' class
|
// In 350 ms remove the 'active' class
|
||||||
asyncDelay(350).then(() => pasteIcon.classList.remove('active'))
|
asyncDelay(350).then(() => pasteIcon.classList.remove("active"))
|
||||||
|
|
||||||
// Retrieve clipboard content and try to parse it
|
// Retrieve clipboard content and try to parse it
|
||||||
const text = await navigator.clipboard.readText();
|
const text = await navigator.clipboard.readText()
|
||||||
await parseContent(text)
|
await parseContent(text)
|
||||||
})
|
})
|
||||||
resetSettings.parentNode.insertBefore(pasteIcon, resetSettings)
|
resetSettings.parentNode.insertBefore(pasteIcon, resetSettings)
|
||||||
}
|
}
|
||||||
navigator.permissions.query({ name: "clipboard-read" }).then(checkReadTextClipboardPermission, (reason) => console.log('clipboard-read is not available. %o', reason))
|
navigator.permissions
|
||||||
|
.query({ name: "clipboard-read" })
|
||||||
|
.then(checkReadTextClipboardPermission, (reason) => console.log("clipboard-read is not available. %o", reason))
|
||||||
|
|
||||||
document.addEventListener('paste', async (event) => {
|
document.addEventListener("paste", async (event) => {
|
||||||
if (event.target) {
|
if (event.target) {
|
||||||
const targetTag = event.target.tagName.toLowerCase()
|
const targetTag = event.target.tagName.toLowerCase()
|
||||||
// Disable when targeting input elements.
|
// Disable when targeting input elements.
|
||||||
if (targetTag === 'input' || targetTag === 'textarea') {
|
if (targetTag === "input" || targetTag === "textarea") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const paste = (event.clipboardData || window.clipboardData).getData('text')
|
const paste = (event.clipboardData || window.clipboardData).getData("text")
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
if (selection.toString().trim().length <= 0 && await parseContent(paste)) {
|
if (paste != "" && selection.toString().trim().length <= 0 && (await parseContent(paste))) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Adds a copy and a paste icon if the browser grants permission to write to clipboard.
|
// Adds a copy and a paste icon if the browser grants permission to write to clipboard.
|
||||||
function checkWriteToClipboardPermission (result) {
|
function checkWriteToClipboardPermission(result) {
|
||||||
if (result.state != "granted" && result.state != "prompt") {
|
if (result.state != "granted" && result.state != "prompt") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// COPY ICON
|
// COPY ICON
|
||||||
const copyIcon = document.createElement('i')
|
const copyIcon = document.createElement("i")
|
||||||
copyIcon.className = 'fa-solid fa-clipboard section-button'
|
copyIcon.className = "fa-solid fa-clipboard section-button"
|
||||||
copyIcon.innerHTML = `<span class="simple-tooltip top-left">Copy Image Settings</span>`
|
copyIcon.innerHTML = `<span class="simple-tooltip top-left">Copy Image Settings</span>`
|
||||||
copyIcon.addEventListener('click', (event) => {
|
copyIcon.addEventListener("click", (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
// Add css class 'active'
|
// Add css class 'active'
|
||||||
copyIcon.classList.add('active')
|
copyIcon.classList.add("active")
|
||||||
// In 350 ms remove the 'active' class
|
// In 350 ms remove the 'active' class
|
||||||
asyncDelay(350).then(() => copyIcon.classList.remove('active'))
|
asyncDelay(350).then(() => copyIcon.classList.remove("active"))
|
||||||
const uiState = readUI()
|
const uiState = readUI()
|
||||||
TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key])
|
TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key])
|
||||||
if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) {
|
if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) {
|
||||||
@ -628,8 +724,8 @@ function checkWriteToClipboardPermission (result) {
|
|||||||
}
|
}
|
||||||
// Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS.
|
// Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS.
|
||||||
navigator.permissions.query({ name: "clipboard-write" }).then(checkWriteToClipboardPermission, (e) => {
|
navigator.permissions.query({ name: "clipboard-write" }).then(checkWriteToClipboardPermission, (e) => {
|
||||||
if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === 'function') {
|
if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === "function") {
|
||||||
// Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373
|
// Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373
|
||||||
checkWriteToClipboardPermission({state:"granted"})
|
checkWriteToClipboardPermission({ state: "granted" })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -6,17 +6,17 @@ const IMAGE_EDITOR_BUTTONS = [
|
|||||||
{
|
{
|
||||||
name: "Cancel",
|
name: "Cancel",
|
||||||
icon: "fa-regular fa-circle-xmark",
|
icon: "fa-regular fa-circle-xmark",
|
||||||
handler: editor => {
|
handler: (editor) => {
|
||||||
editor.hide()
|
editor.hide()
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Save",
|
name: "Save",
|
||||||
icon: "fa-solid fa-floppy-disk",
|
icon: "fa-solid fa-floppy-disk",
|
||||||
handler: editor => {
|
handler: (editor) => {
|
||||||
editor.saveImage()
|
editor.saveImage()
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => {
|
const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => {
|
||||||
@ -46,7 +46,7 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer",
|
cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer",
|
||||||
begin: defaultToolBegin,
|
begin: defaultToolBegin,
|
||||||
move: defaultToolMove,
|
move: defaultToolMove,
|
||||||
end: defaultToolEnd
|
end: defaultToolEnd,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "erase",
|
id: "erase",
|
||||||
@ -76,7 +76,7 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
},
|
},
|
||||||
setBrush: (editor, layer) => {
|
setBrush: (editor, layer) => {
|
||||||
layer.ctx.globalCompositeOperation = "destination-out"
|
layer.ctx.globalCompositeOperation = "destination-out"
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fill",
|
id: "fill",
|
||||||
@ -91,7 +91,7 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: toolDoNothing,
|
move: toolDoNothing,
|
||||||
end: toolDoNothing
|
end: toolDoNothing,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "colorpicker",
|
id: "colorpicker",
|
||||||
@ -104,16 +104,16 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data
|
var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data
|
||||||
var drawn_opacity = drawn_rgb[3] / 255
|
var drawn_opacity = drawn_rgb[3] / 255
|
||||||
editor.custom_color_input.value = rgbToHex({
|
editor.custom_color_input.value = rgbToHex({
|
||||||
r: (drawn_rgb[0] * drawn_opacity) + (img_rgb[0] * (1 - drawn_opacity)),
|
r: drawn_rgb[0] * drawn_opacity + img_rgb[0] * (1 - drawn_opacity),
|
||||||
g: (drawn_rgb[1] * drawn_opacity) + (img_rgb[1] * (1 - drawn_opacity)),
|
g: drawn_rgb[1] * drawn_opacity + img_rgb[1] * (1 - drawn_opacity),
|
||||||
b: (drawn_rgb[2] * drawn_opacity) + (img_rgb[2] * (1 - drawn_opacity)),
|
b: drawn_rgb[2] * drawn_opacity + img_rgb[2] * (1 - drawn_opacity),
|
||||||
})
|
})
|
||||||
editor.custom_color_input.dispatchEvent(new Event("change"))
|
editor.custom_color_input.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: toolDoNothing,
|
move: toolDoNothing,
|
||||||
end: toolDoNothing
|
end: toolDoNothing,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const IMAGE_EDITOR_ACTIONS = [
|
const IMAGE_EDITOR_ACTIONS = [
|
||||||
@ -123,7 +123,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
className: "load_mask",
|
className: "load_mask",
|
||||||
icon: "fa-regular fa-folder-open",
|
icon: "fa-regular fa-folder-open",
|
||||||
handler: (editor) => {
|
handler: (editor) => {
|
||||||
let el = document.createElement('input')
|
let el = document.createElement("input")
|
||||||
el.setAttribute("type", "file")
|
el.setAttribute("type", "file")
|
||||||
el.addEventListener("change", function() {
|
el.addEventListener("change", function() {
|
||||||
if (this.files.length === 0) {
|
if (this.files.length === 0) {
|
||||||
@ -133,7 +133,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
let reader = new FileReader()
|
let reader = new FileReader()
|
||||||
let file = this.files[0]
|
let file = this.files[0]
|
||||||
|
|
||||||
reader.addEventListener('load', function(event) {
|
reader.addEventListener("load", function(event) {
|
||||||
let maskData = reader.result
|
let maskData = reader.result
|
||||||
|
|
||||||
editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height)
|
editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height)
|
||||||
@ -151,7 +151,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
|
|
||||||
el.click()
|
el.click()
|
||||||
},
|
},
|
||||||
trackHistory: true
|
trackHistory: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fill_all",
|
id: "fill_all",
|
||||||
@ -163,7 +163,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
editor.ctx_current.fill()
|
editor.ctx_current.fill()
|
||||||
editor.setBrush()
|
editor.setBrush()
|
||||||
},
|
},
|
||||||
trackHistory: true
|
trackHistory: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "clear",
|
id: "clear",
|
||||||
@ -173,7 +173,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
editor.ctx_current.clearRect(0, 0, editor.width, editor.height)
|
editor.ctx_current.clearRect(0, 0, editor.width, editor.height)
|
||||||
imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas
|
imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas
|
||||||
},
|
},
|
||||||
trackHistory: true
|
trackHistory: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "undo",
|
id: "undo",
|
||||||
@ -182,7 +182,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
handler: (editor) => {
|
handler: (editor) => {
|
||||||
editor.history.undo()
|
editor.history.undo()
|
||||||
},
|
},
|
||||||
trackHistory: false
|
trackHistory: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "redo",
|
id: "redo",
|
||||||
@ -191,8 +191,8 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
handler: (editor) => {
|
handler: (editor) => {
|
||||||
editor.history.redo()
|
editor.history.redo()
|
||||||
},
|
},
|
||||||
trackHistory: false
|
trackHistory: false,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
var IMAGE_EDITOR_SECTIONS = [
|
var IMAGE_EDITOR_SECTIONS = [
|
||||||
@ -200,17 +200,17 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
name: "tool",
|
name: "tool",
|
||||||
title: "Tool",
|
title: "Tool",
|
||||||
default: "draw",
|
default: "draw",
|
||||||
options: Array.from(IMAGE_EDITOR_TOOLS.map(t => t.id)),
|
options: Array.from(IMAGE_EDITOR_TOOLS.map((t) => t.id)),
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
var tool_info = IMAGE_EDITOR_TOOLS.find(t => t.id == option)
|
var tool_info = IMAGE_EDITOR_TOOLS.find((t) => t.id == option)
|
||||||
element.className = "image-editor-button button"
|
element.className = "image-editor-button button"
|
||||||
var sub_element = document.createElement("div")
|
var sub_element = document.createElement("div")
|
||||||
var icon = document.createElement("i")
|
var icon = document.createElement("i")
|
||||||
tool_info.icon.split(" ").forEach(c => icon.classList.add(c))
|
tool_info.icon.split(" ").forEach((c) => icon.classList.add(c))
|
||||||
sub_element.appendChild(icon)
|
sub_element.appendChild(icon)
|
||||||
sub_element.append(tool_info.name)
|
sub_element.append(tool_info.name)
|
||||||
element.appendChild(sub_element)
|
element.appendChild(sub_element)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "color",
|
name: "color",
|
||||||
@ -218,14 +218,46 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
default: "#f1c232",
|
default: "#f1c232",
|
||||||
options: [
|
options: [
|
||||||
"custom",
|
"custom",
|
||||||
"#ea9999", "#e06666", "#cc0000", "#990000", "#660000",
|
"#ea9999",
|
||||||
"#f9cb9c", "#f6b26b", "#e69138", "#b45f06", "#783f04",
|
"#e06666",
|
||||||
"#ffe599", "#ffd966", "#f1c232", "#bf9000", "#7f6000",
|
"#cc0000",
|
||||||
"#b6d7a8", "#93c47d", "#6aa84f", "#38761d", "#274e13",
|
"#990000",
|
||||||
"#a4c2f4", "#6d9eeb", "#3c78d8", "#1155cc", "#1c4587",
|
"#660000",
|
||||||
"#b4a7d6", "#8e7cc3", "#674ea7", "#351c75", "#20124d",
|
"#f9cb9c",
|
||||||
"#d5a6bd", "#c27ba0", "#a64d79", "#741b47", "#4c1130",
|
"#f6b26b",
|
||||||
"#ffffff", "#c0c0c0", "#838383", "#525252", "#000000",
|
"#e69138",
|
||||||
|
"#b45f06",
|
||||||
|
"#783f04",
|
||||||
|
"#ffe599",
|
||||||
|
"#ffd966",
|
||||||
|
"#f1c232",
|
||||||
|
"#bf9000",
|
||||||
|
"#7f6000",
|
||||||
|
"#b6d7a8",
|
||||||
|
"#93c47d",
|
||||||
|
"#6aa84f",
|
||||||
|
"#38761d",
|
||||||
|
"#274e13",
|
||||||
|
"#a4c2f4",
|
||||||
|
"#6d9eeb",
|
||||||
|
"#3c78d8",
|
||||||
|
"#1155cc",
|
||||||
|
"#1c4587",
|
||||||
|
"#b4a7d6",
|
||||||
|
"#8e7cc3",
|
||||||
|
"#674ea7",
|
||||||
|
"#351c75",
|
||||||
|
"#20124d",
|
||||||
|
"#d5a6bd",
|
||||||
|
"#c27ba0",
|
||||||
|
"#a64d79",
|
||||||
|
"#741b47",
|
||||||
|
"#4c1130",
|
||||||
|
"#ffffff",
|
||||||
|
"#c0c0c0",
|
||||||
|
"#838383",
|
||||||
|
"#525252",
|
||||||
|
"#000000",
|
||||||
],
|
],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
if (option == "custom") {
|
if (option == "custom") {
|
||||||
@ -238,43 +270,42 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
input.click()
|
input.click()
|
||||||
}
|
}
|
||||||
element.appendChild(span)
|
element.appendChild(span)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
element.style.background = option
|
element.style.background = option
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getCustom: editor => {
|
getCustom: (editor) => {
|
||||||
var input = editor.popup.querySelector(".image_editor_color input")
|
var input = editor.popup.querySelector(".image_editor_color input")
|
||||||
return input.value
|
return input.value
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "brush_size",
|
name: "brush_size",
|
||||||
title: "Brush Size",
|
title: "Brush Size",
|
||||||
default: 48,
|
default: 48,
|
||||||
options: [ 6, 12, 16, 24, 30, 40, 48, 64 ],
|
options: [6, 12, 16, 24, 30, 40, 48, 64],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
element.parentElement.style.flex = option
|
element.parentElement.style.flex = option
|
||||||
element.style.width = option + "px"
|
element.style.width = option + "px"
|
||||||
element.style.height = option + "px"
|
element.style.height = option + "px"
|
||||||
element.style['margin-right'] = '2px'
|
element.style["margin-right"] = "2px"
|
||||||
element.style["border-radius"] = (option / 2).toFixed() + "px"
|
element.style["border-radius"] = (option / 2).toFixed() + "px"
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "opacity",
|
name: "opacity",
|
||||||
title: "Opacity",
|
title: "Opacity",
|
||||||
default: 0,
|
default: 0,
|
||||||
options: [ 0, 0.2, 0.4, 0.6, 0.8 ],
|
options: [0, 0.2, 0.4, 0.6, 0.8],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px`
|
element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px`
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sharpness",
|
name: "sharpness",
|
||||||
title: "Sharpness",
|
title: "Sharpness",
|
||||||
default: 0,
|
default: 0,
|
||||||
options: [ 0, 0.05, 0.1, 0.2, 0.3 ],
|
options: [0, 0.05, 0.1, 0.2, 0.3],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
var size = 32
|
var size = 32
|
||||||
var blur_amount = parseInt(option * size)
|
var blur_amount = parseInt(option * size)
|
||||||
@ -283,11 +314,11 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
sub_element.style.filter = `blur(${blur_amount}px)`
|
sub_element.style.filter = `blur(${blur_amount}px)`
|
||||||
sub_element.style.width = `${size - 2}px`
|
sub_element.style.width = `${size - 2}px`
|
||||||
sub_element.style.height = `${size - 2}px`
|
sub_element.style.height = `${size - 2}px`
|
||||||
sub_element.style['border-radius'] = `${size}px`
|
sub_element.style["border-radius"] = `${size}px`
|
||||||
element.style.background = "none"
|
element.style.background = "none"
|
||||||
element.appendChild(sub_element)
|
element.appendChild(sub_element)
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
class EditorHistory {
|
class EditorHistory {
|
||||||
@ -312,15 +343,15 @@ class EditorHistory {
|
|||||||
pushAction(action) {
|
pushAction(action) {
|
||||||
this.push({
|
this.push({
|
||||||
type: "action",
|
type: "action",
|
||||||
id: action
|
id: action,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
editBegin(x, y) {
|
editBegin(x, y) {
|
||||||
this.current_edit = {
|
this.current_edit = {
|
||||||
type: "edit",
|
type: "edit",
|
||||||
id: this.editor.getOptionValue("tool"),
|
id: this.editor.getOptionValue("tool"),
|
||||||
options: Object.assign({}, this.editor.options),
|
options: Object.assign({}, this.editor.options),
|
||||||
points: [ { x: x, y: y } ]
|
points: [{ x: x, y: y }],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editMove(x, y) {
|
editMove(x, y) {
|
||||||
@ -345,7 +376,7 @@ class EditorHistory {
|
|||||||
}
|
}
|
||||||
rewindTo(new_rewind_index) {
|
rewindTo(new_rewind_index) {
|
||||||
if (new_rewind_index < 0 || new_rewind_index > this.events.length) {
|
if (new_rewind_index < 0 || new_rewind_index > this.events.length) {
|
||||||
return; // do nothing if target index is out of bounds
|
return // do nothing if target index is out of bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = this.editor.layers.drawing.ctx
|
var ctx = this.editor.layers.drawing.ctx
|
||||||
@ -361,17 +392,16 @@ class EditorHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot_index != -1) {
|
if (snapshot_index != -1) {
|
||||||
ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0);
|
ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = (snapshot_index + 1); i <= target_index; i++) {
|
for (var i = snapshot_index + 1; i <= target_index; i++) {
|
||||||
var event = this.events[i]
|
var event = this.events[i]
|
||||||
if (event.type == "action") {
|
if (event.type == "action") {
|
||||||
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == event.id)
|
var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == event.id)
|
||||||
action.handler(this.editor)
|
action.handler(this.editor)
|
||||||
}
|
} else if (event.type == "edit") {
|
||||||
else if (event.type == "edit") {
|
var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == event.id)
|
||||||
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == event.id)
|
|
||||||
this.editor.setBrush(this.editor.layers.drawing, event.options)
|
this.editor.setBrush(this.editor.layers.drawing, event.options)
|
||||||
|
|
||||||
var first_point = event.points[0]
|
var first_point = event.points[0]
|
||||||
@ -403,19 +433,15 @@ class ImageEditor {
|
|||||||
this.temp_previous_tool = null // used for the ctrl-colorpicker functionality
|
this.temp_previous_tool = null // used for the ctrl-colorpicker functionality
|
||||||
this.container = popup.querySelector(".editor-controls-center > div")
|
this.container = popup.querySelector(".editor-controls-center > div")
|
||||||
this.layers = {}
|
this.layers = {}
|
||||||
var layer_names = [
|
var layer_names = ["background", "drawing", "overlay"]
|
||||||
"background",
|
layer_names.forEach((name) => {
|
||||||
"drawing",
|
|
||||||
"overlay"
|
|
||||||
]
|
|
||||||
layer_names.forEach(name => {
|
|
||||||
let canvas = document.createElement("canvas")
|
let canvas = document.createElement("canvas")
|
||||||
canvas.className = `editor-canvas-${name}`
|
canvas.className = `editor-canvas-${name}`
|
||||||
this.container.appendChild(canvas)
|
this.container.appendChild(canvas)
|
||||||
this.layers[name] = {
|
this.layers[name] = {
|
||||||
name: name,
|
name: name,
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
ctx: canvas.getContext("2d")
|
ctx: canvas.getContext("2d"),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -434,7 +460,7 @@ class ImageEditor {
|
|||||||
// initialize editor controls
|
// initialize editor controls
|
||||||
this.options = {}
|
this.options = {}
|
||||||
this.optionElements = {}
|
this.optionElements = {}
|
||||||
IMAGE_EDITOR_SECTIONS.forEach(section => {
|
IMAGE_EDITOR_SECTIONS.forEach((section) => {
|
||||||
section.id = `image_editor_${section.name}`
|
section.id = `image_editor_${section.name}`
|
||||||
var sectionElement = document.createElement("div")
|
var sectionElement = document.createElement("div")
|
||||||
sectionElement.className = section.id
|
sectionElement.className = section.id
|
||||||
@ -452,7 +478,7 @@ class ImageEditor {
|
|||||||
var optionElement = document.createElement("div")
|
var optionElement = document.createElement("div")
|
||||||
optionHolder.appendChild(optionElement)
|
optionHolder.appendChild(optionElement)
|
||||||
section.initElement(optionElement, option)
|
section.initElement(optionElement, option)
|
||||||
optionElement.addEventListener("click", target => this.selectOption(section.name, index))
|
optionElement.addEventListener("click", (target) => this.selectOption(section.name, index))
|
||||||
optionsContainer.appendChild(optionHolder)
|
optionsContainer.appendChild(optionHolder)
|
||||||
this.optionElements[section.name].push(optionElement)
|
this.optionElements[section.name].push(optionElement)
|
||||||
})
|
})
|
||||||
@ -470,13 +496,13 @@ class ImageEditor {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (this.inpainter) {
|
if (this.inpainter) {
|
||||||
this.selectOption("color", IMAGE_EDITOR_SECTIONS.find(s => s.name == "color").options.indexOf("#ffffff"))
|
this.selectOption("color", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "color").options.indexOf("#ffffff"))
|
||||||
this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find(s => s.name == "opacity").options.indexOf(0.4))
|
this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "opacity").options.indexOf(0.4))
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the right-side controls
|
// initialize the right-side controls
|
||||||
var buttonContainer = document.createElement("div")
|
var buttonContainer = document.createElement("div")
|
||||||
IMAGE_EDITOR_BUTTONS.forEach(button => {
|
IMAGE_EDITOR_BUTTONS.forEach((button) => {
|
||||||
var element = document.createElement("div")
|
var element = document.createElement("div")
|
||||||
var icon = document.createElement("i")
|
var icon = document.createElement("i")
|
||||||
element.className = "image-editor-button button"
|
element.className = "image-editor-button button"
|
||||||
@ -484,13 +510,13 @@ class ImageEditor {
|
|||||||
element.appendChild(icon)
|
element.appendChild(icon)
|
||||||
element.append(button.name)
|
element.append(button.name)
|
||||||
buttonContainer.appendChild(element)
|
buttonContainer.appendChild(element)
|
||||||
element.addEventListener("click", event => button.handler(this))
|
element.addEventListener("click", (event) => button.handler(this))
|
||||||
})
|
})
|
||||||
var actionsContainer = document.createElement("div")
|
var actionsContainer = document.createElement("div")
|
||||||
var actionsTitle = document.createElement("h4")
|
var actionsTitle = document.createElement("h4")
|
||||||
actionsTitle.textContent = "Actions"
|
actionsTitle.textContent = "Actions"
|
||||||
actionsContainer.appendChild(actionsTitle);
|
actionsContainer.appendChild(actionsTitle)
|
||||||
IMAGE_EDITOR_ACTIONS.forEach(action => {
|
IMAGE_EDITOR_ACTIONS.forEach((action) => {
|
||||||
var element = document.createElement("div")
|
var element = document.createElement("div")
|
||||||
var icon = document.createElement("i")
|
var icon = document.createElement("i")
|
||||||
element.className = "image-editor-button button"
|
element.className = "image-editor-button button"
|
||||||
@ -501,7 +527,7 @@ class ImageEditor {
|
|||||||
element.appendChild(icon)
|
element.appendChild(icon)
|
||||||
element.append(action.name)
|
element.append(action.name)
|
||||||
actionsContainer.appendChild(element)
|
actionsContainer.appendChild(element)
|
||||||
element.addEventListener("click", event => this.runAction(action.id))
|
element.addEventListener("click", (event) => this.runAction(action.id))
|
||||||
})
|
})
|
||||||
this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer)
|
this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer)
|
||||||
this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer)
|
this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer)
|
||||||
@ -512,13 +538,13 @@ class ImageEditor {
|
|||||||
}
|
}
|
||||||
show() {
|
show() {
|
||||||
this.popup.classList.add("active")
|
this.popup.classList.add("active")
|
||||||
document.addEventListener("keydown", this.keyHandlerBound)
|
document.addEventListener("keydown", this.keyHandlerBound, true)
|
||||||
document.addEventListener("keyup", this.keyHandlerBound)
|
document.addEventListener("keyup", this.keyHandlerBound, true)
|
||||||
}
|
}
|
||||||
hide() {
|
hide() {
|
||||||
this.popup.classList.remove("active")
|
this.popup.classList.remove("active")
|
||||||
document.removeEventListener("keydown", this.keyHandlerBound)
|
document.removeEventListener("keydown", this.keyHandlerBound, true)
|
||||||
document.removeEventListener("keyup", this.keyHandlerBound)
|
document.removeEventListener("keyup", this.keyHandlerBound, true)
|
||||||
}
|
}
|
||||||
setSize(width, height) {
|
setSize(width, height) {
|
||||||
if (width == this.width && height == this.height) {
|
if (width == this.width && height == this.height) {
|
||||||
@ -530,8 +556,7 @@ class ImageEditor {
|
|||||||
var multiplier = max_size / width
|
var multiplier = max_size / width
|
||||||
width = (multiplier * width).toFixed()
|
width = (multiplier * width).toFixed()
|
||||||
height = (multiplier * height).toFixed()
|
height = (multiplier * height).toFixed()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768)
|
var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768)
|
||||||
var multiplier = max_size / height
|
var multiplier = max_size / height
|
||||||
width = (multiplier * width).toFixed()
|
width = (multiplier * width).toFixed()
|
||||||
@ -543,7 +568,7 @@ class ImageEditor {
|
|||||||
this.container.style.width = width + "px"
|
this.container.style.width = width + "px"
|
||||||
this.container.style.height = height + "px"
|
this.container.style.height = height + "px"
|
||||||
|
|
||||||
Object.values(this.layers).forEach(layer => {
|
Object.values(this.layers).forEach((layer) => {
|
||||||
layer.canvas.width = width
|
layer.canvas.width = width
|
||||||
layer.canvas.height = height
|
layer.canvas.height = height
|
||||||
})
|
})
|
||||||
@ -556,11 +581,11 @@ class ImageEditor {
|
|||||||
}
|
}
|
||||||
get tool() {
|
get tool() {
|
||||||
var tool_id = this.getOptionValue("tool")
|
var tool_id = this.getOptionValue("tool")
|
||||||
return IMAGE_EDITOR_TOOLS.find(t => t.id == tool_id);
|
return IMAGE_EDITOR_TOOLS.find((t) => t.id == tool_id)
|
||||||
}
|
}
|
||||||
loadTool() {
|
loadTool() {
|
||||||
this.drawing = false
|
this.drawing = false
|
||||||
this.container.style.cursor = this.tool.cursor;
|
this.container.style.cursor = this.tool.cursor
|
||||||
}
|
}
|
||||||
setImage(url, width, height) {
|
setImage(url, width, height) {
|
||||||
this.setSize(width, height)
|
this.setSize(width, height)
|
||||||
@ -574,8 +599,7 @@ class ImageEditor {
|
|||||||
this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height)
|
this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height)
|
||||||
}
|
}
|
||||||
image.src = url
|
image.src = url
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.layers.background.ctx.fillStyle = "#ffffff"
|
this.layers.background.ctx.fillStyle = "#ffffff"
|
||||||
this.layers.background.ctx.beginPath()
|
this.layers.background.ctx.beginPath()
|
||||||
this.layers.background.ctx.rect(0, 0, this.width, this.height)
|
this.layers.background.ctx.rect(0, 0, this.width, this.height)
|
||||||
@ -589,23 +613,24 @@ class ImageEditor {
|
|||||||
this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height)
|
this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height)
|
||||||
var base64 = this.layers.background.canvas.toDataURL()
|
var base64 = this.layers.background.canvas.toDataURL()
|
||||||
initImagePreview.src = base64 // this will trigger the rest of the app to use it
|
initImagePreview.src = base64 // this will trigger the rest of the app to use it
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// This is an inpainter, so make sure the toggle is set accordingly
|
// This is an inpainter, so make sure the toggle is set accordingly
|
||||||
var is_blank = !this.layers.drawing.ctx
|
var is_blank = !this.layers.drawing.ctx
|
||||||
.getImageData(0, 0, this.width, this.height).data
|
.getImageData(0, 0, this.width, this.height)
|
||||||
.some(channel => channel !== 0)
|
.data.some((channel) => channel !== 0)
|
||||||
maskSetting.checked = !is_blank
|
maskSetting.checked = !is_blank
|
||||||
}
|
}
|
||||||
this.hide()
|
this.hide()
|
||||||
}
|
}
|
||||||
getImg() { // a drop-in replacement of the drawingboard version
|
getImg() {
|
||||||
|
// a drop-in replacement of the drawingboard version
|
||||||
return this.layers.drawing.canvas.toDataURL()
|
return this.layers.drawing.canvas.toDataURL()
|
||||||
}
|
}
|
||||||
setImg(dataUrl) { // a drop-in replacement of the drawingboard version
|
setImg(dataUrl) {
|
||||||
|
// a drop-in replacement of the drawingboard version
|
||||||
var image = new Image()
|
var image = new Image()
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
var ctx = this.layers.drawing.ctx;
|
var ctx = this.layers.drawing.ctx
|
||||||
ctx.clearRect(0, 0, this.width, this.height)
|
ctx.clearRect(0, 0, this.width, this.height)
|
||||||
ctx.globalCompositeOperation = "source-over"
|
ctx.globalCompositeOperation = "source-over"
|
||||||
ctx.globalAlpha = 1
|
ctx.globalAlpha = 1
|
||||||
@ -616,7 +641,7 @@ class ImageEditor {
|
|||||||
image.src = dataUrl
|
image.src = dataUrl
|
||||||
}
|
}
|
||||||
runAction(action_id) {
|
runAction(action_id) {
|
||||||
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == action_id)
|
var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == action_id)
|
||||||
if (action.trackHistory) {
|
if (action.trackHistory) {
|
||||||
this.history.pushAction(action_id)
|
this.history.pushAction(action_id)
|
||||||
}
|
}
|
||||||
@ -634,15 +659,16 @@ class ImageEditor {
|
|||||||
layer.ctx.strokeStyle = options.color
|
layer.ctx.strokeStyle = options.color
|
||||||
var sharpness = parseInt(options.sharpness * options.brush_size)
|
var sharpness = parseInt(options.sharpness * options.brush_size)
|
||||||
layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)`
|
layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)`
|
||||||
layer.ctx.globalAlpha = (1 - options.opacity)
|
layer.ctx.globalAlpha = 1 - options.opacity
|
||||||
layer.ctx.globalCompositeOperation = "source-over"
|
layer.ctx.globalCompositeOperation = "source-over"
|
||||||
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == options.tool)
|
var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == options.tool)
|
||||||
if (tool && tool.setBrush) {
|
if (tool && tool.setBrush) {
|
||||||
tool.setBrush(editor, layer)
|
tool.setBrush(editor, layer)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
Object.values(["drawing", "overlay"])
|
||||||
Object.values([ "drawing", "overlay" ]).map(name => this.layers[name]).forEach(l => {
|
.map((name) => this.layers[name])
|
||||||
|
.forEach((l) => {
|
||||||
this.setBrush(l)
|
this.setBrush(l)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -650,13 +676,15 @@ class ImageEditor {
|
|||||||
get ctx_overlay() {
|
get ctx_overlay() {
|
||||||
return this.layers.overlay.ctx
|
return this.layers.overlay.ctx
|
||||||
}
|
}
|
||||||
get ctx_current() { // the idea is this will help support having custom layers and editing each one
|
get ctx_current() {
|
||||||
|
// the idea is this will help support having custom layers and editing each one
|
||||||
return this.layers.drawing.ctx
|
return this.layers.drawing.ctx
|
||||||
}
|
}
|
||||||
get canvas_current() {
|
get canvas_current() {
|
||||||
return this.layers.drawing.canvas
|
return this.layers.drawing.canvas
|
||||||
}
|
}
|
||||||
keyHandler(event) { // handles keybinds like ctrl+z, ctrl+y
|
keyHandler(event) {
|
||||||
|
// handles keybinds like ctrl+z, ctrl+y
|
||||||
if (!this.popup.classList.contains("active")) {
|
if (!this.popup.classList.contains("active")) {
|
||||||
document.removeEventListener("keydown", this.keyHandlerBound)
|
document.removeEventListener("keydown", this.keyHandlerBound)
|
||||||
document.removeEventListener("keyup", this.keyHandlerBound)
|
document.removeEventListener("keyup", this.keyHandlerBound)
|
||||||
@ -668,40 +696,50 @@ class ImageEditor {
|
|||||||
if ((event.key == "z" || event.key == "Z") && event.ctrlKey) {
|
if ((event.key == "z" || event.key == "Z") && event.ctrlKey) {
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey) {
|
||||||
this.history.undo()
|
this.history.undo()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.history.redo()
|
this.history.redo()
|
||||||
}
|
}
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
if (event.key == "y" && event.ctrlKey) {
|
if (event.key == "y" && event.ctrlKey) {
|
||||||
this.history.redo()
|
this.history.redo()
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
this.hide()
|
this.hide()
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dropper ctrl holding handler stuff
|
// dropper ctrl holding handler stuff
|
||||||
var dropper_active = this.temp_previous_tool != null;
|
var dropper_active = this.temp_previous_tool != null
|
||||||
if (dropper_active && !event.ctrlKey) {
|
if (dropper_active && !event.ctrlKey) {
|
||||||
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == this.temp_previous_tool))
|
this.selectOption(
|
||||||
|
"tool",
|
||||||
|
IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == this.temp_previous_tool)
|
||||||
|
)
|
||||||
this.temp_previous_tool = null
|
this.temp_previous_tool = null
|
||||||
}
|
} else if (!dropper_active && event.ctrlKey) {
|
||||||
else if (!dropper_active && event.ctrlKey) {
|
|
||||||
this.temp_previous_tool = this.getOptionValue("tool")
|
this.temp_previous_tool = this.getOptionValue("tool")
|
||||||
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == "colorpicker"))
|
this.selectOption(
|
||||||
|
"tool",
|
||||||
|
IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == "colorpicker")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mouseHandler(event) {
|
mouseHandler(event) {
|
||||||
var bbox = this.layers.overlay.canvas.getBoundingClientRect()
|
var bbox = this.layers.overlay.canvas.getBoundingClientRect()
|
||||||
var x = (event.clientX || 0) - bbox.left
|
var x = (event.clientX || 0) - bbox.left
|
||||||
var y = (event.clientY || 0) - bbox.top
|
var y = (event.clientY || 0) - bbox.top
|
||||||
var type = event.type;
|
var type = event.type
|
||||||
var touchmap = {
|
var touchmap = {
|
||||||
touchstart: "mousedown",
|
touchstart: "mousedown",
|
||||||
touchmove: "mousemove",
|
touchmove: "mousemove",
|
||||||
touchend: "mouseup",
|
touchend: "mouseup",
|
||||||
touchcancel: "mouseup"
|
touchcancel: "mouseup",
|
||||||
}
|
}
|
||||||
if (type in touchmap) {
|
if (type in touchmap) {
|
||||||
type = touchmap[type]
|
type = touchmap[type]
|
||||||
@ -738,15 +776,15 @@ class ImageEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
getOptionValue(section_name) {
|
getOptionValue(section_name) {
|
||||||
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name)
|
var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name)
|
||||||
return this.options && section_name in this.options ? this.options[section_name] : section.default
|
return this.options && section_name in this.options ? this.options[section_name] : section.default
|
||||||
}
|
}
|
||||||
selectOption(section_name, option_index) {
|
selectOption(section_name, option_index) {
|
||||||
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name)
|
var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name)
|
||||||
var value = section.options[option_index]
|
var value = section.options[option_index]
|
||||||
this.options[section_name] = value == "custom" ? section.getCustom(this) : value
|
this.options[section_name] = value == "custom" ? section.getCustom(this) : value
|
||||||
|
|
||||||
this.optionElements[section_name].forEach(element => element.classList.remove("active"))
|
this.optionElements[section_name].forEach((element) => element.classList.remove("active"))
|
||||||
this.optionElements[section_name][option_index].classList.add("active")
|
this.optionElements[section_name][option_index].classList.add("active")
|
||||||
|
|
||||||
// change the editor
|
// change the editor
|
||||||
@ -772,7 +810,6 @@ document.getElementById("init_image_button_inpaint").addEventListener("click", (
|
|||||||
|
|
||||||
img2imgUnload() // no init image when the app starts
|
img2imgUnload() // no init image when the app starts
|
||||||
|
|
||||||
|
|
||||||
function rgbToHex(rgb) {
|
function rgbToHex(rgb) {
|
||||||
function componentToHex(c) {
|
function componentToHex(c) {
|
||||||
var hex = parseInt(c).toString(16)
|
var hex = parseInt(c).toString(16)
|
||||||
@ -782,12 +819,14 @@ function rgbToHex(rgb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hexToRgb(hex) {
|
function hexToRgb(hex) {
|
||||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
return result ? {
|
return result
|
||||||
|
? {
|
||||||
r: parseInt(result[1], 16),
|
r: parseInt(result[1], 16),
|
||||||
g: parseInt(result[2], 16),
|
g: parseInt(result[2], 16),
|
||||||
b: parseInt(result[3], 16)
|
b: parseInt(result[3], 16),
|
||||||
} : null;
|
}
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function pixelCompare(int1, int2) {
|
function pixelCompare(int1, int2) {
|
||||||
@ -795,83 +834,95 @@ function pixelCompare(int1, int2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// adapted from https://ben.akrin.com/canvas_fill/fill_04.html
|
// adapted from https://ben.akrin.com/canvas_fill/fill_04.html
|
||||||
|
// May 2023 - look at using a library instead of custom code: https://github.com/shaneosullivan/example-canvas-fill
|
||||||
function flood_fill(editor, the_canvas_context, x, y, color) {
|
function flood_fill(editor, the_canvas_context, x, y, color) {
|
||||||
pixel_stack = [{x:x, y:y}] ;
|
pixel_stack = [{ x: x, y: y }]
|
||||||
pixels = the_canvas_context.getImageData( 0, 0, editor.width, editor.height ) ;
|
pixels = the_canvas_context.getImageData(0, 0, editor.width, editor.height)
|
||||||
var linear_cords = ( y * editor.width + x ) * 4 ;
|
var linear_cords = (y * editor.width + x) * 4
|
||||||
var original_color = {r:pixels.data[linear_cords],
|
var original_color = {
|
||||||
g:pixels.data[linear_cords+1],
|
r: pixels.data[linear_cords],
|
||||||
b:pixels.data[linear_cords+2],
|
g: pixels.data[linear_cords + 1],
|
||||||
a:pixels.data[linear_cords+3]} ;
|
b: pixels.data[linear_cords + 2],
|
||||||
|
a: pixels.data[linear_cords + 3],
|
||||||
|
}
|
||||||
|
|
||||||
var opacity = color.a / 255;
|
var opacity = color.a / 255
|
||||||
var new_color = {
|
var new_color = {
|
||||||
r: parseInt((color.r * opacity) + (original_color.r * (1 - opacity))),
|
r: parseInt(color.r * opacity + original_color.r * (1 - opacity)),
|
||||||
g: parseInt((color.g * opacity) + (original_color.g * (1 - opacity))),
|
g: parseInt(color.g * opacity + original_color.g * (1 - opacity)),
|
||||||
b: parseInt((color.b * opacity) + (original_color.b * (1 - opacity)))
|
b: parseInt(color.b * opacity + original_color.b * (1 - opacity)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((pixelCompare(new_color.r, original_color.r) &&
|
if (
|
||||||
|
pixelCompare(new_color.r, original_color.r) &&
|
||||||
pixelCompare(new_color.g, original_color.g) &&
|
pixelCompare(new_color.g, original_color.g) &&
|
||||||
pixelCompare(new_color.b, original_color.b)))
|
pixelCompare(new_color.b, original_color.b)
|
||||||
{
|
) {
|
||||||
return; // This color is already the color we want, so do nothing
|
return // This color is already the color we want, so do nothing
|
||||||
}
|
}
|
||||||
var max_stack_size = editor.width * editor.height;
|
var max_stack_size = editor.width * editor.height
|
||||||
while( pixel_stack.length > 0 && pixel_stack.length < max_stack_size ) {
|
while (pixel_stack.length > 0 && pixel_stack.length < max_stack_size) {
|
||||||
new_pixel = pixel_stack.shift() ;
|
new_pixel = pixel_stack.shift()
|
||||||
x = new_pixel.x ;
|
x = new_pixel.x
|
||||||
y = new_pixel.y ;
|
y = new_pixel.y
|
||||||
|
|
||||||
linear_cords = ( y * editor.width + x ) * 4 ;
|
linear_cords = (y * editor.width + x) * 4
|
||||||
while( y-->=0 &&
|
while (
|
||||||
(pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
y-- >= 0 &&
|
||||||
pixelCompare(pixels.data[linear_cords+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords+2], original_color.b))) {
|
pixelCompare(pixels.data[linear_cords + 1], original_color.g) &&
|
||||||
linear_cords -= editor.width * 4 ;
|
pixelCompare(pixels.data[linear_cords + 2], original_color.b)
|
||||||
|
) {
|
||||||
|
linear_cords -= editor.width * 4
|
||||||
}
|
}
|
||||||
linear_cords += editor.width * 4 ;
|
linear_cords += editor.width * 4
|
||||||
y++ ;
|
y++
|
||||||
|
|
||||||
var reached_left = false ;
|
var reached_left = false
|
||||||
var reached_right = false ;
|
var reached_right = false
|
||||||
while( y++<editor.height &&
|
while (
|
||||||
(pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
y++ < editor.height &&
|
||||||
pixelCompare(pixels.data[linear_cords+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords+2], original_color.b))) {
|
pixelCompare(pixels.data[linear_cords + 1], original_color.g) &&
|
||||||
pixels.data[linear_cords] = new_color.r ;
|
pixelCompare(pixels.data[linear_cords + 2], original_color.b)
|
||||||
pixels.data[linear_cords+1] = new_color.g ;
|
) {
|
||||||
pixels.data[linear_cords+2] = new_color.b ;
|
pixels.data[linear_cords] = new_color.r
|
||||||
pixels.data[linear_cords+3] = 255 ;
|
pixels.data[linear_cords + 1] = new_color.g
|
||||||
|
pixels.data[linear_cords + 2] = new_color.b
|
||||||
|
pixels.data[linear_cords + 3] = 255
|
||||||
|
|
||||||
if( x>0 ) {
|
if (x > 0) {
|
||||||
if( pixelCompare(pixels.data[linear_cords-4], original_color.r) &&
|
if (
|
||||||
pixelCompare(pixels.data[linear_cords-4+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords - 4], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords-4+2], original_color.b)) {
|
pixelCompare(pixels.data[linear_cords - 4 + 1], original_color.g) &&
|
||||||
if( !reached_left ) {
|
pixelCompare(pixels.data[linear_cords - 4 + 2], original_color.b)
|
||||||
pixel_stack.push( {x:x-1, y:y} ) ;
|
) {
|
||||||
reached_left = true ;
|
if (!reached_left) {
|
||||||
|
pixel_stack.push({ x: x - 1, y: y })
|
||||||
|
reached_left = true
|
||||||
}
|
}
|
||||||
} else if( reached_left ) {
|
} else if (reached_left) {
|
||||||
reached_left = false ;
|
reached_left = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( x<editor.width-1 ) {
|
if (x < editor.width - 1) {
|
||||||
if( pixelCompare(pixels.data[linear_cords+4], original_color.r) &&
|
if (
|
||||||
pixelCompare(pixels.data[linear_cords+4+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords + 4], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords+4+2], original_color.b)) {
|
pixelCompare(pixels.data[linear_cords + 4 + 1], original_color.g) &&
|
||||||
if( !reached_right ) {
|
pixelCompare(pixels.data[linear_cords + 4 + 2], original_color.b)
|
||||||
pixel_stack.push( {x:x+1,y:y} ) ;
|
) {
|
||||||
reached_right = true ;
|
if (!reached_right) {
|
||||||
|
pixel_stack.push({ x: x + 1, y: y })
|
||||||
|
reached_right = true
|
||||||
}
|
}
|
||||||
} else if( reached_right ) {
|
} else if (reached_right) {
|
||||||
reached_right = false ;
|
reached_right = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
linear_cords += editor.width * 4 ;
|
linear_cords += editor.width * 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
the_canvas_context.putImageData( pixels, 0, 0 ) ;
|
the_canvas_context.putImageData(pixels, 0, 0)
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,45 @@
|
|||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} ImageModalRequest
|
||||||
|
* @property {string} src
|
||||||
|
* @property {ImageModalRequest | () => ImageModalRequest | undefined} previous
|
||||||
|
* @property {ImageModalRequest | () => ImageModalRequest | undefined} next
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}}
|
||||||
|
*/
|
||||||
const imageModal = (function() {
|
const imageModal = (function() {
|
||||||
const zoomElem = createElement(
|
const backElem = createElement("i", undefined, ["fa-solid", "fa-arrow-left", "tertiaryButton"])
|
||||||
'i',
|
|
||||||
undefined,
|
|
||||||
['fa-solid', 'tertiaryButton'],
|
|
||||||
)
|
|
||||||
|
|
||||||
const closeElem = createElement(
|
const forwardElem = createElement("i", undefined, ["fa-solid", "fa-arrow-right", "tertiaryButton"])
|
||||||
'i',
|
|
||||||
undefined,
|
|
||||||
['fa-solid', 'fa-xmark', 'tertiaryButton'],
|
|
||||||
)
|
|
||||||
|
|
||||||
const menuBarElem = createElement('div', undefined, 'menu-bar', [zoomElem, closeElem])
|
const zoomElem = createElement("i", undefined, ["fa-solid", "tertiaryButton"])
|
||||||
|
|
||||||
const imageContainer = createElement('div', undefined, 'image-wrapper')
|
const closeElem = createElement("i", undefined, ["fa-solid", "fa-xmark", "tertiaryButton"])
|
||||||
|
|
||||||
const backdrop = createElement('div', undefined, 'backdrop')
|
const menuBarElem = createElement("div", undefined, "menu-bar", [backElem, forwardElem, zoomElem, closeElem])
|
||||||
|
|
||||||
const modalContainer = createElement('div', undefined, 'content', [menuBarElem, imageContainer])
|
const imageContainer = createElement("div", undefined, "image-wrapper")
|
||||||
|
|
||||||
const modalElem = createElement(
|
const backdrop = createElement("div", undefined, "backdrop")
|
||||||
'div',
|
|
||||||
{ id: 'viewFullSizeImgModal' },
|
const modalContainer = createElement("div", undefined, "content", [menuBarElem, imageContainer])
|
||||||
['popup'],
|
|
||||||
[backdrop, modalContainer],
|
const modalElem = createElement("div", { id: "viewFullSizeImgModal" }, ["popup"], [backdrop, modalContainer])
|
||||||
)
|
|
||||||
document.body.appendChild(modalElem)
|
document.body.appendChild(modalElem)
|
||||||
|
|
||||||
const setZoomLevel = (value) => {
|
const setZoomLevel = (value) => {
|
||||||
const img = imageContainer.querySelector('img')
|
const img = imageContainer.querySelector("img")
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
zoomElem.classList.remove('fa-magnifying-glass-plus')
|
zoomElem.classList.remove("fa-magnifying-glass-plus")
|
||||||
zoomElem.classList.add('fa-magnifying-glass-minus')
|
zoomElem.classList.add("fa-magnifying-glass-minus")
|
||||||
if (img) {
|
if (img) {
|
||||||
img.classList.remove('natural-zoom')
|
img.classList.remove("natural-zoom")
|
||||||
|
|
||||||
let zoomLevel = typeof value === 'number' ? value : img.dataset.zoomLevel
|
let zoomLevel = typeof value === "number" ? value : img.dataset.zoomLevel
|
||||||
if (!zoomLevel) {
|
if (!zoomLevel) {
|
||||||
zoomLevel = 100
|
zoomLevel = 100
|
||||||
}
|
}
|
||||||
@ -48,34 +49,164 @@ const imageModal = (function() {
|
|||||||
img.height = img.naturalHeight * (+zoomLevel / 100)
|
img.height = img.naturalHeight * (+zoomLevel / 100)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
zoomElem.classList.remove('fa-magnifying-glass-minus')
|
zoomElem.classList.remove("fa-magnifying-glass-minus")
|
||||||
zoomElem.classList.add('fa-magnifying-glass-plus')
|
zoomElem.classList.add("fa-magnifying-glass-plus")
|
||||||
if (img) {
|
if (img) {
|
||||||
img.classList.add('natural-zoom')
|
img.classList.add("natural-zoom")
|
||||||
img.removeAttribute('width')
|
img.removeAttribute("width")
|
||||||
img.removeAttribute('height')
|
img.removeAttribute("height")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomElem.addEventListener(
|
zoomElem.addEventListener("click", () =>
|
||||||
'click',
|
setZoomLevel(imageContainer.querySelector("img")?.classList?.contains("natural-zoom"))
|
||||||
() => setZoomLevel(imageContainer.querySelector('img')?.classList?.contains('natural-zoom')),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const close = () => {
|
const initialState = () => ({
|
||||||
imageContainer.innerHTML = ''
|
previous: undefined,
|
||||||
modalElem.classList.remove('active')
|
next: undefined,
|
||||||
document.body.style.overflow = 'initial'
|
|
||||||
|
start: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = initialState()
|
||||||
|
|
||||||
|
// Allow grabbing the image to scroll
|
||||||
|
const stopGrabbing = (e) => {
|
||||||
|
if(imageContainer.classList.contains("grabbing")) {
|
||||||
|
imageContainer.classList.remove("grabbing")
|
||||||
|
e?.preventDefault()
|
||||||
|
console.log(`stopGrabbing()`, e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', (e) => {
|
const addImageGrabbing = (image) => {
|
||||||
if (e.key === 'Escape' && modalElem.classList.contains('active')) {
|
image?.addEventListener('mousedown', (e) => {
|
||||||
close()
|
if (!image.classList.contains("natural-zoom")) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.stopImmediatePropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
imageContainer.classList.add("grabbing")
|
||||||
|
state.start.x = e.pageX - imageContainer.offsetLeft
|
||||||
|
state.scroll.x = imageContainer.scrollLeft
|
||||||
|
state.start.y = e.pageY - imageContainer.offsetTop
|
||||||
|
state.scroll.y = imageContainer.scrollTop
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
window.addEventListener('click', (e) => {
|
|
||||||
if (modalElem.classList.contains('active')) {
|
image?.addEventListener('mouseup', stopGrabbing)
|
||||||
|
image?.addEventListener('mouseleave', stopGrabbing)
|
||||||
|
image?.addEventListener('mousemove', (e) => {
|
||||||
|
if(imageContainer.classList.contains("grabbing")) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.stopImmediatePropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
// Might need to increase this multiplier based on the image size to window size ratio
|
||||||
|
// The default 1:1 is pretty slow
|
||||||
|
const multiplier = 1.0
|
||||||
|
|
||||||
|
const deltaX = e.pageX - imageContainer.offsetLeft - state.start.x
|
||||||
|
imageContainer.scrollLeft = state.scroll.x - (deltaX * multiplier)
|
||||||
|
const deltaY = e.pageY - imageContainer.offsetTop - state.start.y
|
||||||
|
imageContainer.scrollTop = state.scroll.y - (deltaY * multiplier)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
imageContainer.innerHTML = ""
|
||||||
|
|
||||||
|
Object.entries(initialState()).forEach(([key, value]) => state[key] = value)
|
||||||
|
|
||||||
|
stopGrabbing()
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
clear()
|
||||||
|
modalElem.classList.remove("active")
|
||||||
|
document.body.style.overflow = "initial"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory
|
||||||
|
*/
|
||||||
|
function init(optionsFactory) {
|
||||||
|
if (!optionsFactory) {
|
||||||
|
close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clear()
|
||||||
|
|
||||||
|
const options = typeof optionsFactory === "function" ? optionsFactory() : optionsFactory
|
||||||
|
const src = typeof options === "string" ? options : options.src
|
||||||
|
|
||||||
|
const imgElem = createElement("img", { src }, "natural-zoom")
|
||||||
|
addImageGrabbing(imgElem)
|
||||||
|
imageContainer.appendChild(imgElem)
|
||||||
|
modalElem.classList.add("active")
|
||||||
|
document.body.style.overflow = "hidden"
|
||||||
|
setZoomLevel(false)
|
||||||
|
|
||||||
|
if (typeof options === "object" && options.previous) {
|
||||||
|
state.previous = options.previous
|
||||||
|
backElem.style.display = "unset"
|
||||||
|
} else {
|
||||||
|
backElem.style.display = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options === "object" && options.next) {
|
||||||
|
state.next = options.next
|
||||||
|
forwardElem.style.display = "unset"
|
||||||
|
} else {
|
||||||
|
forwardElem.style.display = "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const back = () => {
|
||||||
|
if (state.previous) {
|
||||||
|
init(state.previous)
|
||||||
|
} else {
|
||||||
|
backElem.style.display = "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const forward = () => {
|
||||||
|
if (state.next) {
|
||||||
|
init(state.next)
|
||||||
|
} else {
|
||||||
|
forwardElem.style.display = "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("keydown", (e) => {
|
||||||
|
if (modalElem.classList.contains("active")) {
|
||||||
|
switch (e.key) {
|
||||||
|
case "Escape":
|
||||||
|
close()
|
||||||
|
break
|
||||||
|
case "ArrowLeft":
|
||||||
|
back()
|
||||||
|
break
|
||||||
|
case "ArrowRight":
|
||||||
|
forward()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
window.addEventListener("click", (e) => {
|
||||||
|
if (modalElem.classList.contains("active")) {
|
||||||
if (e.target === backdrop || e.target === closeElem) {
|
if (e.target === backdrop || e.target === closeElem) {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
@ -86,15 +217,12 @@ const imageModal = (function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (optionsFactory) => {
|
backElem.addEventListener("click", back)
|
||||||
const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory
|
|
||||||
const src = typeof options === 'string' ? options : options.src
|
|
||||||
|
|
||||||
// TODO center it if < window size
|
forwardElem.addEventListener("click", forward)
|
||||||
const imgElem = createElement('img', { src }, 'natural-zoom')
|
|
||||||
imageContainer.appendChild(imgElem)
|
/**
|
||||||
modalElem.classList.add('active')
|
* @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory
|
||||||
document.body.style.overflow = 'hidden'
|
*/
|
||||||
setZoomLevel(false)
|
return (optionsFactory) => init(optionsFactory)
|
||||||
}
|
|
||||||
})()
|
})()
|
||||||
|
@ -1,216 +1,249 @@
|
|||||||
let activeTags = []
|
let activeTags = []
|
||||||
let modifiers = []
|
let modifiers = []
|
||||||
let customModifiersGroupElement = undefined
|
let customModifiersGroupElement = undefined
|
||||||
let customModifiersInitialContent
|
let customModifiersInitialContent = ""
|
||||||
|
let modifierPanelFreezed = false
|
||||||
|
|
||||||
let editorModifierEntries = document.querySelector('#editor-modifiers-entries')
|
let modifiersMainContainer = document.querySelector("#editor-modifiers")
|
||||||
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list')
|
let modifierDropdown = document.querySelector("#image-modifier-dropdown")
|
||||||
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container')
|
let editorModifiersContainer = document.querySelector("#editor-modifiers")
|
||||||
let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider')
|
let editorModifierEntries = document.querySelector("#editor-modifiers-entries")
|
||||||
let previewImageField = document.querySelector('#preview-image')
|
let editorModifierTagsList = document.querySelector("#editor-inputs-tags-list")
|
||||||
let modifierSettingsBtn = document.querySelector('#modifier-settings-btn')
|
let editorTagsContainer = document.querySelector("#editor-inputs-tags-container")
|
||||||
let modifierSettingsOverlay = document.querySelector('#modifier-settings-config')
|
let modifierCardSizeSlider = document.querySelector("#modifier-card-size-slider")
|
||||||
let customModifiersTextBox = document.querySelector('#custom-modifiers-input')
|
let previewImageField = document.querySelector("#preview-image")
|
||||||
let customModifierEntriesToolbar = document.querySelector('#editor-modifiers-entries-toolbar')
|
let modifierSettingsBtn = document.querySelector("#modifier-settings-btn")
|
||||||
|
let modifiersContainerSizeBtn = document.querySelector("#modifiers-container-size-btn")
|
||||||
|
let modifiersCloseBtn = document.querySelector("#modifiers-close-button")
|
||||||
|
let modifiersCollapsiblesBtn = document.querySelector("#modifiers-action-collapsibles-btn")
|
||||||
|
let modifierSettingsOverlay = document.querySelector("#modifier-settings-config")
|
||||||
|
let customModifiersTextBox = document.querySelector("#custom-modifiers-input")
|
||||||
|
let customModifierEntriesToolbar = document.querySelector("#editor-modifiers-entries-toolbar")
|
||||||
|
|
||||||
const modifierThumbnailPath = 'media/modifier-thumbnails'
|
const modifierThumbnailPath = "media/modifier-thumbnails"
|
||||||
const activeCardClass = 'modifier-card-active'
|
const activeCardClass = "modifier-card-active"
|
||||||
const CUSTOM_MODIFIERS_KEY = "customModifiers"
|
const CUSTOM_MODIFIERS_KEY = "customModifiers"
|
||||||
|
|
||||||
function createModifierCard(name, previews, removeBy) {
|
function createModifierCard(name, previews, removeBy) {
|
||||||
const modifierCard = document.createElement('div')
|
let cardPreviewImageType = previewImageField.value
|
||||||
let style = previewImageField.value
|
|
||||||
let styleIndex = (style=='portrait') ? 0 : 1
|
|
||||||
|
|
||||||
modifierCard.className = 'modifier-card'
|
const modifierCard = document.createElement("div")
|
||||||
|
modifierCard.className = "modifier-card"
|
||||||
modifierCard.innerHTML = `
|
modifierCard.innerHTML = `
|
||||||
<div class="modifier-card-overlay"></div>
|
<div class="modifier-card-overlay"></div>
|
||||||
<div class="modifier-card-image-container">
|
<div class="modifier-card-image-container">
|
||||||
<div class="modifier-card-image-overlay">+</div>
|
<div class="modifier-card-image-overlay">+</div>
|
||||||
<p class="modifier-card-error-label"></p>
|
<p class="modifier-card-error-label">No Image</p>
|
||||||
<img onerror="this.remove()" alt="Modifier Image" class="modifier-card-image">
|
<img onerror="this.remove()" alt="Modifier Image" class="modifier-card-image">
|
||||||
</div>
|
</div>
|
||||||
<div class="modifier-card-container">
|
<div class="modifier-card-container">
|
||||||
<div class="modifier-card-label"><p></p></div>
|
<div class="modifier-card-label">
|
||||||
|
<span class="long-label hidden"></span>
|
||||||
|
<p class="regular-label"></p>
|
||||||
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
const image = modifierCard.querySelector('.modifier-card-image')
|
const image = modifierCard.querySelector(".modifier-card-image")
|
||||||
const errorText = modifierCard.querySelector('.modifier-card-error-label')
|
const longLabel = modifierCard.querySelector(".modifier-card-label span.long-label")
|
||||||
const label = modifierCard.querySelector('.modifier-card-label')
|
const regularLabel = modifierCard.querySelector(".modifier-card-label p.regular-label")
|
||||||
|
|
||||||
errorText.innerText = 'No Image'
|
if (typeof previews == "object") {
|
||||||
|
image.src = previews[cardPreviewImageType == "portrait" ? 0 : 1] // 0 index is portrait, 1 landscape
|
||||||
if (typeof previews == 'object') {
|
image.setAttribute("preview-type", cardPreviewImageType)
|
||||||
image.src = previews[styleIndex]; // portrait
|
|
||||||
image.setAttribute('preview-type', style)
|
|
||||||
} else {
|
} else {
|
||||||
image.remove()
|
image.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLabelLength = 30
|
const maxLabelLength = 30
|
||||||
const cardLabel = removeBy ? name.replace('by ', '') : name
|
const cardLabel = removeBy ? name.replace("by ", "") : name
|
||||||
|
|
||||||
if(cardLabel.length <= maxLabelLength) {
|
function getFormattedLabel(length) {
|
||||||
label.querySelector('p').innerText = cardLabel
|
if (cardLabel?.length <= length) {
|
||||||
|
return cardLabel
|
||||||
} else {
|
} else {
|
||||||
const tooltipText = document.createElement('span')
|
return cardLabel.substring(0, length) + "..."
|
||||||
tooltipText.className = 'tooltip-text'
|
}
|
||||||
tooltipText.innerText = name
|
}
|
||||||
|
|
||||||
label.classList.add('tooltip')
|
modifierCard.dataset.fullName = name // preserve the full name
|
||||||
label.appendChild(tooltipText)
|
regularLabel.dataset.fullName = name // preserve the full name, legacy support for older plugins
|
||||||
|
|
||||||
label.querySelector('p').innerText = cardLabel.substring(0, maxLabelLength) + '...'
|
longLabel.innerText = getFormattedLabel(maxLabelLength * 2)
|
||||||
|
regularLabel.innerText = getFormattedLabel(maxLabelLength)
|
||||||
|
|
||||||
|
if (cardLabel.length > maxLabelLength) {
|
||||||
|
modifierCard.classList.add("support-long-label")
|
||||||
|
|
||||||
|
if (cardLabel.length > maxLabelLength * 2) {
|
||||||
|
modifierCard.title = `"${name}"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
label.querySelector('p').dataset.fullName = name // preserve the full name
|
|
||||||
|
|
||||||
return modifierCard
|
return modifierCard
|
||||||
}
|
}
|
||||||
|
|
||||||
function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) {
|
function createModifierGroup(modifierGroup, isInitiallyOpen, removeBy) {
|
||||||
const title = modifierGroup.category
|
const title = modifierGroup.category
|
||||||
const modifiers = modifierGroup.modifiers
|
const modifiers = modifierGroup.modifiers
|
||||||
|
|
||||||
const titleEl = document.createElement('h5')
|
const titleEl = document.createElement("h5")
|
||||||
titleEl.className = 'collapsible'
|
titleEl.className = "collapsible"
|
||||||
titleEl.innerText = title
|
titleEl.innerText = title
|
||||||
|
|
||||||
const modifiersEl = document.createElement('div')
|
const modifiersEl = document.createElement("div")
|
||||||
modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf')
|
modifiersEl.classList.add("collapsible-content", "editor-modifiers-leaf")
|
||||||
|
|
||||||
if (initiallyExpanded === true) {
|
if (isInitiallyOpen === true) {
|
||||||
titleEl.className += ' active'
|
titleEl.classList.add("active")
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiers.forEach(modObj => {
|
modifiers.forEach((modObj) => {
|
||||||
const modifierName = modObj.modifier
|
const modifierName = modObj.modifier
|
||||||
const modifierPreviews = modObj?.previews?.map(preview => `${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + '/' + preview.path}`)
|
const modifierPreviews = modObj?.previews?.map(
|
||||||
|
(preview) =>
|
||||||
|
`${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + "/" + preview.path}`
|
||||||
|
)
|
||||||
|
|
||||||
const modifierCard = createModifierCard(modifierName, modifierPreviews, removeBy)
|
const modifierCard = createModifierCard(modifierName, modifierPreviews, removeBy)
|
||||||
|
|
||||||
if(typeof modifierCard == 'object') {
|
if (typeof modifierCard == "object") {
|
||||||
modifiersEl.appendChild(modifierCard)
|
modifiersEl.appendChild(modifierCard)
|
||||||
const trimmedName = trimModifiers(modifierName)
|
const trimmedName = trimModifiers(modifierName)
|
||||||
|
|
||||||
modifierCard.addEventListener('click', () => {
|
modifierCard.addEventListener("click", () => {
|
||||||
if (activeTags.map(x => trimModifiers(x.name)).includes(trimmedName)) {
|
if (activeTags.map((x) => trimModifiers(x.name)).includes(trimmedName)) {
|
||||||
// remove modifier from active array
|
// remove modifier from active array
|
||||||
activeTags = activeTags.filter(x => trimModifiers(x.name) != trimmedName)
|
activeTags = activeTags.filter((x) => trimModifiers(x.name) != trimmedName)
|
||||||
toggleCardState(trimmedName, false)
|
toggleCardState(trimmedName, false)
|
||||||
} else {
|
} else {
|
||||||
// add modifier to active array
|
// add modifier to active array
|
||||||
activeTags.push({
|
activeTags.push({
|
||||||
'name': modifierName,
|
name: modifierName,
|
||||||
'element': modifierCard.cloneNode(true),
|
element: modifierCard.cloneNode(true),
|
||||||
'originElement': modifierCard,
|
originElement: modifierCard,
|
||||||
'previews': modifierPreviews
|
previews: modifierPreviews,
|
||||||
})
|
})
|
||||||
toggleCardState(trimmedName, true)
|
toggleCardState(trimmedName, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTagsList()
|
refreshTagsList()
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let brk = document.createElement('br')
|
let brk = document.createElement("br")
|
||||||
brk.style.clear = 'both'
|
brk.style.clear = "both"
|
||||||
modifiersEl.appendChild(brk)
|
modifiersEl.appendChild(brk)
|
||||||
|
|
||||||
let e = document.createElement('div')
|
let e = document.createElement("div")
|
||||||
e.className = 'modifier-category'
|
e.className = "modifier-category"
|
||||||
e.appendChild(titleEl)
|
e.appendChild(titleEl)
|
||||||
e.appendChild(modifiersEl)
|
e.appendChild(modifiersEl)
|
||||||
|
|
||||||
editorModifierEntries.insertBefore(e, customModifierEntriesToolbar.nextSibling)
|
editorModifierEntries.prepend(e)
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
function trimModifiers(tag) {
|
function trimModifiers(tag) {
|
||||||
// Remove trailing '-' and/or '+'
|
// Remove trailing '-' and/or '+'
|
||||||
tag = tag.replace(/[-+]+$/, '');
|
tag = tag.replace(/[-+]+$/, "")
|
||||||
// Remove parentheses at beginning and end
|
// Remove parentheses at beginning and end
|
||||||
return tag.replace(/^[(]+|[\s)]+$/g, '');
|
return tag.replace(/^[(]+|[\s)]+$/g, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadModifiers() {
|
async function loadModifiers() {
|
||||||
try {
|
try {
|
||||||
let res = await fetch('/get/modifiers')
|
let res = await fetch("/get/modifiers")
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
res = await res.json()
|
res = await res.json()
|
||||||
|
|
||||||
modifiers = res; // update global variable
|
modifiers = res // update global variable
|
||||||
|
|
||||||
res.reverse()
|
res.reverse()
|
||||||
|
|
||||||
res.forEach((modifierGroup, idx) => {
|
res.forEach((modifierGroup, idx) => {
|
||||||
createModifierGroup(modifierGroup, idx === res.length - 1, modifierGroup === 'Artist' ? true : false) // only remove "By " for artists
|
const isInitiallyOpen = false // idx === res.length - 1
|
||||||
|
const removeBy = modifierGroup === "Artist" ? true : false // only remove "By " for artists
|
||||||
|
|
||||||
|
createModifierGroup(modifierGroup, isInitiallyOpen, removeBy)
|
||||||
})
|
})
|
||||||
|
|
||||||
createCollapsibles(editorModifierEntries)
|
createCollapsibles(editorModifierEntries)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('error fetching modifiers', e)
|
console.error("error fetching modifiers", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCustomModifiers()
|
loadCustomModifiers()
|
||||||
resizeModifierCards(modifierCardSizeSlider.value)
|
resizeModifierCards(modifierCardSizeSlider.value)
|
||||||
document.dispatchEvent(new Event('loadImageModifiers'))
|
document.dispatchEvent(new Event("loadImageModifiers"))
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshModifiersState(newTags, inactiveTags) {
|
function refreshModifiersState(newTags, inactiveTags) {
|
||||||
// clear existing modifiers
|
// clear existing modifiers
|
||||||
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => {
|
document
|
||||||
const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName // pick the full modifier name
|
.querySelector("#editor-modifiers")
|
||||||
if (activeTags.map(x => x.name).includes(modifierName)) {
|
.querySelectorAll(".modifier-card")
|
||||||
|
.forEach((modifierCard) => {
|
||||||
|
const modifierName = modifierCard.dataset.fullName // pick the full modifier name
|
||||||
|
if (activeTags.map((x) => x.name).includes(modifierName)) {
|
||||||
modifierCard.classList.remove(activeCardClass)
|
modifierCard.classList.remove(activeCardClass)
|
||||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
activeTags = []
|
activeTags = []
|
||||||
|
|
||||||
// set new modifiers
|
// set new modifiers
|
||||||
newTags.forEach(tag => {
|
newTags.forEach((tag) => {
|
||||||
let found = false
|
let found = false
|
||||||
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => {
|
document
|
||||||
const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName
|
.querySelector("#editor-modifiers")
|
||||||
const shortModifierName = modifierCard.querySelector('.modifier-card-label p').innerText
|
.querySelectorAll(".modifier-card")
|
||||||
|
.forEach((modifierCard) => {
|
||||||
|
const modifierName = modifierCard.dataset.fullName
|
||||||
|
const shortModifierName = modifierCard.querySelector(".modifier-card-label p").innerText
|
||||||
|
|
||||||
if (trimModifiers(tag) == trimModifiers(modifierName)) {
|
if (trimModifiers(tag) == trimModifiers(modifierName)) {
|
||||||
// add modifier to active array
|
// add modifier to active array
|
||||||
if (!activeTags.map(x => x.name).includes(tag)) { // only add each tag once even if several custom modifier cards share the same tag
|
if (!activeTags.map((x) => x.name).includes(tag)) {
|
||||||
|
// only add each tag once even if several custom modifier cards share the same tag
|
||||||
const imageModifierCard = modifierCard.cloneNode(true)
|
const imageModifierCard = modifierCard.cloneNode(true)
|
||||||
imageModifierCard.querySelector('.modifier-card-label p').innerText = tag.replace(modifierName, shortModifierName)
|
imageModifierCard.querySelector(".modifier-card-label p").innerText = tag.replace(
|
||||||
|
modifierName,
|
||||||
|
shortModifierName
|
||||||
|
)
|
||||||
activeTags.push({
|
activeTags.push({
|
||||||
'name': tag,
|
name: tag,
|
||||||
'element': imageModifierCard,
|
element: imageModifierCard,
|
||||||
'originElement': modifierCard
|
originElement: modifierCard,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
modifierCard.classList.add(activeCardClass)
|
modifierCard.classList.add(activeCardClass)
|
||||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-'
|
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "-"
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (found == false) { // custom tag went missing, create one here
|
if (found == false) {
|
||||||
|
// custom tag went missing, create one here
|
||||||
let modifierCard = createModifierCard(tag, undefined, false) // create a modifier card for the missing tag, no image
|
let modifierCard = createModifierCard(tag, undefined, false) // create a modifier card for the missing tag, no image
|
||||||
|
|
||||||
modifierCard.addEventListener('click', () => {
|
modifierCard.addEventListener("click", () => {
|
||||||
if (activeTags.map(x => x.name).includes(tag)) {
|
if (activeTags.map((x) => x.name).includes(tag)) {
|
||||||
// remove modifier from active array
|
// remove modifier from active array
|
||||||
activeTags = activeTags.filter(x => x.name != tag)
|
activeTags = activeTags.filter((x) => x.name != tag)
|
||||||
modifierCard.classList.remove(activeCardClass)
|
modifierCard.classList.remove(activeCardClass)
|
||||||
|
|
||||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+"
|
||||||
}
|
}
|
||||||
refreshTagsList()
|
refreshTagsList()
|
||||||
})
|
})
|
||||||
|
|
||||||
activeTags.push({
|
activeTags.push({
|
||||||
'name': tag,
|
name: tag,
|
||||||
'element': modifierCard,
|
element: modifierCard,
|
||||||
'originElement': undefined // no origin element for missing tags
|
originElement: undefined, // no origin element for missing tags
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -220,41 +253,50 @@ function refreshModifiersState(newTags, inactiveTags) {
|
|||||||
function refreshInactiveTags(inactiveTags) {
|
function refreshInactiveTags(inactiveTags) {
|
||||||
// update inactive tags
|
// update inactive tags
|
||||||
if (inactiveTags !== undefined && inactiveTags.length > 0) {
|
if (inactiveTags !== undefined && inactiveTags.length > 0) {
|
||||||
activeTags.forEach (tag => {
|
activeTags.forEach((tag) => {
|
||||||
if (inactiveTags.find(element => element === tag.name) !== undefined) {
|
if (inactiveTags.find((element) => element === tag.name) !== undefined) {
|
||||||
tag.inactive = true
|
tag.inactive = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// update cards
|
// update cards
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = editorModifierTagsList.querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText
|
let modifierName = i.parentElement.dataset.fullName
|
||||||
if (inactiveTags?.find(element => element === modifierName) !== undefined) {
|
|
||||||
i.parentElement.classList.add('modifier-toggle-inactive')
|
if (inactiveTags?.find((element) => trimModifiers(element) === modifierName) !== undefined) {
|
||||||
|
i.parentElement.classList.add("modifier-toggle-inactive")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshTagsList(inactiveTags) {
|
function refreshTagsList(inactiveTags) {
|
||||||
editorModifierTagsList.innerHTML = ''
|
editorModifierTagsList.innerHTML = ""
|
||||||
|
|
||||||
if (activeTags.length == 0) {
|
if (activeTags.length == 0) {
|
||||||
editorTagsContainer.style.display = 'none'
|
editorTagsContainer.style.display = "none"
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
editorTagsContainer.style.display = 'block'
|
editorTagsContainer.style.display = "block"
|
||||||
|
}
|
||||||
|
|
||||||
|
if(activeTags.length > 15) {
|
||||||
|
editorModifierTagsList.style["overflow-y"] = "auto"
|
||||||
|
} else {
|
||||||
|
editorModifierTagsList.style["overflow-y"] = "unset"
|
||||||
}
|
}
|
||||||
|
|
||||||
activeTags.forEach((tag, index) => {
|
activeTags.forEach((tag, index) => {
|
||||||
tag.element.querySelector('.modifier-card-image-overlay').innerText = '-'
|
tag.element.querySelector(".modifier-card-image-overlay").innerText = "-"
|
||||||
tag.element.classList.add('modifier-card-tiny')
|
tag.element.classList.add("modifier-card-tiny")
|
||||||
|
|
||||||
editorModifierTagsList.appendChild(tag.element)
|
editorModifierTagsList.appendChild(tag.element)
|
||||||
|
|
||||||
tag.element.addEventListener('click', () => {
|
tag.element.addEventListener("click", () => {
|
||||||
let idx = activeTags.findIndex(o => { return o.name === tag.name })
|
let idx = activeTags.findIndex((o) => {
|
||||||
|
return o.name === tag.name
|
||||||
|
})
|
||||||
|
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
toggleCardState(activeTags[idx].name, false)
|
toggleCardState(activeTags[idx].name, false)
|
||||||
@ -262,120 +304,90 @@ function refreshTagsList(inactiveTags) {
|
|||||||
activeTags.splice(idx, 1)
|
activeTags.splice(idx, 1)
|
||||||
refreshTagsList()
|
refreshTagsList()
|
||||||
}
|
}
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let brk = document.createElement('br')
|
let brk = document.createElement("br")
|
||||||
brk.style.clear = 'both'
|
brk.style.clear = "both"
|
||||||
|
|
||||||
editorModifierTagsList.appendChild(brk)
|
editorModifierTagsList.appendChild(brk)
|
||||||
|
|
||||||
refreshInactiveTags(inactiveTags)
|
refreshInactiveTags(inactiveTags)
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers')) // notify plugins that the image tags have been refreshed
|
|
||||||
|
document.dispatchEvent(new Event("refreshImageModifiers")) // notify plugins that the image tags have been refreshed
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCardState(modifierName, makeActive) {
|
function toggleCardState(modifierName, makeActive) {
|
||||||
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => {
|
const cards = [...document.querySelectorAll("#editor-modifiers .modifier-card")]
|
||||||
const name = card.querySelector('.modifier-card-label').innerText
|
.filter(cardElem => trimModifiers(cardElem.dataset.fullName) == trimModifiers(modifierName))
|
||||||
if ( trimModifiers(modifierName) == trimModifiers(name)
|
|
||||||
|| trimModifiers(modifierName) == 'by ' + trimModifiers(name)) {
|
const cardExists = typeof cards == "object" && cards?.length > 0
|
||||||
if(makeActive) {
|
|
||||||
|
if (cardExists) {
|
||||||
|
const card = cards[0]
|
||||||
|
|
||||||
|
if (makeActive) {
|
||||||
card.classList.add(activeCardClass)
|
card.classList.add(activeCardClass)
|
||||||
card.querySelector('.modifier-card-image-overlay').innerText = '-'
|
card.querySelector(".modifier-card-image-overlay").innerText = "-"
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
card.classList.remove(activeCardClass)
|
card.classList.remove(activeCardClass)
|
||||||
card.querySelector('.modifier-card-image-overlay').innerText = '+'
|
card.querySelector(".modifier-card-image-overlay").innerText = "+"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePreviewImages(val) {
|
function changePreviewImages(val) {
|
||||||
const previewImages = document.querySelectorAll('.modifier-card-image-container img')
|
const previewImages = document.querySelectorAll(".modifier-card-image-container img")
|
||||||
|
|
||||||
let previewArr = []
|
const previewArr = modifiers.flatMap((x) => x.modifiers.map((m) => m.previews))
|
||||||
|
.map((x) => x.reduce((obj, preview) => {
|
||||||
modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews)))
|
|
||||||
|
|
||||||
previewArr = previewArr.map(x => {
|
|
||||||
let obj = {}
|
|
||||||
|
|
||||||
x.forEach(preview => {
|
|
||||||
obj[preview.name] = preview.path
|
obj[preview.name] = preview.path
|
||||||
})
|
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
})
|
}, {}))
|
||||||
|
|
||||||
previewImages.forEach(previewImage => {
|
previewImages.forEach((previewImage) => {
|
||||||
const currentPreviewType = previewImage.getAttribute('preview-type')
|
const currentPreviewType = previewImage.getAttribute("preview-type")
|
||||||
const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop()
|
const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + "/").pop()
|
||||||
|
|
||||||
const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType])
|
const previews = previewArr.find((preview) => relativePreviewPath == preview[currentPreviewType])
|
||||||
|
|
||||||
if(typeof previews == 'object') {
|
if (typeof previews == "object") {
|
||||||
let preview = null
|
let preview = null
|
||||||
|
|
||||||
if (val == 'portrait') {
|
if (val == "portrait") {
|
||||||
preview = previews.portrait
|
preview = previews.portrait
|
||||||
}
|
} else if (val == "landscape") {
|
||||||
else if (val == 'landscape') {
|
|
||||||
preview = previews.landscape
|
preview = previews.landscape
|
||||||
}
|
}
|
||||||
|
|
||||||
if(preview != null) {
|
if (preview != null) {
|
||||||
previewImage.src = `${modifierThumbnailPath}/${preview}`
|
previewImage.src = `${modifierThumbnailPath}/${preview}`
|
||||||
previewImage.setAttribute('preview-type', val)
|
previewImage.setAttribute("preview-type", val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizeModifierCards(val) {
|
function resizeModifierCards(val) {
|
||||||
const cardSizePrefix = 'modifier-card-size_'
|
const cardSizePrefix = "modifier-card-size_"
|
||||||
const modifierCardClass = 'modifier-card'
|
const modifierCardClass = "modifier-card"
|
||||||
|
|
||||||
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`)
|
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`)
|
||||||
const cardSize = n => `${cardSizePrefix}${n}`
|
const cardSize = (n) => `${cardSizePrefix}${n}`
|
||||||
|
|
||||||
modifierCards.forEach(card => {
|
modifierCards.forEach((card) => {
|
||||||
// remove existing size classes
|
// remove existing size classes
|
||||||
const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix))
|
const classes = card.className.split(" ").filter((c) => !c.startsWith(cardSizePrefix))
|
||||||
card.className = classes.join(' ').trim()
|
card.className = classes.join(" ").trim()
|
||||||
|
|
||||||
if(val != 0) {
|
if (val != 0) {
|
||||||
card.classList.add(cardSize(val))
|
card.classList.add(cardSize(val))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
|
|
||||||
previewImageField.onchange = () => changePreviewImages(previewImageField.value)
|
|
||||||
|
|
||||||
modifierSettingsBtn.addEventListener('click', function(e) {
|
|
||||||
modifierSettingsOverlay.classList.add("active")
|
|
||||||
customModifiersTextBox.setSelectionRange(0, 0)
|
|
||||||
customModifiersTextBox.focus()
|
|
||||||
customModifiersInitialContent = customModifiersTextBox.value // preserve the initial content
|
|
||||||
e.stopPropagation()
|
|
||||||
})
|
|
||||||
|
|
||||||
modifierSettingsOverlay.addEventListener('keydown', function(e) {
|
|
||||||
switch (e.key) {
|
|
||||||
case "Escape": // Escape to cancel
|
|
||||||
customModifiersTextBox.value = customModifiersInitialContent // undo the changes
|
|
||||||
modifierSettingsOverlay.classList.remove("active")
|
|
||||||
e.stopPropagation()
|
|
||||||
break
|
|
||||||
case "Enter":
|
|
||||||
if (e.ctrlKey) { // Ctrl+Enter to confirm
|
|
||||||
modifierSettingsOverlay.classList.remove("active")
|
|
||||||
e.stopPropagation()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function saveCustomModifiers() {
|
function saveCustomModifiers() {
|
||||||
localStorage.setItem(CUSTOM_MODIFIERS_KEY, customModifiersTextBox.value.trim())
|
localStorage.setItem(CUSTOM_MODIFIERS_KEY, customModifiersTextBox.value.trim())
|
||||||
|
|
||||||
@ -383,7 +395,167 @@ function saveCustomModifiers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadCustomModifiers() {
|
function loadCustomModifiers() {
|
||||||
PLUGINS['MODIFIERS_LOAD'].forEach(fn=>fn.loader.call())
|
PLUGINS["MODIFIERS_LOAD"].forEach((fn) => fn.loader.call())
|
||||||
}
|
}
|
||||||
|
|
||||||
customModifiersTextBox.addEventListener('change', saveCustomModifiers)
|
function showModifierContainer() {
|
||||||
|
document.addEventListener("click", checkIfClickedOutsideDropdownElem)
|
||||||
|
|
||||||
|
modifierDropdown.dataset.active = true
|
||||||
|
editorModifiersContainer.classList.add("active")
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideModifierContainer() {
|
||||||
|
document.removeEventListener("click", checkIfClickedOutsideDropdownElem)
|
||||||
|
|
||||||
|
modifierDropdown.dataset.active = false
|
||||||
|
editorModifiersContainer.classList.remove("active")
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfClickedOutsideDropdownElem(e) {
|
||||||
|
const clickedElement = e.target
|
||||||
|
|
||||||
|
const clickedInsideSpecificElems = [modifierDropdown, editorModifiersContainer, modifierSettingsOverlay].some((div) =>
|
||||||
|
div && (div.contains(clickedElement) || div === clickedElement))
|
||||||
|
|
||||||
|
if (!clickedInsideSpecificElems && !modifierPanelFreezed) {
|
||||||
|
hideModifierContainer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseAllModifierCategory() {
|
||||||
|
const collapsibleElems = editorModifierEntries.querySelectorAll(".modifier-category .collapsible"); // needs to have ";"
|
||||||
|
|
||||||
|
[...collapsibleElems].forEach((elem) => {
|
||||||
|
const isActive = elem.classList.contains("active")
|
||||||
|
|
||||||
|
if(isActive) {
|
||||||
|
elem?.click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandAllModifierCategory() {
|
||||||
|
const collapsibleElems = editorModifierEntries.querySelectorAll(".modifier-category .collapsible"); // needs to have ";"
|
||||||
|
|
||||||
|
[...collapsibleElems].forEach((elem) => {
|
||||||
|
const isActive = elem.classList.contains("active")
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
elem?.click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
customModifiersTextBox.addEventListener("change", saveCustomModifiers)
|
||||||
|
|
||||||
|
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
|
||||||
|
previewImageField.onchange = () => changePreviewImages(previewImageField.value)
|
||||||
|
|
||||||
|
modifierSettingsOverlay.addEventListener("keydown", function(e) {
|
||||||
|
switch (e.key) {
|
||||||
|
case "Escape": // Escape to cancel
|
||||||
|
customModifiersTextBox.value = customModifiersInitialContent // undo the changes
|
||||||
|
modifierSettingsOverlay.classList.remove("active")
|
||||||
|
e.stopPropagation()
|
||||||
|
break
|
||||||
|
case "Enter":
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
// Ctrl+Enter to confirm
|
||||||
|
modifierSettingsOverlay.classList.remove("active")
|
||||||
|
e.stopPropagation()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
modifierDropdown.addEventListener("click", e => {
|
||||||
|
const targetElem = e.target
|
||||||
|
const isDropdownActive = targetElem.dataset.active == "true" ? true : false
|
||||||
|
|
||||||
|
if (!isDropdownActive)
|
||||||
|
showModifierContainer()
|
||||||
|
else
|
||||||
|
hideModifierContainer()
|
||||||
|
})
|
||||||
|
|
||||||
|
let collapsiblesBtnState = false
|
||||||
|
|
||||||
|
modifiersCollapsiblesBtn.addEventListener("click", (e) => {
|
||||||
|
const btnElem = modifiersCollapsiblesBtn
|
||||||
|
|
||||||
|
const collapseText = "Collapse Categories"
|
||||||
|
const expandText = "Expand Categories"
|
||||||
|
|
||||||
|
const collapseIconClasses = ["fa-solid", "fa-square-minus"]
|
||||||
|
const expandIconClasses = ["fa-solid", "fa-square-plus"]
|
||||||
|
|
||||||
|
const iconElem = btnElem.querySelector(".modifiers-action-icon")
|
||||||
|
const textElem = btnElem.querySelector(".modifiers-action-text")
|
||||||
|
|
||||||
|
if (collapsiblesBtnState) {
|
||||||
|
collapseAllModifierCategory()
|
||||||
|
|
||||||
|
collapsiblesBtnState = false
|
||||||
|
|
||||||
|
collapseIconClasses.forEach((c) => iconElem.classList.remove(c))
|
||||||
|
expandIconClasses.forEach((c) => iconElem.classList.add(c))
|
||||||
|
|
||||||
|
textElem.innerText = expandText
|
||||||
|
} else {
|
||||||
|
expandAllModifierCategory()
|
||||||
|
|
||||||
|
collapsiblesBtnState = true
|
||||||
|
|
||||||
|
expandIconClasses.forEach((c) => iconElem.classList.remove(c))
|
||||||
|
collapseIconClasses.forEach((c) => iconElem.classList.add(c))
|
||||||
|
|
||||||
|
textElem.innerText = collapseText
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let containerSizeBtnState = false
|
||||||
|
|
||||||
|
modifiersContainerSizeBtn.addEventListener("click", (e) => {
|
||||||
|
const btnElem = modifiersContainerSizeBtn
|
||||||
|
|
||||||
|
const maximizeIconClasses = ["fa-solid", "fa-expand"]
|
||||||
|
const revertIconClasses = ["fa-solid", "fa-compress"]
|
||||||
|
|
||||||
|
modifiersMainContainer.classList.toggle("modifiers-maximized")
|
||||||
|
|
||||||
|
if(containerSizeBtnState) {
|
||||||
|
revertIconClasses.forEach((c) => btnElem.classList.remove(c))
|
||||||
|
maximizeIconClasses.forEach((c) => btnElem.classList.add(c))
|
||||||
|
|
||||||
|
containerSizeBtnState = false
|
||||||
|
} else {
|
||||||
|
maximizeIconClasses.forEach((c) => btnElem.classList.remove(c))
|
||||||
|
revertIconClasses.forEach((c) => btnElem.classList.add(c))
|
||||||
|
|
||||||
|
containerSizeBtnState = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
modifierSettingsBtn.addEventListener("click", (e) => {
|
||||||
|
modifierSettingsOverlay.classList.add("active")
|
||||||
|
customModifiersTextBox.setSelectionRange(0, 0)
|
||||||
|
customModifiersTextBox.focus()
|
||||||
|
customModifiersInitialContent = customModifiersTextBox.value // preserve the initial content
|
||||||
|
e.stopPropagation()
|
||||||
|
})
|
||||||
|
|
||||||
|
modifiersCloseBtn.addEventListener("click", (e) => {
|
||||||
|
hideModifierContainer()
|
||||||
|
})
|
||||||
|
|
||||||
|
// prevents the modifier panel closing at the same time as the settings overlay
|
||||||
|
new MutationObserver(() => {
|
||||||
|
const isActive = modifierSettingsOverlay.classList.contains("active")
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
modifierPanelFreezed = true
|
||||||
|
|
||||||
|
setTimeout(() => modifierPanelFreezed = false, 25)
|
||||||
|
}
|
||||||
|
}).observe(modifierSettingsOverlay, { attributes: true })
|
1418
ui/media/js/main.js
1418
ui/media/js/main.js
File diff suppressed because it is too large
Load Diff
@ -3,25 +3,33 @@
|
|||||||
* @readonly
|
* @readonly
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
var ParameterType = {
|
var ParameterType = {
|
||||||
checkbox: "checkbox",
|
checkbox: "checkbox",
|
||||||
select: "select",
|
select: "select",
|
||||||
select_multiple: "select_multiple",
|
select_multiple: "select_multiple",
|
||||||
slider: "slider",
|
slider: "slider",
|
||||||
custom: "custom",
|
custom: "custom",
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element shortcuts
|
||||||
|
*/
|
||||||
|
let parametersTable = document.querySelector("#system-settings-table")
|
||||||
|
let networkParametersTable = document.querySelector("#system-settings-network-table")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSDoc style
|
* JSDoc style
|
||||||
* @typedef {object} Parameter
|
* @typedef {object} Parameter
|
||||||
* @property {string} id
|
* @property {string} id
|
||||||
* @property {ParameterType} type
|
* @property {keyof ParameterType} type
|
||||||
* @property {string} label
|
* @property {string | (parameter: Parameter) => (HTMLElement | string)} label
|
||||||
* @property {?string} note
|
* @property {string | (parameter: Parameter) => (HTMLElement | string) | undefined} note
|
||||||
|
* @property {(parameter: Parameter) => (HTMLElement | string) | undefined} render
|
||||||
|
* @property {string | undefined} icon
|
||||||
* @property {number|boolean|string} default
|
* @property {number|boolean|string} default
|
||||||
|
* @property {boolean?} saveInAppConfig
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/** @type {Array.<Parameter>} */
|
/** @type {Array.<Parameter>} */
|
||||||
var PARAMETERS = [
|
var PARAMETERS = [
|
||||||
{
|
{
|
||||||
@ -30,13 +38,14 @@ var PARAMETERS = [
|
|||||||
label: "Theme",
|
label: "Theme",
|
||||||
default: "theme-default",
|
default: "theme-default",
|
||||||
note: "customize the look and feel of the ui",
|
note: "customize the look and feel of the ui",
|
||||||
options: [ // Note: options expanded dynamically
|
options: [
|
||||||
|
// Note: options expanded dynamically
|
||||||
{
|
{
|
||||||
value: "theme-default",
|
value: "theme-default",
|
||||||
label: "Default"
|
label: "Default",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
icon: "fa-palette"
|
icon: "fa-palette",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "save_to_disk",
|
id: "save_to_disk",
|
||||||
@ -52,7 +61,7 @@ var PARAMETERS = [
|
|||||||
label: "Save Location",
|
label: "Save Location",
|
||||||
render: (parameter) => {
|
render: (parameter) => {
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}" size="30" disabled>`
|
return `<input id="${parameter.id}" name="${parameter.id}" size="30" disabled>`
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "metadata_output_format",
|
id: "metadata_output_format",
|
||||||
@ -63,20 +72,28 @@ var PARAMETERS = [
|
|||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "none",
|
value: "none",
|
||||||
label: "none"
|
label: "none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "txt",
|
value: "txt",
|
||||||
label: "txt"
|
label: "txt",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "json",
|
value: "json",
|
||||||
label: "json"
|
label: "json",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "embed",
|
value: "embed",
|
||||||
label: "embed"
|
label: "embed",
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
value: "embed,txt",
|
||||||
|
label: "embed & txt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "embed,json",
|
||||||
|
label: "embed & json",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -110,21 +127,23 @@ var PARAMETERS = [
|
|||||||
note: "starts the default browser on startup",
|
note: "starts the default browser on startup",
|
||||||
icon: "fa-window-restore",
|
icon: "fa-window-restore",
|
||||||
default: true,
|
default: true,
|
||||||
|
saveInAppConfig: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "vram_usage_level",
|
id: "vram_usage_level",
|
||||||
type: ParameterType.select,
|
type: ParameterType.select,
|
||||||
label: "GPU Memory Usage",
|
label: "GPU Memory Usage",
|
||||||
note: "Faster performance requires more GPU memory (VRAM)<br/><br/>" +
|
note:
|
||||||
|
"Faster performance requires more GPU memory (VRAM)<br/><br/>" +
|
||||||
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
|
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
|
||||||
"<b>High:</b> fastest, maximum GPU memory usage</br>" +
|
"<b>High:</b> fastest, maximum GPU memory usage</br>" +
|
||||||
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory",
|
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory",
|
||||||
icon: "fa-forward",
|
icon: "fa-forward",
|
||||||
default: "balanced",
|
default: "balanced",
|
||||||
options: [
|
options: [
|
||||||
{value: "balanced", label: "Balanced"},
|
{ value: "balanced", label: "Balanced" },
|
||||||
{value: "high", label: "High"},
|
{ value: "high", label: "High" },
|
||||||
{value: "low", label: "Low"}
|
{ value: "low", label: "Low" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -160,7 +179,8 @@ var PARAMETERS = [
|
|||||||
id: "confirm_dangerous_actions",
|
id: "confirm_dangerous_actions",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Confirm dangerous actions",
|
label: "Confirm dangerous actions",
|
||||||
note: "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
|
note:
|
||||||
|
"Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
|
||||||
icon: "fa-check-double",
|
icon: "fa-check-double",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
@ -168,25 +188,31 @@ var PARAMETERS = [
|
|||||||
id: "listen_to_network",
|
id: "listen_to_network",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Make Stable Diffusion available on your network",
|
label: "Make Stable Diffusion available on your network",
|
||||||
note: "Other devices on your network can access this web page",
|
note: "Other devices on your network can access this web page. Please restart the program after changing this.",
|
||||||
icon: "fa-network-wired",
|
icon: "fa-network-wired",
|
||||||
default: true,
|
default: true,
|
||||||
|
saveInAppConfig: true,
|
||||||
|
table: networkParametersTable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "listen_port",
|
id: "listen_port",
|
||||||
type: ParameterType.custom,
|
type: ParameterType.custom,
|
||||||
label: "Network port",
|
label: "Network port",
|
||||||
note: "Port that this server listens to. The '9000' part in 'http://localhost:9000'",
|
note:
|
||||||
|
"Port that this server listens to. The '9000' part in 'http://localhost:9000'. Please restart the program after changing this.",
|
||||||
icon: "fa-anchor",
|
icon: "fa-anchor",
|
||||||
render: (parameter) => {
|
render: (parameter) => {
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
|
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
|
||||||
}
|
},
|
||||||
|
saveInAppConfig: true,
|
||||||
|
table: networkParametersTable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "use_beta_channel",
|
id: "use_beta_channel",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Beta channel",
|
label: "Beta channel",
|
||||||
note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.",
|
note:
|
||||||
|
"Get the latest features immediately (but could be less stable). Please restart the program after changing this.",
|
||||||
icon: "fa-fire",
|
icon: "fa-fire",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
@ -194,14 +220,31 @@ var PARAMETERS = [
|
|||||||
id: "test_diffusers",
|
id: "test_diffusers",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Test Diffusers",
|
label: "Test 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.",
|
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.",
|
||||||
icon: "fa-bolt",
|
icon: "fa-bolt",
|
||||||
default: false,
|
default: false,
|
||||||
|
saveInAppConfig: true,
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
id: "cloudflare",
|
||||||
|
type: ParameterType.custom,
|
||||||
|
label: "Cloudflare tunnel",
|
||||||
|
note: `<span id="cloudflare-off">Create a VPN tunnel to share your Easy Diffusion instance with your friends. This will
|
||||||
|
generate a web server address on the public Internet for your Easy Diffusion instance. </span>
|
||||||
|
<div id="cloudflare-on" class="displayNone"><div>This Easy Diffusion server is available on the Internet using the
|
||||||
|
address:</div><div><div id="cloudflare-address"></div><button id="copy-cloudflare-address">Copy</button></div></div>
|
||||||
|
<b>Anyone knowing this address can access your server.</b> The address of your server will change each time
|
||||||
|
you share a session.<br>
|
||||||
|
Uses <a href="https://try.cloudflare.com/" target="_blank">Cloudflare services</a>.`,
|
||||||
|
icon: ["fa-brands", "fa-cloudflare"],
|
||||||
|
render: () => '<button id="toggle-cloudflare-tunnel" class="primaryButton">Start</button>',
|
||||||
|
table: networkParametersTable,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
function getParameterSettingsEntry(id) {
|
function getParameterSettingsEntry(id) {
|
||||||
let parameter = PARAMETERS.filter(p => p.id === id)
|
let parameter = PARAMETERS.filter((p) => p.id === id)
|
||||||
if (parameter.length === 0) {
|
if (parameter.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -209,63 +252,125 @@ function getParameterSettingsEntry(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sliderUpdate(event) {
|
function sliderUpdate(event) {
|
||||||
if (event.srcElement.id.endsWith('-input')) {
|
if (event.srcElement.id.endsWith("-input")) {
|
||||||
let slider = document.getElementById(event.srcElement.id.slice(0,-6))
|
let slider = document.getElementById(event.srcElement.id.slice(0, -6))
|
||||||
slider.value = event.srcElement.value
|
slider.value = event.srcElement.value
|
||||||
slider.dispatchEvent(new Event("change"))
|
slider.dispatchEvent(new Event("change"))
|
||||||
} else {
|
} else {
|
||||||
let field = document.getElementById(event.srcElement.id+'-input')
|
let field = document.getElementById(event.srcElement.id + "-input")
|
||||||
field.value = event.srcElement.value
|
field.value = event.srcElement.value
|
||||||
field.dispatchEvent(new Event("change"))
|
field.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Parameter} parameter
|
||||||
|
* @returns {string | HTMLElement}
|
||||||
|
*/
|
||||||
function getParameterElement(parameter) {
|
function getParameterElement(parameter) {
|
||||||
switch (parameter.type) {
|
switch (parameter.type) {
|
||||||
case ParameterType.checkbox:
|
case ParameterType.checkbox:
|
||||||
var is_checked = parameter.default ? " checked" : "";
|
var is_checked = parameter.default ? " checked" : ""
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}"${is_checked} type="checkbox">`
|
return `<input id="${parameter.id}" name="${parameter.id}"${is_checked} type="checkbox">`
|
||||||
case ParameterType.select:
|
case ParameterType.select:
|
||||||
case ParameterType.select_multiple:
|
case ParameterType.select_multiple:
|
||||||
var options = (parameter.options || []).map(option => `<option value="${option.value}">${option.label}</option>`).join("")
|
var options = (parameter.options || [])
|
||||||
var multiple = (parameter.type == ParameterType.select_multiple ? 'multiple' : '')
|
.map((option) => `<option value="${option.value}">${option.label}</option>`)
|
||||||
|
.join("")
|
||||||
|
var multiple = parameter.type == ParameterType.select_multiple ? "multiple" : ""
|
||||||
return `<select id="${parameter.id}" name="${parameter.id}" ${multiple}>${options}</select>`
|
return `<select id="${parameter.id}" name="${parameter.id}" ${multiple}>${options}</select>`
|
||||||
case ParameterType.slider:
|
case ParameterType.slider:
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}" class="editor-slider" type="range" value="${parameter.default}" min="${parameter.slider_min}" max="${parameter.slider_max}" oninput="sliderUpdate(event)"> <input id="${parameter.id}-input" name="${parameter.id}-input" size="4" value="${parameter.default}" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)" oninput="sliderUpdate(event)"> ${parameter.slider_unit}`
|
return `<input id="${parameter.id}" name="${parameter.id}" class="editor-slider" type="range" value="${parameter.default}" min="${parameter.slider_min}" max="${parameter.slider_max}" oninput="sliderUpdate(event)"> <input id="${parameter.id}-input" name="${parameter.id}-input" size="4" value="${parameter.default}" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)" oninput="sliderUpdate(event)"> ${parameter.slider_unit}`
|
||||||
case ParameterType.custom:
|
case ParameterType.custom:
|
||||||
return parameter.render(parameter)
|
return parameter.render(parameter)
|
||||||
default:
|
default:
|
||||||
console.error(`Invalid type for parameter ${parameter.id}`);
|
console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`)
|
||||||
return "ERROR: Invalid Type"
|
return "ERROR: Invalid Type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let parametersTable = document.querySelector("#system-settings .parameters-table")
|
/**
|
||||||
/* fill in the system settings popup table */
|
* fill in the system settings popup table
|
||||||
function initParameters() {
|
* @param {Array<Parameter> | undefined} parameters
|
||||||
PARAMETERS.forEach(parameter => {
|
* */
|
||||||
var element = getParameterElement(parameter)
|
function initParameters(parameters) {
|
||||||
var note = parameter.note ? `<small>${parameter.note}</small>` : "";
|
parameters.forEach((parameter) => {
|
||||||
var icon = parameter.icon ? `<i class="fa ${parameter.icon}"></i>` : "";
|
const element = getParameterElement(parameter)
|
||||||
var newrow = document.createElement('div')
|
const elementWrapper = createElement("div")
|
||||||
newrow.innerHTML = `
|
if (element instanceof Node) {
|
||||||
<div>${icon}</div>
|
elementWrapper.appendChild(element)
|
||||||
<div><label for="${parameter.id}">${parameter.label}</label>${note}</div>
|
} else {
|
||||||
<div>${element}</div>`
|
elementWrapper.innerHTML = element
|
||||||
parametersTable.appendChild(newrow)
|
}
|
||||||
|
|
||||||
|
const note = typeof parameter.note === "function" ? parameter.note(parameter) : parameter.note
|
||||||
|
const noteElements = []
|
||||||
|
if (note) {
|
||||||
|
const noteElement = createElement("small")
|
||||||
|
if (note instanceof Node) {
|
||||||
|
noteElement.appendChild(note)
|
||||||
|
} else {
|
||||||
|
noteElement.innerHTML = note || ""
|
||||||
|
}
|
||||||
|
noteElements.push(noteElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(parameter.icon) == "string") {
|
||||||
|
parameter.icon = [parameter.icon]
|
||||||
|
}
|
||||||
|
const icon = parameter.icon ? [createElement("i", undefined, ["fa", ...parameter.icon])] : []
|
||||||
|
|
||||||
|
const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label
|
||||||
|
const labelElement = createElement("label", { for: parameter.id })
|
||||||
|
if (label instanceof Node) {
|
||||||
|
labelElement.appendChild(label)
|
||||||
|
} else {
|
||||||
|
labelElement.innerHTML = label
|
||||||
|
}
|
||||||
|
|
||||||
|
const newrow = createElement(
|
||||||
|
"div",
|
||||||
|
{ "data-setting-id": parameter.id, "data-save-in-app-config": parameter.saveInAppConfig },
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
|
createElement("div", undefined, undefined, icon),
|
||||||
|
createElement("div", undefined, undefined, [labelElement, ...noteElements]),
|
||||||
|
elementWrapper,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
let p = parametersTable
|
||||||
|
if (parameter.table) {
|
||||||
|
p = parameter.table
|
||||||
|
}
|
||||||
|
p.appendChild(newrow)
|
||||||
|
|
||||||
parameter.settingsEntry = newrow
|
parameter.settingsEntry = newrow
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
initParameters()
|
initParameters(PARAMETERS)
|
||||||
|
|
||||||
let vramUsageLevelField = document.querySelector('#vram_usage_level')
|
// listen to parameters from plugins
|
||||||
let useCPUField = document.querySelector('#use_cpu')
|
PARAMETERS.addEventListener("push", (...items) => {
|
||||||
let autoPickGPUsField = document.querySelector('#auto_pick_gpus')
|
initParameters(items)
|
||||||
let useGPUsField = document.querySelector('#use_gpus')
|
|
||||||
let saveToDiskField = document.querySelector('#save_to_disk')
|
if (items.find((item) => item.saveInAppConfig)) {
|
||||||
let diskPathField = document.querySelector('#diskPath')
|
console.log(
|
||||||
let metadataOutputFormatField = document.querySelector('#metadata_output_format')
|
"Reloading app config for new parameters",
|
||||||
|
items.map((p) => p.id)
|
||||||
|
)
|
||||||
|
getAppConfig()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let vramUsageLevelField = document.querySelector("#vram_usage_level")
|
||||||
|
let useCPUField = document.querySelector("#use_cpu")
|
||||||
|
let autoPickGPUsField = document.querySelector("#auto_pick_gpus")
|
||||||
|
let useGPUsField = document.querySelector("#use_gpus")
|
||||||
|
let saveToDiskField = document.querySelector("#save_to_disk")
|
||||||
|
let diskPathField = document.querySelector("#diskPath")
|
||||||
|
let metadataOutputFormatField = document.querySelector("#metadata_output_format")
|
||||||
let listenToNetworkField = document.querySelector("#listen_to_network")
|
let listenToNetworkField = document.querySelector("#listen_to_network")
|
||||||
let listenPortField = document.querySelector("#listen_port")
|
let listenPortField = document.querySelector("#listen_port")
|
||||||
let useBetaChannelField = document.querySelector("#use_beta_channel")
|
let useBetaChannelField = document.querySelector("#use_beta_channel")
|
||||||
@ -273,34 +378,38 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star
|
|||||||
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
|
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
|
||||||
let testDiffusers = document.querySelector("#test_diffusers")
|
let testDiffusers = document.querySelector("#test_diffusers")
|
||||||
|
|
||||||
let saveSettingsBtn = document.querySelector('#save-system-settings-btn')
|
let saveSettingsBtn = document.querySelector("#save-system-settings-btn")
|
||||||
|
|
||||||
|
|
||||||
async function changeAppConfig(configDelta) {
|
async function changeAppConfig(configDelta) {
|
||||||
try {
|
try {
|
||||||
let res = await fetch('/app_config', {
|
let res = await fetch("/app_config", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(configDelta)
|
body: JSON.stringify(configDelta),
|
||||||
})
|
})
|
||||||
res = await res.json()
|
res = await res.json()
|
||||||
|
|
||||||
console.log('set config status response', res)
|
console.log("set config status response", res)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('set config status error', e)
|
console.log("set config status error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAppConfig() {
|
async function getAppConfig() {
|
||||||
try {
|
try {
|
||||||
let res = await fetch('/get/app_config')
|
let res = await fetch("/get/app_config")
|
||||||
const config = await res.json()
|
const config = await res.json()
|
||||||
|
|
||||||
if (config.update_branch === 'beta') {
|
applySettingsFromConfig(config)
|
||||||
|
|
||||||
|
// custom overrides
|
||||||
|
if (config.update_branch === "beta") {
|
||||||
useBetaChannelField.checked = true
|
useBetaChannelField.checked = true
|
||||||
document.querySelector("#updateBranchLabel").innerText = "(beta)"
|
document.querySelector("#updateBranchLabel").innerText = "(beta)"
|
||||||
|
} else {
|
||||||
|
getParameterSettingsEntry("test_diffusers").style.display = "none"
|
||||||
}
|
}
|
||||||
if (config.ui && config.ui.open_browser_on_start === false) {
|
if (config.ui && config.ui.open_browser_on_start === false) {
|
||||||
uiOpenBrowserOnStartField.checked = false
|
uiOpenBrowserOnStartField.checked = false
|
||||||
@ -311,86 +420,146 @@ async function getAppConfig() {
|
|||||||
if (config.net && config.net.listen_port !== undefined) {
|
if (config.net && config.net.listen_port !== undefined) {
|
||||||
listenPortField.value = config.net.listen_port
|
listenPortField.value = config.net.listen_port
|
||||||
}
|
}
|
||||||
if (config.test_diffusers !== undefined) {
|
|
||||||
testDiffusers.checked = config.test_diffusers
|
const testDiffusersEnabled = config.test_diffusers && config.update_branch !== "main"
|
||||||
document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none')
|
testDiffusers.checked = testDiffusersEnabled
|
||||||
|
|
||||||
|
if (!testDiffusersEnabled) {
|
||||||
|
document.querySelector("#lora_model_container").style.display = "none"
|
||||||
|
document.querySelector("#lora_alpha_container").style.display = "none"
|
||||||
|
document.querySelector("#tiling_container").style.display = "none"
|
||||||
|
|
||||||
|
document.querySelectorAll("#sampler_name option.diffusers-only").forEach((option) => {
|
||||||
|
option.style.display = "none"
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
document.querySelector("#lora_model_container").style.display = ""
|
||||||
|
document.querySelector("#lora_alpha_container").style.display = loraModelField.value ? "" : "none"
|
||||||
|
document.querySelector("#tiling_container").style.display = ""
|
||||||
|
|
||||||
|
document.querySelectorAll("#sampler_name option.k_diffusion-only").forEach((option) => {
|
||||||
|
option.disabled = true
|
||||||
|
})
|
||||||
|
document.querySelector("#clip_skip_config").classList.remove("displayNone")
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('get config status response', config)
|
console.log("get config status response", config)
|
||||||
|
|
||||||
|
return config
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('get config status error', e)
|
console.log("get config status error", e)
|
||||||
|
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToDiskField.addEventListener('change', function(e) {
|
function applySettingsFromConfig(config) {
|
||||||
|
Array.from(parametersTable.children).forEach((parameterRow) => {
|
||||||
|
if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === "true") {
|
||||||
|
const configValue = config[parameterRow.dataset.settingId]
|
||||||
|
const parameterElement =
|
||||||
|
document.getElementById(parameterRow.dataset.settingId) ||
|
||||||
|
parameterRow.querySelector("input") ||
|
||||||
|
parameterRow.querySelector("select")
|
||||||
|
|
||||||
|
switch (parameterElement?.tagName) {
|
||||||
|
case "INPUT":
|
||||||
|
if (parameterElement.type === "checkbox") {
|
||||||
|
parameterElement.checked = configValue
|
||||||
|
} else {
|
||||||
|
parameterElement.value = configValue
|
||||||
|
}
|
||||||
|
parameterElement.dispatchEvent(new Event("change"))
|
||||||
|
break
|
||||||
|
case "SELECT":
|
||||||
|
if (Array.isArray(configValue)) {
|
||||||
|
Array.from(parameterElement.options).forEach((option) => {
|
||||||
|
if (configValue.includes(option.value || option.text)) {
|
||||||
|
option.selected = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
parameterElement.value = configValue
|
||||||
|
}
|
||||||
|
parameterElement.dispatchEvent(new Event("change"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToDiskField.addEventListener("change", function(e) {
|
||||||
diskPathField.disabled = !this.checked
|
diskPathField.disabled = !this.checked
|
||||||
metadataOutputFormatField.disabled = !this.checked
|
metadataOutputFormatField.disabled = !this.checked
|
||||||
})
|
})
|
||||||
|
|
||||||
function getCurrentRenderDeviceSelection() {
|
function getCurrentRenderDeviceSelection() {
|
||||||
let selectedGPUs = $('#use_gpus').val()
|
let selectedGPUs = $("#use_gpus").val()
|
||||||
|
|
||||||
if (useCPUField.checked && !autoPickGPUsField.checked) {
|
if (useCPUField.checked && !autoPickGPUsField.checked) {
|
||||||
return 'cpu'
|
return "cpu"
|
||||||
}
|
}
|
||||||
if (autoPickGPUsField.checked || selectedGPUs.length == 0) {
|
if (autoPickGPUsField.checked || selectedGPUs.length == 0) {
|
||||||
return 'auto'
|
return "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedGPUs.join(',')
|
return selectedGPUs.join(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
useCPUField.addEventListener('click', function() {
|
useCPUField.addEventListener("click", function() {
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus')
|
let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
|
||||||
if (this.checked) {
|
if (this.checked) {
|
||||||
gpuSettingEntry.style.display = 'none'
|
gpuSettingEntry.style.display = "none"
|
||||||
autoPickGPUSettingEntry.style.display = 'none'
|
autoPickGPUSettingEntry.style.display = "none"
|
||||||
autoPickGPUsField.setAttribute('data-old-value', autoPickGPUsField.checked)
|
autoPickGPUsField.setAttribute("data-old-value", autoPickGPUsField.checked)
|
||||||
autoPickGPUsField.checked = false
|
autoPickGPUsField.checked = false
|
||||||
} else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) {
|
} else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) {
|
||||||
gpuSettingEntry.style.display = ''
|
gpuSettingEntry.style.display = ""
|
||||||
autoPickGPUSettingEntry.style.display = ''
|
autoPickGPUSettingEntry.style.display = ""
|
||||||
let oldVal = autoPickGPUsField.getAttribute('data-old-value')
|
let oldVal = autoPickGPUsField.getAttribute("data-old-value")
|
||||||
if (oldVal === null || oldVal === undefined) { // the UI started with CPU selected by default
|
if (oldVal === null || oldVal === undefined) {
|
||||||
|
// the UI started with CPU selected by default
|
||||||
autoPickGPUsField.checked = true
|
autoPickGPUsField.checked = true
|
||||||
} else {
|
} else {
|
||||||
autoPickGPUsField.checked = (oldVal === 'true')
|
autoPickGPUsField.checked = oldVal === "true"
|
||||||
}
|
}
|
||||||
gpuSettingEntry.style.display = (autoPickGPUsField.checked ? 'none' : '')
|
gpuSettingEntry.style.display = autoPickGPUsField.checked ? "none" : ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useGPUsField.addEventListener('click', function() {
|
useGPUsField.addEventListener("click", function() {
|
||||||
let selectedGPUs = $('#use_gpus').val()
|
let selectedGPUs = $("#use_gpus").val()
|
||||||
autoPickGPUsField.checked = (selectedGPUs.length === 0)
|
autoPickGPUsField.checked = selectedGPUs.length === 0
|
||||||
})
|
})
|
||||||
|
|
||||||
autoPickGPUsField.addEventListener('click', function() {
|
autoPickGPUsField.addEventListener("click", function() {
|
||||||
if (this.checked) {
|
if (this.checked) {
|
||||||
$('#use_gpus').val([])
|
$("#use_gpus").val([])
|
||||||
}
|
}
|
||||||
|
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
gpuSettingEntry.style.display = (this.checked ? 'none' : '')
|
gpuSettingEntry.style.display = this.checked ? "none" : ""
|
||||||
})
|
})
|
||||||
|
|
||||||
async function setDiskPath(defaultDiskPath, force=false) {
|
async function setDiskPath(defaultDiskPath, force = false) {
|
||||||
var diskPath = getSetting("diskPath")
|
var diskPath = getSetting("diskPath")
|
||||||
if (force || diskPath == '' || diskPath == undefined || diskPath == "undefined") {
|
if (force || diskPath == "" || diskPath == undefined || diskPath == "undefined") {
|
||||||
setSetting("diskPath", defaultDiskPath)
|
setSetting("diskPath", defaultDiskPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDeviceInfo(devices) {
|
function setDeviceInfo(devices) {
|
||||||
let cpu = devices.all.cpu.name
|
let cpu = devices.all.cpu.name
|
||||||
let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu')
|
let allGPUs = Object.keys(devices.all).filter((d) => d != "cpu")
|
||||||
let activeGPUs = Object.keys(devices.active)
|
let activeGPUs = Object.keys(devices.active)
|
||||||
|
|
||||||
function ID_TO_TEXT(d) {
|
function ID_TO_TEXT(d) {
|
||||||
let info = devices.all[d]
|
let info = devices.all[d]
|
||||||
if ("mem_free" in info && "mem_total" in info) {
|
if ("mem_free" in info && "mem_total" in info) {
|
||||||
return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)</small>`
|
return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(
|
||||||
|
1
|
||||||
|
)} Gb total)</small>`
|
||||||
} else {
|
} else {
|
||||||
return `${info.name} <small>(${d}) (no memory info)</small>`
|
return `${info.name} <small>(${d}) (no memory info)</small>`
|
||||||
}
|
}
|
||||||
@ -399,63 +568,73 @@ function setDeviceInfo(devices) {
|
|||||||
allGPUs = allGPUs.map(ID_TO_TEXT)
|
allGPUs = allGPUs.map(ID_TO_TEXT)
|
||||||
activeGPUs = activeGPUs.map(ID_TO_TEXT)
|
activeGPUs = activeGPUs.map(ID_TO_TEXT)
|
||||||
|
|
||||||
let systemInfoEl = document.querySelector('#system-info')
|
let systemInfoEl = document.querySelector("#system-info")
|
||||||
systemInfoEl.querySelector('#system-info-cpu').innerText = cpu
|
systemInfoEl.querySelector("#system-info-cpu").innerText = cpu
|
||||||
systemInfoEl.querySelector('#system-info-gpus-all').innerHTML = allGPUs.join('</br>')
|
systemInfoEl.querySelector("#system-info-gpus-all").innerHTML = allGPUs.join("</br>")
|
||||||
systemInfoEl.querySelector('#system-info-rendering-devices').innerHTML = activeGPUs.join('</br>')
|
systemInfoEl.querySelector("#system-info-rendering-devices").innerHTML = activeGPUs.join("</br>")
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHostInfo(hosts) {
|
function setHostInfo(hosts) {
|
||||||
let port = listenPortField.value
|
let port = listenPortField.value
|
||||||
hosts = hosts.map(addr => `http://${addr}:${port}/`).map(url => `<div><a href="${url}">${url}</a></div>`)
|
hosts = hosts.map((addr) => `http://${addr}:${port}/`).map((url) => `<div><a href="${url}">${url}</a></div>`)
|
||||||
document.querySelector('#system-info-server-hosts').innerHTML = hosts.join('')
|
document.querySelector("#system-info-server-hosts").innerHTML = hosts.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSystemInfo() {
|
async function getSystemInfo() {
|
||||||
try {
|
try {
|
||||||
const res = await SD.getSystemInfo()
|
const res = await SD.getSystemInfo()
|
||||||
let devices = res['devices']
|
let devices = res["devices"]
|
||||||
|
|
||||||
let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu')
|
let allDeviceIds = Object.keys(devices["all"]).filter((d) => d !== "cpu")
|
||||||
let activeDeviceIds = Object.keys(devices['active']).filter(d => d !== 'cpu')
|
let activeDeviceIds = Object.keys(devices["active"]).filter((d) => d !== "cpu")
|
||||||
|
|
||||||
if (activeDeviceIds.length === 0) {
|
if (activeDeviceIds.length === 0) {
|
||||||
useCPUField.checked = true
|
useCPUField.checked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) {
|
if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) {
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
gpuSettingEntry.style.display = 'none'
|
gpuSettingEntry.style.display = "none"
|
||||||
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus')
|
let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
|
||||||
autoPickGPUSettingEntry.style.display = 'none'
|
autoPickGPUSettingEntry.style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allDeviceIds.length === 0) {
|
if (allDeviceIds.length === 0) {
|
||||||
useCPUField.checked = true
|
useCPUField.checked = true
|
||||||
useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory
|
useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory
|
||||||
|
|
||||||
|
getParameterSettingsEntry("use_cpu").addEventListener("click", function() {
|
||||||
|
alert(
|
||||||
|
"Sorry, we could not find a compatible graphics card! Easy Diffusion supports graphics cards with minimum 2 GB of RAM. " +
|
||||||
|
"Only NVIDIA cards are supported on Windows. NVIDIA and AMD cards are supported on Linux.<br/><br/>" +
|
||||||
|
"If you have a compatible graphics card, please try updating to the latest drivers.<br/><br/>" +
|
||||||
|
"Only the CPU can be used for generating images, without a compatible graphics card.",
|
||||||
|
"No compatible graphics card found!"
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
autoPickGPUsField.checked = (devices['config'] === 'auto')
|
autoPickGPUsField.checked = devices["config"] === "auto"
|
||||||
|
|
||||||
useGPUsField.innerHTML = ''
|
useGPUsField.innerHTML = ""
|
||||||
allDeviceIds.forEach(device => {
|
allDeviceIds.forEach((device) => {
|
||||||
let deviceName = devices['all'][device]['name']
|
let deviceName = devices["all"][device]["name"]
|
||||||
let deviceOption = `<option value="${device}">${deviceName} (${device})</option>`
|
let deviceOption = `<option value="${device}">${deviceName} (${device})</option>`
|
||||||
useGPUsField.insertAdjacentHTML('beforeend', deviceOption)
|
useGPUsField.insertAdjacentHTML("beforeend", deviceOption)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (autoPickGPUsField.checked) {
|
if (autoPickGPUsField.checked) {
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
gpuSettingEntry.style.display = 'none'
|
gpuSettingEntry.style.display = "none"
|
||||||
} else {
|
} else {
|
||||||
$('#use_gpus').val(activeDeviceIds)
|
$("#use_gpus").val(activeDeviceIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
setDeviceInfo(devices)
|
document.dispatchEvent(new CustomEvent("system_info_update", { detail: devices }))
|
||||||
setHostInfo(res['hosts'])
|
setHostInfo(res["hosts"])
|
||||||
let force = false
|
let force = false
|
||||||
if (res['enforce_output_dir'] !== undefined) {
|
if (res["enforce_output_dir"] !== undefined) {
|
||||||
force = res['enforce_output_dir']
|
force = res["enforce_output_dir"]
|
||||||
if (force == true) {
|
if (force == true) {
|
||||||
saveToDiskField.checked = true
|
saveToDiskField.checked = true
|
||||||
metadataOutputFormatField.disabled = false
|
metadataOutputFormatField.disabled = false
|
||||||
@ -463,31 +642,81 @@ async function getSystemInfo() {
|
|||||||
saveToDiskField.disabled = force
|
saveToDiskField.disabled = force
|
||||||
diskPathField.disabled = force
|
diskPathField.disabled = force
|
||||||
}
|
}
|
||||||
setDiskPath(res['default_output_dir'], force)
|
setDiskPath(res["default_output_dir"], force)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error fetching devices', e)
|
console.log("error fetching devices", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSettingsBtn.addEventListener('click', function() {
|
saveSettingsBtn.addEventListener("click", function() {
|
||||||
if (listenPortField.value == '') {
|
if (listenPortField.value == "") {
|
||||||
alert('The network port field must not be empty.')
|
alert("The network port field must not be empty.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (listenPortField.value < 1 || listenPortField.value > 65535) {
|
if (listenPortField.value < 1 || listenPortField.value > 65535) {
|
||||||
alert('The network port must be a number from 1 to 65535')
|
alert("The network port must be a number from 1 to 65535")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let updateBranch = (useBetaChannelField.checked ? 'beta' : 'main')
|
const updateBranch = useBetaChannelField.checked ? "beta" : "main"
|
||||||
changeAppConfig({
|
|
||||||
'render_devices': getCurrentRenderDeviceSelection(),
|
const updateAppConfigRequest = {
|
||||||
'update_branch': updateBranch,
|
render_devices: getCurrentRenderDeviceSelection(),
|
||||||
'ui_open_browser_on_start': uiOpenBrowserOnStartField.checked,
|
update_branch: updateBranch,
|
||||||
'listen_to_network': listenToNetworkField.checked,
|
}
|
||||||
'listen_port': listenPortField.value,
|
|
||||||
'test_diffusers': testDiffusers.checked
|
document.querySelectorAll('#system-settings [data-setting-id]').forEach((parameterRow) => {
|
||||||
|
if (parameterRow.dataset.saveInAppConfig === "true") {
|
||||||
|
const parameterElement =
|
||||||
|
document.getElementById(parameterRow.dataset.settingId) ||
|
||||||
|
parameterRow.querySelector("input") ||
|
||||||
|
parameterRow.querySelector("select")
|
||||||
|
|
||||||
|
switch (parameterElement?.tagName) {
|
||||||
|
case "INPUT":
|
||||||
|
if (parameterElement.type === "checkbox") {
|
||||||
|
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked
|
||||||
|
} else {
|
||||||
|
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "SELECT":
|
||||||
|
if (parameterElement.multiple) {
|
||||||
|
updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options)
|
||||||
|
.filter((option) => option.selected)
|
||||||
|
.map((option) => option.value || option.text)
|
||||||
|
} else {
|
||||||
|
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.error(
|
||||||
|
`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a <input /> or a <select />!`
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
saveSettingsBtn.classList.add('active')
|
|
||||||
asyncDelay(300).then(() => saveSettingsBtn.classList.remove('active'))
|
const savePromise = changeAppConfig(updateAppConfigRequest)
|
||||||
|
showToast("Settings saved")
|
||||||
|
saveSettingsBtn.classList.add("active")
|
||||||
|
Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove("active"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
listenToNetworkField.addEventListener("change", debounce( ()=>{
|
||||||
|
saveSettingsBtn.click()
|
||||||
|
}, 1000))
|
||||||
|
|
||||||
|
listenPortField.addEventListener("change", debounce( ()=>{
|
||||||
|
saveSettingsBtn.click()
|
||||||
|
}, 1000))
|
||||||
|
|
||||||
|
let copyCloudflareAddressBtn = document.querySelector("#copy-cloudflare-address")
|
||||||
|
let cloudflareAddressField = document.getElementById("cloudflare-address")
|
||||||
|
|
||||||
|
copyCloudflareAddressBtn.addEventListener("click", (e) => {
|
||||||
|
navigator.clipboard.writeText(cloudflareAddressField.innerHTML)
|
||||||
|
showToast("Copied server address to clipboard")
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener("system_info_update", (e) => setDeviceInfo(e.detail))
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,8 +21,7 @@ let hypernetworkModelField = new ModelDropdown(document.querySelector('#hypernet
|
|||||||
|
|
||||||
3) Model dropdowns will be refreshed automatically when the reload models button is invoked.
|
3) Model dropdowns will be refreshed automatically when the reload models button is invoked.
|
||||||
*/
|
*/
|
||||||
class ModelDropdown
|
class ModelDropdown {
|
||||||
{
|
|
||||||
modelFilter //= document.querySelector("#model-filter")
|
modelFilter //= document.querySelector("#model-filter")
|
||||||
modelFilterArrow //= document.querySelector("#model-filter-arrow")
|
modelFilterArrow //= document.querySelector("#model-filter-arrow")
|
||||||
modelList //= document.querySelector("#model-list")
|
modelList //= document.querySelector("#model-list")
|
||||||
@ -39,6 +38,8 @@ class ModelDropdown
|
|||||||
noneEntry //= ''
|
noneEntry //= ''
|
||||||
modelFilterInitialized //= undefined
|
modelFilterInitialized //= undefined
|
||||||
|
|
||||||
|
sorted //= true
|
||||||
|
|
||||||
/* MIMIC A REGULAR INPUT FIELD */
|
/* MIMIC A REGULAR INPUT FIELD */
|
||||||
get parentElement() {
|
get parentElement() {
|
||||||
return this.modelFilter.parentElement
|
return this.modelFilter.parentElement
|
||||||
@ -59,11 +60,11 @@ class ModelDropdown
|
|||||||
set disabled(state) {
|
set disabled(state) {
|
||||||
this.modelFilter.disabled = state
|
this.modelFilter.disabled = state
|
||||||
if (this.modelFilterArrow) {
|
if (this.modelFilterArrow) {
|
||||||
this.modelFilterArrow.style.color = state ? 'dimgray' : ''
|
this.modelFilterArrow.style.color = state ? "dimgray" : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get modelElements() {
|
get modelElements() {
|
||||||
return this.modelList.querySelectorAll('.model-file')
|
return this.modelList.querySelectorAll(".model-file")
|
||||||
}
|
}
|
||||||
addEventListener(type, listener, options) {
|
addEventListener(type, listener, options) {
|
||||||
return this.modelFilter.addEventListener(type, listener, options)
|
return this.modelFilter.addEventListener(type, listener, options)
|
||||||
@ -83,20 +84,38 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* SEARCHABLE INPUT */
|
/* SEARCHABLE INPUT */
|
||||||
constructor (input, modelKey, noneEntry = '') {
|
|
||||||
|
constructor(input, modelKey, noneEntry = "", sorted = true) {
|
||||||
this.modelFilter = input
|
this.modelFilter = input
|
||||||
this.noneEntry = noneEntry
|
this.noneEntry = noneEntry
|
||||||
this.modelKey = modelKey
|
this.modelKey = modelKey
|
||||||
|
this.sorted = sorted
|
||||||
|
|
||||||
if (modelsOptions !== undefined) { // reuse models from cache (only useful for plugins, which are loaded after models)
|
if (modelsOptions !== undefined) {
|
||||||
this.inputModels = modelsOptions[this.modelKey]
|
// reuse models from cache (only useful for plugins, which are loaded after models)
|
||||||
|
this.inputModels = []
|
||||||
|
let modelKeys = Array.isArray(this.modelKey) ? this.modelKey : [this.modelKey]
|
||||||
|
for (let i = 0; i < modelKeys.length; i++) {
|
||||||
|
let key = modelKeys[i]
|
||||||
|
let k = Array.isArray(modelsOptions[key]) ? modelsOptions[key] : [modelsOptions[key]]
|
||||||
|
this.inputModels.push(...k)
|
||||||
|
}
|
||||||
this.populateModels()
|
this.populateModels()
|
||||||
}
|
}
|
||||||
document.addEventListener("refreshModels", this.bind(function(e) {
|
document.addEventListener(
|
||||||
|
"refreshModels",
|
||||||
|
this.bind(function(e) {
|
||||||
// reload the models
|
// reload the models
|
||||||
this.inputModels = modelsOptions[this.modelKey]
|
this.inputModels = []
|
||||||
|
let modelKeys = Array.isArray(this.modelKey) ? this.modelKey : [this.modelKey]
|
||||||
|
for (let i = 0; i < modelKeys.length; i++) {
|
||||||
|
let key = modelKeys[i]
|
||||||
|
let k = Array.isArray(modelsOptions[key]) ? modelsOptions[key] : [modelsOptions[key]]
|
||||||
|
this.inputModels.push(...k)
|
||||||
|
}
|
||||||
this.populateModels()
|
this.populateModels()
|
||||||
}, this))
|
}, this)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCurrentSelection(elem, value, path) {
|
saveCurrentSelection(elem, value, path) {
|
||||||
@ -105,13 +124,13 @@ class ModelDropdown
|
|||||||
this.currentSelection.path = path
|
this.currentSelection.path = path
|
||||||
this.modelFilter.dataset.path = path
|
this.modelFilter.dataset.path = path
|
||||||
this.modelFilter.value = value
|
this.modelFilter.value = value
|
||||||
this.modelFilter.dispatchEvent(new Event('change'))
|
this.modelFilter.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
processClick(e) {
|
processClick(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (e.srcElement.classList.contains('model-file') || e.srcElement.classList.contains('fa-file')) {
|
if (e.srcElement.classList.contains("model-file") || e.srcElement.classList.contains("fa-file")) {
|
||||||
const elem = e.srcElement.classList.contains('model-file') ? e.srcElement : e.srcElement.parentElement
|
const elem = e.srcElement.classList.contains("model-file") ? e.srcElement : e.srcElement.parentElement
|
||||||
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
||||||
this.hideModelList()
|
this.hideModelList()
|
||||||
this.modelFilter.focus()
|
this.modelFilter.focus()
|
||||||
@ -126,35 +145,38 @@ class ModelDropdown
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return modelElements.slice(0, index).reverse().find(e => e.style.display === 'list-item')
|
return modelElements
|
||||||
|
.slice(0, index)
|
||||||
|
.reverse()
|
||||||
|
.find((e) => e.style.display === "list-item")
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastVisibleChild(elem) {
|
getLastVisibleChild(elem) {
|
||||||
let lastElementChild = elem.lastElementChild
|
let lastElementChild = elem.lastElementChild
|
||||||
if (lastElementChild.style.display == 'list-item') return lastElementChild
|
if (lastElementChild.style.display == "list-item") return lastElementChild
|
||||||
return this.getPreviousVisibleSibling(lastElementChild)
|
return this.getPreviousVisibleSibling(lastElementChild)
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextVisibleSibling(elem) {
|
getNextVisibleSibling(elem) {
|
||||||
const modelElements = Array.from(this.modelElements)
|
const modelElements = Array.from(this.modelElements)
|
||||||
const index = modelElements.indexOf(elem)
|
const index = modelElements.indexOf(elem)
|
||||||
return modelElements.slice(index + 1).find(e => e.style.display === 'list-item')
|
return modelElements.slice(index + 1).find((e) => e.style.display === "list-item")
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstVisibleChild(elem) {
|
getFirstVisibleChild(elem) {
|
||||||
let firstElementChild = elem.firstElementChild
|
let firstElementChild = elem.firstElementChild
|
||||||
if (firstElementChild.style.display == 'list-item') return firstElementChild
|
if (firstElementChild.style.display == "list-item") return firstElementChild
|
||||||
return this.getNextVisibleSibling(firstElementChild)
|
return this.getNextVisibleSibling(firstElementChild)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectModelEntry(elem) {
|
selectModelEntry(elem) {
|
||||||
if (elem) {
|
if (elem) {
|
||||||
if (this.highlightedModelEntry !== undefined) {
|
if (this.highlightedModelEntry !== undefined) {
|
||||||
this.highlightedModelEntry.classList.remove('selected')
|
this.highlightedModelEntry.classList.remove("selected")
|
||||||
}
|
}
|
||||||
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
||||||
elem.classList.add('selected')
|
elem.classList.add("selected")
|
||||||
elem.scrollIntoView({block: 'nearest'})
|
elem.scrollIntoView({ block: "nearest" })
|
||||||
this.highlightedModelEntry = elem
|
this.highlightedModelEntry = elem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,11 +185,9 @@ class ModelDropdown
|
|||||||
const elem = this.getPreviousVisibleSibling(this.highlightedModelEntry)
|
const elem = this.getPreviousVisibleSibling(this.highlightedModelEntry)
|
||||||
if (elem) {
|
if (elem) {
|
||||||
this.selectModelEntry(elem)
|
this.selectModelEntry(elem)
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
//this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'})
|
//this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'})
|
||||||
this.highlightedModelEntry.closest('.model-list').scrollTop = 0
|
this.highlightedModelEntry.closest(".model-list").scrollTop = 0
|
||||||
}
|
}
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
@ -178,14 +198,14 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectFirstFile() {
|
selectFirstFile() {
|
||||||
this.selectModelEntry(this.modelList.querySelector('.model-file'))
|
this.selectModelEntry(this.modelList.querySelector(".model-file"))
|
||||||
this.highlightedModelEntry.scrollIntoView({block: 'nearest'})
|
this.highlightedModelEntry.scrollIntoView({ block: "nearest" })
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
selectLastFile() {
|
selectLastFile() {
|
||||||
const elems = this.modelList.querySelectorAll('.model-file:last-child')
|
const elems = this.modelList.querySelectorAll(".model-file:last-child")
|
||||||
this.selectModelEntry(elems[elems.length -1])
|
this.selectModelEntry(elems[elems.length - 1])
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,57 +218,57 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
validEntrySelected() {
|
validEntrySelected() {
|
||||||
return (this.modelNoResult.style.display === 'none')
|
return this.modelNoResult.style.display === "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
processKey(e) {
|
processKey(e) {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Escape':
|
case "Escape":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.resetSelection()
|
this.resetSelection()
|
||||||
break
|
break
|
||||||
case 'Enter':
|
case "Enter":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
this.showModelList()
|
this.showModelList()
|
||||||
}
|
} else {
|
||||||
else
|
this.saveCurrentSelection(
|
||||||
{
|
this.highlightedModelEntry,
|
||||||
this.saveCurrentSelection(this.highlightedModelEntry, this.highlightedModelEntry.innerText, this.highlightedModelEntry.dataset.path)
|
this.highlightedModelEntry.innerText,
|
||||||
|
this.highlightedModelEntry.dataset.path
|
||||||
|
)
|
||||||
this.hideModelList()
|
this.hideModelList()
|
||||||
this.showAllEntries()
|
this.showAllEntries()
|
||||||
}
|
}
|
||||||
this.modelFilter.focus()
|
this.modelFilter.focus()
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
this.resetSelection()
|
this.resetSelection()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowUp':
|
case "ArrowUp":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectPreviousFile()
|
this.selectPreviousFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowDown':
|
case "ArrowDown":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectNextFile()
|
this.selectNextFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowLeft':
|
case "ArrowLeft":
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowRight':
|
case "ArrowRight":
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'PageUp':
|
case "PageUp":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectPreviousFile()
|
this.selectPreviousFile()
|
||||||
@ -261,7 +281,7 @@ class ModelDropdown
|
|||||||
this.selectPreviousFile()
|
this.selectPreviousFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'PageDown':
|
case "PageDown":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectNextFile()
|
this.selectNextFile()
|
||||||
@ -274,7 +294,7 @@ class ModelDropdown
|
|||||||
this.selectNextFile()
|
this.selectNextFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Home':
|
case "Home":
|
||||||
//if (this.modelList.style.display != 'block') {
|
//if (this.modelList.style.display != 'block') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
@ -282,7 +302,7 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
//}
|
//}
|
||||||
break
|
break
|
||||||
case 'End':
|
case "End":
|
||||||
//if (this.modelList.style.display != 'block') {
|
//if (this.modelList.style.display != 'block') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
@ -301,29 +321,27 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
showModelList() {
|
showModelList() {
|
||||||
this.modelList.style.display = 'block'
|
this.modelList.style.display = "block"
|
||||||
this.selectEntry()
|
this.selectEntry()
|
||||||
this.showAllEntries()
|
this.showAllEntries()
|
||||||
//this.modelFilter.value = ''
|
//this.modelFilter.value = ''
|
||||||
this.modelFilter.select() // preselect the entire string so user can just start typing.
|
this.modelFilter.select() // preselect the entire string so user can just start typing.
|
||||||
this.modelFilter.focus()
|
this.modelFilter.focus()
|
||||||
this.modelFilter.style.cursor = 'auto'
|
this.modelFilter.style.cursor = "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
hideModelList() {
|
hideModelList() {
|
||||||
this.modelList.style.display = 'none'
|
this.modelList.style.display = "none"
|
||||||
this.modelFilter.value = this.currentSelection.value
|
this.modelFilter.value = this.currentSelection.value
|
||||||
this.modelFilter.style.cursor = ''
|
this.modelFilter.style.cursor = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModelList(e) {
|
toggleModelList(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!this.modelFilter.disabled) {
|
if (!this.modelFilter.disabled) {
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
this.showModelList()
|
this.showModelList()
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
this.hideModelList()
|
this.hideModelList()
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
@ -332,13 +350,13 @@ class ModelDropdown
|
|||||||
|
|
||||||
selectEntry(path) {
|
selectEntry(path) {
|
||||||
if (path !== undefined) {
|
if (path !== undefined) {
|
||||||
const entries = this.modelElements;
|
const entries = this.modelElements
|
||||||
|
|
||||||
for (const elem of entries) {
|
for (const elem of entries) {
|
||||||
if (elem.dataset.path == path) {
|
if (elem.dataset.path == path) {
|
||||||
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
||||||
this.highlightedModelEntry = elem
|
this.highlightedModelEntry = elem
|
||||||
elem.scrollIntoView({block: 'nearest'})
|
elem.scrollIntoView({ block: "nearest" })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,14 +365,12 @@ class ModelDropdown
|
|||||||
if (this.currentSelection.elem !== undefined) {
|
if (this.currentSelection.elem !== undefined) {
|
||||||
// select the previous element
|
// select the previous element
|
||||||
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != this.currentSelection.elem) {
|
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != this.currentSelection.elem) {
|
||||||
this.highlightedModelEntry.classList.remove('selected')
|
this.highlightedModelEntry.classList.remove("selected")
|
||||||
}
|
}
|
||||||
this.currentSelection.elem.classList.add('selected')
|
this.currentSelection.elem.classList.add("selected")
|
||||||
this.highlightedModelEntry = this.currentSelection.elem
|
this.highlightedModelEntry = this.currentSelection.elem
|
||||||
this.currentSelection.elem.scrollIntoView({block: 'nearest'})
|
this.currentSelection.elem.scrollIntoView({ block: "nearest" })
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
this.selectFirstFile()
|
this.selectFirstFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,28 +378,28 @@ class ModelDropdown
|
|||||||
highlightModelAtPosition(e) {
|
highlightModelAtPosition(e) {
|
||||||
let elem = document.elementFromPoint(e.clientX, e.clientY)
|
let elem = document.elementFromPoint(e.clientX, e.clientY)
|
||||||
|
|
||||||
if (elem.classList.contains('model-file')) {
|
if (elem.classList.contains("model-file")) {
|
||||||
this.highlightModel(elem)
|
this.highlightModel(elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightModel(elem) {
|
highlightModel(elem) {
|
||||||
if (elem.classList.contains('model-file')) {
|
if (elem.classList.contains("model-file")) {
|
||||||
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != elem) {
|
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != elem) {
|
||||||
this.highlightedModelEntry.classList.remove('selected')
|
this.highlightedModelEntry.classList.remove("selected")
|
||||||
}
|
}
|
||||||
elem.classList.add('selected')
|
elem.classList.add("selected")
|
||||||
this.highlightedModelEntry = elem
|
this.highlightedModelEntry = elem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showAllEntries() {
|
showAllEntries() {
|
||||||
this.modelList.querySelectorAll('li').forEach(function(li) {
|
this.modelList.querySelectorAll("li").forEach(function(li) {
|
||||||
if (li.id !== 'model-no-result') {
|
if (li.id !== "model-no-result") {
|
||||||
li.style.display = 'list-item'
|
li.style.display = "list-item"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.modelNoResult.style.display = 'none'
|
this.modelNoResult.style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
filterList(e) {
|
filterList(e) {
|
||||||
@ -391,37 +407,35 @@ class ModelDropdown
|
|||||||
let found = false
|
let found = false
|
||||||
let showAllChildren = false
|
let showAllChildren = false
|
||||||
|
|
||||||
this.modelList.querySelectorAll('li').forEach(function(li) {
|
this.modelList.querySelectorAll("li").forEach(function(li) {
|
||||||
if (li.classList.contains('model-folder')) {
|
if (li.classList.contains("model-folder")) {
|
||||||
showAllChildren = false
|
showAllChildren = false
|
||||||
}
|
}
|
||||||
if (filter == '') {
|
if (filter == "") {
|
||||||
li.style.display = 'list-item'
|
li.style.display = "list-item"
|
||||||
found = true
|
found = true
|
||||||
} else if (showAllChildren || li.textContent.toLowerCase().match(filter)) {
|
} else if (showAllChildren || li.textContent.toLowerCase().match(filter)) {
|
||||||
li.style.display = 'list-item'
|
li.style.display = "list-item"
|
||||||
if (li.classList.contains('model-folder') && li.firstChild.textContent.toLowerCase().match(filter)) {
|
if (li.classList.contains("model-folder") && li.firstChild.textContent.toLowerCase().match(filter)) {
|
||||||
showAllChildren = true
|
showAllChildren = true
|
||||||
}
|
}
|
||||||
found = true
|
found = true
|
||||||
} else {
|
} else {
|
||||||
li.style.display = 'none'
|
li.style.display = "none"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
this.modelResult.style.display = 'list-item'
|
this.modelResult.style.display = "list-item"
|
||||||
this.modelNoResult.style.display = 'none'
|
this.modelNoResult.style.display = "none"
|
||||||
const elem = this.getNextVisibleSibling(this.modelList.querySelector('.model-file'))
|
const elem = this.getNextVisibleSibling(this.modelList.querySelector(".model-file"))
|
||||||
this.highlightModel(elem)
|
this.highlightModel(elem)
|
||||||
elem.scrollIntoView({block: 'nearest'})
|
elem.scrollIntoView({ block: "nearest" })
|
||||||
|
} else {
|
||||||
|
this.modelResult.style.display = "none"
|
||||||
|
this.modelNoResult.style.display = "list-item"
|
||||||
}
|
}
|
||||||
else
|
this.modelList.style.display = "block"
|
||||||
{
|
|
||||||
this.modelResult.style.display = 'none'
|
|
||||||
this.modelNoResult.style.display = 'list-item'
|
|
||||||
}
|
|
||||||
this.modelList.style.display = 'block'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MODEL LOADER */
|
/* MODEL LOADER */
|
||||||
@ -458,17 +472,17 @@ class ModelDropdown
|
|||||||
* @param {Array<string>} models
|
* @param {Array<string>} models
|
||||||
*/
|
*/
|
||||||
sortStringArray(models) {
|
sortStringArray(models) {
|
||||||
models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
|
models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
|
||||||
}
|
}
|
||||||
|
|
||||||
populateModels() {
|
populateModels() {
|
||||||
this.activeModel = this.modelFilter.dataset.path
|
this.activeModel = this.modelFilter.dataset.path
|
||||||
|
|
||||||
this.currentSelection = { elem: undefined, value: '', path: ''}
|
this.currentSelection = { elem: undefined, value: "", path: "" }
|
||||||
this.highlightedModelEntry = undefined
|
this.highlightedModelEntry = undefined
|
||||||
this.flatModelList = []
|
this.flatModelList = []
|
||||||
|
|
||||||
if(this.modelList !== undefined) {
|
if (this.modelList !== undefined) {
|
||||||
this.modelList.remove()
|
this.modelList.remove()
|
||||||
this.modelFilterArrow.remove()
|
this.modelFilterArrow.remove()
|
||||||
}
|
}
|
||||||
@ -478,39 +492,39 @@ class ModelDropdown
|
|||||||
createDropdown() {
|
createDropdown() {
|
||||||
// create dropdown entries
|
// create dropdown entries
|
||||||
let rootModelList = this.createRootModelList(this.inputModels)
|
let rootModelList = this.createRootModelList(this.inputModels)
|
||||||
this.modelFilter.insertAdjacentElement('afterend', rootModelList)
|
this.modelFilter.insertAdjacentElement("afterend", rootModelList)
|
||||||
this.modelFilter.insertAdjacentElement(
|
this.modelFilter.insertAdjacentElement(
|
||||||
'afterend',
|
"afterend",
|
||||||
createElement(
|
createElement("i", { id: `${this.modelFilter.id}-model-filter-arrow` }, [
|
||||||
'i',
|
"model-selector-arrow",
|
||||||
{ id: `${this.modelFilter.id}-model-filter-arrow` },
|
"fa-solid",
|
||||||
['model-selector-arrow', 'fa-solid', 'fa-angle-down'],
|
"fa-angle-down",
|
||||||
),
|
])
|
||||||
)
|
)
|
||||||
this.modelFilter.classList.add('model-selector')
|
this.modelFilter.classList.add("model-selector")
|
||||||
this.modelFilterArrow = document.querySelector(`#${this.modelFilter.id}-model-filter-arrow`)
|
this.modelFilterArrow = document.querySelector(`#${this.modelFilter.id}-model-filter-arrow`)
|
||||||
if (this.modelFilterArrow) {
|
if (this.modelFilterArrow) {
|
||||||
this.modelFilterArrow.style.color = this.modelFilter.disabled ? 'dimgray' : ''
|
this.modelFilterArrow.style.color = this.modelFilter.disabled ? "dimgray" : ""
|
||||||
}
|
}
|
||||||
this.modelList = document.querySelector(`#${this.modelFilter.id}-model-list`)
|
this.modelList = document.querySelector(`#${this.modelFilter.id}-model-list`)
|
||||||
this.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`)
|
this.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`)
|
||||||
this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`)
|
this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`)
|
||||||
|
|
||||||
if (this.modelFilterInitialized !== true) {
|
if (this.modelFilterInitialized !== true) {
|
||||||
this.modelFilter.addEventListener('input', this.bind(this.filterList, this))
|
this.modelFilter.addEventListener("input", this.bind(this.filterList, this))
|
||||||
this.modelFilter.addEventListener('focus', this.bind(this.modelListFocus, this))
|
this.modelFilter.addEventListener("focus", this.bind(this.modelListFocus, this))
|
||||||
this.modelFilter.addEventListener('blur', this.bind(this.hideModelList, this))
|
this.modelFilter.addEventListener("blur", this.bind(this.hideModelList, this))
|
||||||
this.modelFilter.addEventListener('click', this.bind(this.showModelList, this))
|
this.modelFilter.addEventListener("click", this.bind(this.showModelList, this))
|
||||||
this.modelFilter.addEventListener('keydown', this.bind(this.processKey, this))
|
this.modelFilter.addEventListener("keydown", this.bind(this.processKey, this))
|
||||||
|
|
||||||
this.modelFilterInitialized = true
|
this.modelFilterInitialized = true
|
||||||
}
|
}
|
||||||
this.modelFilterArrow.addEventListener('mousedown', this.bind(this.toggleModelList, this))
|
this.modelFilterArrow.addEventListener("mousedown", this.bind(this.toggleModelList, this))
|
||||||
this.modelList.addEventListener('mousemove', this.bind(this.highlightModelAtPosition, this))
|
this.modelList.addEventListener("mousemove", this.bind(this.highlightModelAtPosition, this))
|
||||||
this.modelList.addEventListener('mousedown', this.bind(this.processClick, this))
|
this.modelList.addEventListener("mousedown", this.bind(this.processClick, this))
|
||||||
|
|
||||||
let mf = this.modelFilter
|
let mf = this.modelFilter
|
||||||
this.modelFilter.addEventListener('focus', function() {
|
this.modelFilter.addEventListener("focus", function() {
|
||||||
let modelFilterStyle = window.getComputedStyle(mf)
|
let modelFilterStyle = window.getComputedStyle(mf)
|
||||||
rootModelList.style.minWidth = modelFilterStyle.width
|
rootModelList.style.minWidth = modelFilterStyle.width
|
||||||
})
|
})
|
||||||
@ -525,69 +539,61 @@ class ModelDropdown
|
|||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
createModelNodeList(folderName, modelTree, isRootFolder) {
|
createModelNodeList(folderName, modelTree, isRootFolder) {
|
||||||
const listElement = createElement('ul')
|
const listElement = createElement("ul")
|
||||||
|
|
||||||
const foldersMap = new Map()
|
const foldersMap = new Map()
|
||||||
const modelsMap = new Map()
|
const modelsMap = new Map()
|
||||||
|
|
||||||
modelTree.forEach(model => {
|
modelTree.forEach((model) => {
|
||||||
if (Array.isArray(model)) {
|
if (Array.isArray(model)) {
|
||||||
const [childFolderName, childModels] = model
|
const [childFolderName, childModels] = model
|
||||||
foldersMap.set(
|
foldersMap.set(
|
||||||
childFolderName,
|
childFolderName,
|
||||||
this.createModelNodeList(
|
this.createModelNodeList(`${folderName || ""}/${childFolderName}`, childModels, false)
|
||||||
`${folderName || ''}/${childFolderName}`,
|
|
||||||
childModels,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const classes = ['model-file']
|
const classes = ["model-file"]
|
||||||
if (isRootFolder) {
|
if (isRootFolder) {
|
||||||
classes.push('in-root-folder')
|
classes.push("in-root-folder")
|
||||||
}
|
}
|
||||||
// Remove the leading slash from the model path
|
// Remove the leading slash from the model path
|
||||||
const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model
|
const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model
|
||||||
modelsMap.set(
|
modelsMap.set(
|
||||||
model,
|
model,
|
||||||
createElement(
|
createElement("li", { "data-path": fullPath }, classes, [
|
||||||
'li',
|
createElement("i", undefined, ["fa-regular", "fa-file", "icon"]),
|
||||||
{ 'data-path': fullPath },
|
|
||||||
classes,
|
|
||||||
[
|
|
||||||
createElement('i', undefined, ['fa-regular', 'fa-file', 'icon']),
|
|
||||||
model,
|
model,
|
||||||
],
|
])
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const childFolderNames = Array.from(foldersMap.keys())
|
const childFolderNames = Array.from(foldersMap.keys())
|
||||||
|
if (this.sorted) {
|
||||||
this.sortStringArray(childFolderNames)
|
this.sortStringArray(childFolderNames)
|
||||||
const folderElements = childFolderNames.map(name => foldersMap.get(name))
|
}
|
||||||
|
const folderElements = childFolderNames.map((name) => foldersMap.get(name))
|
||||||
|
|
||||||
const modelNames = Array.from(modelsMap.keys())
|
const modelNames = Array.from(modelsMap.keys())
|
||||||
|
if (this.sorted) {
|
||||||
this.sortStringArray(modelNames)
|
this.sortStringArray(modelNames)
|
||||||
const modelElements = modelNames.map(name => modelsMap.get(name))
|
}
|
||||||
|
const modelElements = modelNames.map((name) => modelsMap.get(name))
|
||||||
|
|
||||||
if (modelElements.length && folderName) {
|
if (modelElements.length && folderName) {
|
||||||
listElement.appendChild(
|
listElement.appendChild(
|
||||||
createElement(
|
createElement(
|
||||||
'li',
|
"li",
|
||||||
undefined,
|
undefined,
|
||||||
['model-folder'],
|
["model-folder"],
|
||||||
[
|
[createElement("i", undefined, ["fa-regular", "fa-folder-open", "icon"]), folderName.substring(1)]
|
||||||
createElement('i', undefined, ['fa-regular', 'fa-folder-open', 'icon']),
|
|
||||||
folderName.substring(1),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// const allModelElements = isRootFolder ? [...folderElements, ...modelElements] : [...modelElements, ...folderElements]
|
// const allModelElements = isRootFolder ? [...folderElements, ...modelElements] : [...modelElements, ...folderElements]
|
||||||
const allModelElements = [...modelElements, ...folderElements]
|
const allModelElements = [...modelElements, ...folderElements]
|
||||||
allModelElements.forEach(e => listElement.appendChild(e))
|
allModelElements.forEach((e) => listElement.appendChild(e))
|
||||||
return listElement
|
return listElement
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,37 +602,21 @@ class ModelDropdown
|
|||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
createRootModelList(modelTree) {
|
createRootModelList(modelTree) {
|
||||||
const rootList = createElement(
|
const rootList = createElement("ul", { id: `${this.modelFilter.id}-model-list` }, ["model-list"])
|
||||||
'ul',
|
|
||||||
{ id: `${this.modelFilter.id}-model-list` },
|
|
||||||
['model-list'],
|
|
||||||
)
|
|
||||||
rootList.appendChild(
|
rootList.appendChild(
|
||||||
createElement(
|
createElement("li", { id: `${this.modelFilter.id}-model-no-result` }, ["model-no-result"], "No result")
|
||||||
'li',
|
|
||||||
{ id: `${this.modelFilter.id}-model-no-result` },
|
|
||||||
['model-no-result'],
|
|
||||||
'No result'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (this.noneEntry) {
|
if (this.noneEntry) {
|
||||||
rootList.appendChild(
|
rootList.appendChild(
|
||||||
createElement(
|
createElement("li", { "data-path": "" }, ["model-file", "in-root-folder"], this.noneEntry)
|
||||||
'li',
|
|
||||||
{ 'data-path': '' },
|
|
||||||
['model-file', 'in-root-folder'],
|
|
||||||
this.noneEntry,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelTree.length > 0) {
|
if (modelTree.length > 0) {
|
||||||
const containerListItem = createElement(
|
const containerListItem = createElement("li", { id: `${this.modelFilter.id}-model-result` }, [
|
||||||
'li',
|
"model-result",
|
||||||
{ id: `${this.modelFilter.id}-model-result` },
|
])
|
||||||
['model-result'],
|
|
||||||
)
|
|
||||||
//console.log(containerListItem)
|
//console.log(containerListItem)
|
||||||
containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true))
|
containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true))
|
||||||
rootList.appendChild(containerListItem)
|
rootList.appendChild(containerListItem)
|
||||||
@ -640,13 +630,16 @@ class ModelDropdown
|
|||||||
async function getModels() {
|
async function getModels() {
|
||||||
try {
|
try {
|
||||||
modelsCache = await SD.getModels()
|
modelsCache = await SD.getModels()
|
||||||
modelsOptions = modelsCache['options']
|
modelsOptions = modelsCache["options"]
|
||||||
if ("scan-error" in modelsCache) {
|
if ("scan-error" in modelsCache) {
|
||||||
// let previewPane = document.getElementById('tab-content-wrapper')
|
// let previewPane = document.getElementById('tab-content-wrapper')
|
||||||
let previewPane = document.getElementById('preview')
|
let previewPane = document.getElementById("preview")
|
||||||
previewPane.style.background="red"
|
previewPane.style.background = "red"
|
||||||
previewPane.style.textAlign="center"
|
previewPane.style.textAlign = "center"
|
||||||
previewPane.innerHTML = '<H1>🔥Malware alert!🔥</H1><h2>The file <i>' + modelsCache['scan-error'] + '</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>'
|
previewPane.innerHTML =
|
||||||
|
"<H1>🔥Malware alert!🔥</H1><h2>The file <i>" +
|
||||||
|
modelsCache["scan-error"] +
|
||||||
|
'</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>'
|
||||||
makeImageBtn.disabled = true
|
makeImageBtn.disabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,11 +660,11 @@ async function getModels() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// notify ModelDropdown objects to refresh
|
// notify ModelDropdown objects to refresh
|
||||||
document.dispatchEvent(new Event('refreshModels'))
|
document.dispatchEvent(new Event("refreshModels"))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('get models error', e)
|
console.log("get models error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload models button
|
// reload models button
|
||||||
document.querySelector('#reload-models').addEventListener('click', getModels)
|
document.querySelector("#reload-models").addEventListener("click", getModels)
|
||||||
|
@ -1,82 +1,85 @@
|
|||||||
const themeField = document.getElementById("theme");
|
const themeField = document.getElementById("theme")
|
||||||
var DEFAULT_THEME = {};
|
var DEFAULT_THEME = {}
|
||||||
var THEMES = []; // initialized in initTheme from data in css
|
var THEMES = [] // initialized in initTheme from data in css
|
||||||
|
|
||||||
function getThemeName(theme) {
|
function getThemeName(theme) {
|
||||||
theme = theme.replace("theme-", "");
|
theme = theme.replace("theme-", "")
|
||||||
theme = theme.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
theme = theme
|
||||||
return theme;
|
.split("-")
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(" ")
|
||||||
|
return theme
|
||||||
}
|
}
|
||||||
// init themefield
|
// init themefield
|
||||||
function initTheme() {
|
function initTheme() {
|
||||||
Array.from(document.styleSheets)
|
Array.from(document.styleSheets)
|
||||||
.filter(sheet => sheet.href?.startsWith(window.location.origin))
|
.filter((sheet) => sheet.href?.startsWith(window.location.origin))
|
||||||
.flatMap(sheet => Array.from(sheet.cssRules))
|
.flatMap((sheet) => Array.from(sheet.cssRules))
|
||||||
.forEach(rule => {
|
.forEach((rule) => {
|
||||||
var selector = rule.selectorText;
|
var selector = rule.selectorText
|
||||||
if (selector && selector.startsWith(".theme-") && !selector.includes(" ")) {
|
if (selector && selector.startsWith(".theme-") && !selector.includes(" ")) {
|
||||||
if (DEFAULT_THEME) { // re-add props that dont change (css needs this so they update correctly)
|
if (DEFAULT_THEME) {
|
||||||
|
// re-add props that dont change (css needs this so they update correctly)
|
||||||
Array.from(DEFAULT_THEME.rule.style)
|
Array.from(DEFAULT_THEME.rule.style)
|
||||||
.filter(cssVariable => !Array.from(rule.style).includes(cssVariable))
|
.filter((cssVariable) => !Array.from(rule.style).includes(cssVariable))
|
||||||
.forEach(cssVariable => {
|
.forEach((cssVariable) => {
|
||||||
rule.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable));
|
rule.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
var theme_key = selector.substring(1);
|
var theme_key = selector.substring(1)
|
||||||
THEMES.push({
|
THEMES.push({
|
||||||
key: theme_key,
|
key: theme_key,
|
||||||
name: getThemeName(theme_key),
|
name: getThemeName(theme_key),
|
||||||
rule: rule
|
rule: rule,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (selector && selector == ":root") {
|
if (selector && selector == ":root") {
|
||||||
DEFAULT_THEME = {
|
DEFAULT_THEME = {
|
||||||
key: "theme-default",
|
key: "theme-default",
|
||||||
name: "Default",
|
name: "Default",
|
||||||
rule: rule
|
rule: rule,
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
})
|
||||||
THEMES.forEach(theme => {
|
|
||||||
var new_option = document.createElement("option");
|
|
||||||
new_option.setAttribute("value", theme.key);
|
|
||||||
new_option.innerText = theme.name;
|
|
||||||
themeField.appendChild(new_option);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
THEMES.forEach((theme) => {
|
||||||
|
var new_option = document.createElement("option")
|
||||||
|
new_option.setAttribute("value", theme.key)
|
||||||
|
new_option.innerText = theme.name
|
||||||
|
themeField.appendChild(new_option)
|
||||||
|
})
|
||||||
|
|
||||||
// setup the style transitions a second after app initializes, so initial style is instant
|
// setup the style transitions a second after app initializes, so initial style is instant
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
var body = document.querySelector("body");
|
var body = document.querySelector("body")
|
||||||
var style = document.createElement('style');
|
var style = document.createElement("style")
|
||||||
style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }";
|
style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }"
|
||||||
body.appendChild(style);
|
body.appendChild(style)
|
||||||
}, 1000);
|
}, 1000)
|
||||||
}
|
}
|
||||||
initTheme();
|
initTheme()
|
||||||
|
|
||||||
function themeFieldChanged() {
|
function themeFieldChanged() {
|
||||||
var theme_key = themeField.value;
|
var theme_key = themeField.value
|
||||||
|
|
||||||
var body = document.querySelector("body");
|
var body = document.querySelector("body")
|
||||||
body.classList.remove(...THEMES.map(theme => theme.key));
|
body.classList.remove(...THEMES.map((theme) => theme.key))
|
||||||
body.classList.add(theme_key);
|
body.classList.add(theme_key)
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
body.style = "";
|
body.style = ""
|
||||||
var theme = THEMES.find(t => t.key == theme_key);
|
var theme = THEMES.find((t) => t.key == theme_key)
|
||||||
let borderColor = undefined
|
let borderColor = undefined
|
||||||
if (theme) {
|
if (theme) {
|
||||||
borderColor = theme.rule.style.getPropertyValue('--input-border-color').trim()
|
borderColor = theme.rule.style.getPropertyValue("--input-border-color").trim()
|
||||||
if (!borderColor.startsWith('#')) {
|
if (!borderColor.startsWith("#")) {
|
||||||
borderColor = theme.rule.style.getPropertyValue('--theme-color-fallback')
|
borderColor = theme.rule.style.getPropertyValue("--theme-color-fallback")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
borderColor = DEFAULT_THEME.rule.style.getPropertyValue('--theme-color-fallback')
|
borderColor = DEFAULT_THEME.rule.style.getPropertyValue("--theme-color-fallback")
|
||||||
}
|
}
|
||||||
document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor)
|
document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
themeField.addEventListener('change', themeFieldChanged);
|
themeField.addEventListener("change", themeFieldChanged)
|
||||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
@ -2428,6 +2428,19 @@
|
|||||||
"path": "artist/by_yoshitaka_amano/landscape-0.jpg"
|
"path": "artist/by_yoshitaka_amano/landscape-0.jpg"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modifier": "by Zdzislaw Beksinski",
|
||||||
|
"previews": [
|
||||||
|
{
|
||||||
|
"name": "portrait",
|
||||||
|
"path": "artist/by_zdzislaw_beksinski/portrait-0.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "landscape",
|
||||||
|
"path": "artist/by_zdzislaw_beksinski/landscape-0.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
(function () {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
let autoScroll = document.querySelector("#auto_scroll")
|
let autoScroll = document.querySelector("#auto_scroll")
|
||||||
|
|
||||||
// observe for changes in the preview pane
|
// observe for changes in the preview pane
|
||||||
var observer = new MutationObserver(function (mutations) {
|
var observer = new MutationObserver(function(mutations) {
|
||||||
mutations.forEach(function (mutation) {
|
mutations.forEach(function(mutation) {
|
||||||
if (mutation.target.className == 'img-batch') {
|
if (mutation.target.className == "img-batch") {
|
||||||
Autoscroll(mutation.target)
|
Autoscroll(mutation.target)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(document.getElementById('preview'), {
|
observer.observe(document.getElementById("preview"), {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function Autoscroll(target) {
|
function Autoscroll(target) {
|
||||||
if (autoScroll.checked && target !== null) {
|
if (autoScroll.checked && target !== null) {
|
||||||
const img = target.querySelector('img')
|
const img = target.querySelector("img")
|
||||||
img.addEventListener('load', function() {
|
img.addEventListener(
|
||||||
img.closest('.imageTaskContainer').scrollIntoView()
|
"load",
|
||||||
}, { once: true })
|
function() {
|
||||||
|
img?.closest(".imageTaskContainer").scrollIntoView()
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
(function () { "use strict"
|
;(function() {
|
||||||
if (typeof editorModifierTagsList !== 'object') {
|
"use strict"
|
||||||
console.error('editorModifierTagsList missing...')
|
if (typeof editorModifierTagsList !== "object") {
|
||||||
|
console.error("editorModifierTagsList missing...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleSheet = document.createElement("style");
|
const styleSheet = document.createElement("style")
|
||||||
styleSheet.textContent = `
|
styleSheet.textContent = `
|
||||||
.modifier-card-tiny.drag-sort-active {
|
.modifier-card-tiny.drag-sort-active {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 2px dashed white;
|
border: 2px dashed white;
|
||||||
opacity:0.2;
|
opacity:0.2;
|
||||||
}
|
}
|
||||||
`;
|
`
|
||||||
document.head.appendChild(styleSheet);
|
document.head.appendChild(styleSheet)
|
||||||
|
|
||||||
// observe for changes in tag list
|
// observe for changes in tag list
|
||||||
const observer = new MutationObserver(function (mutations) {
|
const observer = new MutationObserver(function(mutations) {
|
||||||
// mutations.forEach(function (mutation) {
|
// mutations.forEach(function (mutation) {
|
||||||
if (editorModifierTagsList.childNodes.length > 0) {
|
if (editorModifierTagsList.childNodes.length > 0) {
|
||||||
ModifierDragAndDrop(editorModifierTagsList)
|
ModifierDragAndDrop(editorModifierTagsList)
|
||||||
@ -24,70 +25,92 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(editorModifierTagsList, {
|
observer.observe(editorModifierTagsList, {
|
||||||
childList: true
|
childList: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
let current
|
let current
|
||||||
function ModifierDragAndDrop(target) {
|
function ModifierDragAndDrop(target) {
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
i.parentElement.draggable = true;
|
i.parentElement.draggable = true
|
||||||
|
|
||||||
i.parentElement.ondragstart = (e) => {
|
i.parentElement.ondragstart = (e) => {
|
||||||
current = i
|
current = i
|
||||||
i.parentElement.getElementsByClassName('modifier-card-image-overlay')[0].innerText = ''
|
i.parentElement.getElementsByClassName("modifier-card-image-overlay")[0].innerText = ""
|
||||||
i.parentElement.draggable = true
|
i.parentElement.draggable = true
|
||||||
i.parentElement.classList.add('drag-sort-active')
|
i.parentElement.classList.add("drag-sort-active")
|
||||||
for(let item of document.querySelector('#editor-inputs-tags-list').getElementsByClassName('modifier-card-image-overlay')) {
|
for (let item of document
|
||||||
if (item.parentElement.parentElement.getElementsByClassName('modifier-card-overlay')[0] != current) {
|
.querySelector("#editor-inputs-tags-list")
|
||||||
item.parentElement.parentElement.getElementsByClassName('modifier-card-image-overlay')[0].style.opacity = 0
|
.getElementsByClassName("modifier-card-image-overlay")) {
|
||||||
if(item.parentElement.getElementsByClassName('modifier-card-image').length > 0) {
|
if (
|
||||||
item.parentElement.getElementsByClassName('modifier-card-image')[0].style.filter = 'none'
|
item.parentElement.parentElement.getElementsByClassName("modifier-card-overlay")[0] != current
|
||||||
|
) {
|
||||||
|
item.parentElement.parentElement.getElementsByClassName(
|
||||||
|
"modifier-card-image-overlay"
|
||||||
|
)[0].style.opacity = 0
|
||||||
|
if (item.parentElement.getElementsByClassName("modifier-card-image").length > 0) {
|
||||||
|
item.parentElement.getElementsByClassName("modifier-card-image")[0].style.filter = "none"
|
||||||
}
|
}
|
||||||
item.parentElement.parentElement.style.transform = 'none'
|
item.parentElement.parentElement.style.transform = "none"
|
||||||
item.parentElement.parentElement.style.boxShadow = 'none'
|
item.parentElement.parentElement.style.boxShadow = "none"
|
||||||
}
|
}
|
||||||
item.innerText = ''
|
item.innerText = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i.ondragenter = (e) => {
|
i.ondragenter = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (i != current) {
|
if (i != current) {
|
||||||
let currentPos = 0, droppedPos = 0;
|
let currentPos = 0,
|
||||||
|
droppedPos = 0
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
if (current == overlays[it]) { currentPos = it; }
|
if (current == overlays[it]) {
|
||||||
if (i == overlays[it]) { droppedPos = it; }
|
currentPos = it
|
||||||
|
}
|
||||||
|
if (i == overlays[it]) {
|
||||||
|
droppedPos = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.parentElement != current.parentElement) {
|
if (i.parentElement != current.parentElement) {
|
||||||
let currentPos = 0, droppedPos = 0
|
let currentPos = 0,
|
||||||
|
droppedPos = 0
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
if (current == overlays[it]) { currentPos = it }
|
if (current == overlays[it]) {
|
||||||
if (i == overlays[it]) { droppedPos = it }
|
currentPos = it
|
||||||
|
}
|
||||||
|
if (i == overlays[it]) {
|
||||||
|
droppedPos = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (currentPos < droppedPos) {
|
if (currentPos < droppedPos) {
|
||||||
current = i.parentElement.parentNode.insertBefore(current.parentElement, i.parentElement.nextSibling).getElementsByClassName('modifier-card-overlay')[0]
|
current = i.parentElement.parentNode
|
||||||
|
.insertBefore(current.parentElement, i.parentElement.nextSibling)
|
||||||
|
.getElementsByClassName("modifier-card-overlay")[0]
|
||||||
} else {
|
} else {
|
||||||
current = i.parentElement.parentNode.insertBefore(current.parentElement, i.parentElement).getElementsByClassName('modifier-card-overlay')[0]
|
current = i.parentElement.parentNode
|
||||||
|
.insertBefore(current.parentElement, i.parentElement)
|
||||||
|
.getElementsByClassName("modifier-card-overlay")[0]
|
||||||
}
|
}
|
||||||
// update activeTags
|
// update activeTags
|
||||||
const tag = activeTags.splice(currentPos, 1)
|
const tag = activeTags.splice(currentPos, 1)
|
||||||
activeTags.splice(droppedPos, 0, tag[0])
|
activeTags.splice(droppedPos, 0, tag[0])
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
i.ondragover = (e) => {
|
i.ondragover = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
i.parentElement.ondragend = (e) => {
|
i.parentElement.ondragend = (e) => {
|
||||||
i.parentElement.classList.remove('drag-sort-active')
|
i.parentElement.classList.remove("drag-sort-active")
|
||||||
for(let item of document.querySelector('#editor-inputs-tags-list').getElementsByClassName('modifier-card-image-overlay')) {
|
for (let item of document
|
||||||
item.style.opacity = ''
|
.querySelector("#editor-inputs-tags-list")
|
||||||
item.innerText = '-'
|
.getElementsByClassName("modifier-card-image-overlay")) {
|
||||||
|
item.style.opacity = ""
|
||||||
|
item.innerText = "-"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
(function () {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
const MAX_WEIGHT = 5
|
const MAX_WEIGHT = 5
|
||||||
|
|
||||||
if (typeof editorModifierTagsList !== 'object') {
|
if (typeof editorModifierTagsList !== "object") {
|
||||||
console.error('editorModifierTagsList missing...')
|
console.error("editorModifierTagsList missing...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// observe for changes in tag list
|
// observe for changes in tag list
|
||||||
const observer = new MutationObserver(function (mutations) {
|
const observer = new MutationObserver(function(mutations) {
|
||||||
// mutations.forEach(function (mutation) {
|
// mutations.forEach(function (mutation) {
|
||||||
if (editorModifierTagsList.childNodes.length > 0) {
|
if (editorModifierTagsList.childNodes.length > 0) {
|
||||||
ModifierMouseWheel(editorModifierTagsList)
|
ModifierMouseWheel(editorModifierTagsList)
|
||||||
@ -18,18 +18,20 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(editorModifierTagsList, {
|
observer.observe(editorModifierTagsList, {
|
||||||
childList: true
|
childList: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function ModifierMouseWheel(target) {
|
function ModifierMouseWheel(target) {
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
i.onwheel = (e) => {
|
i.onwheel = (e) => {
|
||||||
if (e.ctrlKey == true) {
|
if (e.ctrlKey == true) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const delta = Math.sign(event.deltaY)
|
const delta = Math.sign(event.deltaY)
|
||||||
let s = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText
|
let s = i.parentElement
|
||||||
|
.getElementsByClassName("modifier-card-label")[0]
|
||||||
|
.getElementsByTagName("p")[0].innerText
|
||||||
let t
|
let t
|
||||||
// find the corresponding tag
|
// find the corresponding tag
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
@ -38,43 +40,40 @@
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (s.charAt(0) !== '(' && s.charAt(s.length - 1) !== ')' && s.trim().includes(' ')) {
|
if (s.charAt(0) !== "(" && s.charAt(s.length - 1) !== ")" && s.trim().includes(" ")) {
|
||||||
s = '(' + s + ')'
|
s = "(" + s + ")"
|
||||||
t = '(' + t + ')'
|
t = "(" + t + ")"
|
||||||
}
|
}
|
||||||
if (delta < 0) {
|
if (delta < 0) {
|
||||||
// wheel scrolling up
|
// wheel scrolling up
|
||||||
if (s.substring(s.length - 1) == '-') {
|
if (s.substring(s.length - 1) == "-") {
|
||||||
s = s.substring(0, s.length - 1)
|
s = s.substring(0, s.length - 1)
|
||||||
t = t.substring(0, t.length - 1)
|
t = t.substring(0, t.length - 1)
|
||||||
}
|
} else {
|
||||||
else
|
if (s.substring(s.length - MAX_WEIGHT) !== "+".repeat(MAX_WEIGHT)) {
|
||||||
{
|
s = s + "+"
|
||||||
if (s.substring(s.length - MAX_WEIGHT) !== '+'.repeat(MAX_WEIGHT)) {
|
t = t + "+"
|
||||||
s = s + '+'
|
|
||||||
t = t + '+'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
// wheel scrolling down
|
// wheel scrolling down
|
||||||
if (s.substring(s.length - 1) == '+') {
|
if (s.substring(s.length - 1) == "+") {
|
||||||
s = s.substring(0, s.length - 1)
|
s = s.substring(0, s.length - 1)
|
||||||
t = t.substring(0, t.length - 1)
|
t = t.substring(0, t.length - 1)
|
||||||
}
|
} else {
|
||||||
else
|
if (s.substring(s.length - MAX_WEIGHT) !== "-".repeat(MAX_WEIGHT)) {
|
||||||
{
|
s = s + "-"
|
||||||
if (s.substring(s.length - MAX_WEIGHT) !== '-'.repeat(MAX_WEIGHT)) {
|
t = t + "-"
|
||||||
s = s + '-'
|
|
||||||
t = t + '-'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (s.charAt(0) === '(' && s.charAt(s.length - 1) === ')') {
|
if (s.charAt(0) === "(" && s.charAt(s.length - 1) === ")") {
|
||||||
s = s.substring(1, s.length - 1)
|
s = s.substring(1, s.length - 1)
|
||||||
t = t.substring(1, t.length - 1)
|
t = t.substring(1, t.length - 1)
|
||||||
}
|
}
|
||||||
i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText = s
|
i.parentElement
|
||||||
|
.getElementsByClassName("modifier-card-label")[0]
|
||||||
|
.getElementsByTagName("p")[0].innerText = s
|
||||||
// update activeTags
|
// update activeTags
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
if (i == overlays[it]) {
|
if (i == overlays[it]) {
|
||||||
@ -82,7 +81,7 @@
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
(function() {
|
;(function() {
|
||||||
PLUGINS['MODIFIERS_LOAD'].push({
|
PLUGINS["MODIFIERS_LOAD"].push({
|
||||||
loader: function() {
|
loader: function() {
|
||||||
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, '')
|
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, "")
|
||||||
customModifiersTextBox.value = customModifiers
|
customModifiersTextBox.value = customModifiers
|
||||||
|
|
||||||
if (customModifiersGroupElement !== undefined) {
|
if (customModifiersGroupElement !== undefined) {
|
||||||
customModifiersGroupElement.remove()
|
customModifiersGroupElement.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customModifiers && customModifiers.trim() !== '') {
|
if (customModifiers && customModifiers.trim() !== "") {
|
||||||
customModifiers = customModifiers.split('\n')
|
customModifiers = customModifiers.split("\n")
|
||||||
customModifiers = customModifiers.filter(m => m.trim() !== '')
|
customModifiers = customModifiers.filter((m) => m.trim() !== "")
|
||||||
customModifiers = customModifiers.map(function(m) {
|
customModifiers = customModifiers.map(function(m) {
|
||||||
return {
|
return {
|
||||||
"modifier": m
|
modifier: m,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let customGroup = {
|
let customGroup = {
|
||||||
'category': 'Custom Modifiers',
|
category: "Custom Modifiers",
|
||||||
'modifiers': customModifiers
|
modifiers: customModifiers,
|
||||||
}
|
}
|
||||||
|
|
||||||
customModifiersGroupElement = createModifierGroup(customGroup, true)
|
customModifiersGroupElement = createModifierGroup(customGroup, true)
|
||||||
|
|
||||||
createCollapsibles(customModifiersGroupElement)
|
createCollapsibles(customModifiersGroupElement)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
|
@ -26,8 +26,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project
|
after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project
|
||||||
source files or spec files are loaded.
|
source files or spec files are loaded.
|
||||||
*/
|
*/
|
||||||
(function() {
|
;(function() {
|
||||||
const jasmineRequire = window.jasmineRequire || require('./jasmine.js');
|
const jasmineRequire = window.jasmineRequire || require("./jasmine.js")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## Require & Instantiate
|
* ## Require & Instantiate
|
||||||
@ -35,30 +35,30 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
|
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
|
||||||
*/
|
*/
|
||||||
const jasmine = jasmineRequire.core(jasmineRequire),
|
const jasmine = jasmineRequire.core(jasmineRequire),
|
||||||
global = jasmine.getGlobal();
|
global = jasmine.getGlobal()
|
||||||
global.jasmine = jasmine;
|
global.jasmine = jasmine
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
|
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
|
||||||
*/
|
*/
|
||||||
jasmineRequire.html(jasmine);
|
jasmineRequire.html(jasmine)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the Jasmine environment. This is used to run all specs in a project.
|
* Create the Jasmine environment. This is used to run all specs in a project.
|
||||||
*/
|
*/
|
||||||
const env = jasmine.getEnv();
|
const env = jasmine.getEnv()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## The Global Interface
|
* ## The Global Interface
|
||||||
*
|
*
|
||||||
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
|
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
|
||||||
*/
|
*/
|
||||||
const jasmineInterface = jasmineRequire.interface(jasmine, env);
|
const jasmineInterface = jasmineRequire.interface(jasmine, env)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
|
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
|
||||||
*/
|
*/
|
||||||
for (const property in jasmineInterface) {
|
for (const property in jasmineInterface) {
|
||||||
global[property] = jasmineInterface[property];
|
global[property] = jasmineInterface[property]
|
||||||
}
|
}
|
||||||
})();
|
})()
|
||||||
|
@ -33,8 +33,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
after `boot0.js` is loaded and before this file is loaded.
|
after `boot0.js` is loaded and before this file is loaded.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
;(function() {
|
||||||
const env = jasmine.getEnv();
|
const env = jasmine.getEnv()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## Runner Parameters
|
* ## Runner Parameters
|
||||||
@ -44,29 +44,27 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
const queryString = new jasmine.QueryString({
|
const queryString = new jasmine.QueryString({
|
||||||
getWindowLocation: function() {
|
getWindowLocation: function() {
|
||||||
return window.location;
|
return window.location
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const filterSpecs = !!queryString.getParam('spec');
|
const filterSpecs = !!queryString.getParam("spec")
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'),
|
stopOnSpecFailure: queryString.getParam("stopOnSpecFailure"),
|
||||||
stopSpecOnExpectationFailure: queryString.getParam(
|
stopSpecOnExpectationFailure: queryString.getParam("stopSpecOnExpectationFailure"),
|
||||||
'stopSpecOnExpectationFailure'
|
hideDisabled: queryString.getParam("hideDisabled"),
|
||||||
),
|
|
||||||
hideDisabled: queryString.getParam('hideDisabled')
|
|
||||||
};
|
|
||||||
|
|
||||||
const random = queryString.getParam('random');
|
|
||||||
|
|
||||||
if (random !== undefined && random !== '') {
|
|
||||||
config.random = random;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const seed = queryString.getParam('seed');
|
const random = queryString.getParam("random")
|
||||||
|
|
||||||
|
if (random !== undefined && random !== "") {
|
||||||
|
config.random = random
|
||||||
|
}
|
||||||
|
|
||||||
|
const seed = queryString.getParam("seed")
|
||||||
if (seed) {
|
if (seed) {
|
||||||
config.seed = seed;
|
config.seed = seed
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,57 +74,57 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
const htmlReporter = new jasmine.HtmlReporter({
|
const htmlReporter = new jasmine.HtmlReporter({
|
||||||
env: env,
|
env: env,
|
||||||
navigateWithNewParam: function(key, value) {
|
navigateWithNewParam: function(key, value) {
|
||||||
return queryString.navigateWithNewParam(key, value);
|
return queryString.navigateWithNewParam(key, value)
|
||||||
},
|
},
|
||||||
addToExistingQueryString: function(key, value) {
|
addToExistingQueryString: function(key, value) {
|
||||||
return queryString.fullStringWithNewParam(key, value);
|
return queryString.fullStringWithNewParam(key, value)
|
||||||
},
|
},
|
||||||
getContainer: function() {
|
getContainer: function() {
|
||||||
return document.body;
|
return document.body
|
||||||
},
|
},
|
||||||
createElement: function() {
|
createElement: function() {
|
||||||
return document.createElement.apply(document, arguments);
|
return document.createElement.apply(document, arguments)
|
||||||
},
|
},
|
||||||
createTextNode: function() {
|
createTextNode: function() {
|
||||||
return document.createTextNode.apply(document, arguments);
|
return document.createTextNode.apply(document, arguments)
|
||||||
},
|
},
|
||||||
timer: new jasmine.Timer(),
|
timer: new jasmine.Timer(),
|
||||||
filterSpecs: filterSpecs
|
filterSpecs: filterSpecs,
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
|
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
|
||||||
*/
|
*/
|
||||||
env.addReporter(jsApiReporter);
|
env.addReporter(jsApiReporter)
|
||||||
env.addReporter(htmlReporter);
|
env.addReporter(htmlReporter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
|
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
|
||||||
*/
|
*/
|
||||||
const specFilter = new jasmine.HtmlSpecFilter({
|
const specFilter = new jasmine.HtmlSpecFilter({
|
||||||
filterString: function() {
|
filterString: function() {
|
||||||
return queryString.getParam('spec');
|
return queryString.getParam("spec")
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
config.specFilter = function(spec) {
|
config.specFilter = function(spec) {
|
||||||
return specFilter.matches(spec.getFullName());
|
return specFilter.matches(spec.getFullName())
|
||||||
};
|
}
|
||||||
|
|
||||||
env.configure(config);
|
env.configure(config)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## Execution
|
* ## Execution
|
||||||
*
|
*
|
||||||
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
|
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
|
||||||
*/
|
*/
|
||||||
const currentWindowOnload = window.onload;
|
const currentWindowOnload = window.onload
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
if (currentWindowOnload) {
|
if (currentWindowOnload) {
|
||||||
currentWindowOnload();
|
currentWindowOnload()
|
||||||
}
|
}
|
||||||
htmlReporter.initialize();
|
htmlReporter.initialize()
|
||||||
env.execute();
|
env.execute()
|
||||||
};
|
}
|
||||||
})();
|
})()
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,34 +2,34 @@
|
|||||||
|
|
||||||
const JASMINE_SESSION_ID = `jasmine-${String(Date.now()).slice(8)}`
|
const JASMINE_SESSION_ID = `jasmine-${String(Date.now()).slice(8)}`
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function() {
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes
|
||||||
jasmine.addMatchers({
|
jasmine.addMatchers({
|
||||||
toBeOneOf: function () {
|
toBeOneOf: function() {
|
||||||
return {
|
return {
|
||||||
compare: function (actual, expected) {
|
compare: function(actual, expected) {
|
||||||
return {
|
return {
|
||||||
pass: expected.includes(actual)
|
pass: expected.includes(actual),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('stable-diffusion-ui', function() {
|
describe("stable-diffusion-ui", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
expect(typeof SD).toBe('object')
|
expect(typeof SD).toBe("object")
|
||||||
expect(typeof SD.serverState).toBe('object')
|
expect(typeof SD.serverState).toBe("object")
|
||||||
expect(typeof SD.serverState.status).toBe('string')
|
expect(typeof SD.serverState.status).toBe("string")
|
||||||
})
|
})
|
||||||
it('should be able to reach the backend', async function() {
|
it("should be able to reach the backend", async function() {
|
||||||
expect(SD.serverState.status).toBe(SD.ServerStates.unavailable)
|
expect(SD.serverState.status).toBe(SD.ServerStates.unavailable)
|
||||||
SD.sessionId = JASMINE_SESSION_ID
|
SD.sessionId = JASMINE_SESSION_ID
|
||||||
await SD.init()
|
await SD.init()
|
||||||
expect(SD.isServerAvailable()).toBeTrue()
|
expect(SD.isServerAvailable()).toBeTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('enfore the current task state', function() {
|
it("enfore the current task state", function() {
|
||||||
const task = new SD.Task()
|
const task = new SD.Task()
|
||||||
expect(task.status).toBe(SD.TaskStatus.init)
|
expect(task.status).toBe(SD.TaskStatus.init)
|
||||||
expect(task.isPending).toBeTrue()
|
expect(task.isPending).toBeTrue()
|
||||||
@ -65,149 +65,161 @@ describe('stable-diffusion-ui', function() {
|
|||||||
task._setStatus(SD.TaskStatus.completed)
|
task._setStatus(SD.TaskStatus.completed)
|
||||||
}).toThrowError()
|
}).toThrowError()
|
||||||
})
|
})
|
||||||
it('should be able to run tasks', async function() {
|
it("should be able to run tasks", async function() {
|
||||||
expect(typeof SD.Task.run).toBe('function')
|
expect(typeof SD.Task.run).toBe("function")
|
||||||
const promiseGenerator = (function*(val) {
|
const promiseGenerator = (function*(val) {
|
||||||
expect(val).toBe('start')
|
expect(val).toBe("start")
|
||||||
expect(yield 1 + 1).toBe(4)
|
expect(yield 1 + 1).toBe(4)
|
||||||
expect(yield 2 + 2).toBe(8)
|
expect(yield 2 + 2).toBe(8)
|
||||||
yield asyncDelay(500)
|
yield asyncDelay(500)
|
||||||
expect(yield 3 + 3).toBe(12)
|
expect(yield 3 + 3).toBe(12)
|
||||||
expect(yield 4 + 4).toBe(16)
|
expect(yield 4 + 4).toBe(16)
|
||||||
return 8 + 8
|
return 8 + 8
|
||||||
})('start')
|
})("start")
|
||||||
const callback = function({value, done}) {
|
const callback = function({ value, done }) {
|
||||||
return {value: 2 * value, done}
|
return { value: 2 * value, done }
|
||||||
}
|
}
|
||||||
expect(await SD.Task.run(promiseGenerator, {callback})).toBe(32)
|
expect(await SD.Task.run(promiseGenerator, { callback })).toBe(32)
|
||||||
})
|
})
|
||||||
it('should be able to queue tasks', async function() {
|
it("should be able to queue tasks", async function() {
|
||||||
expect(typeof SD.Task.enqueue).toBe('function')
|
expect(typeof SD.Task.enqueue).toBe("function")
|
||||||
const promiseGenerator = (function*(val) {
|
const promiseGenerator = (function*(val) {
|
||||||
expect(val).toBe('start')
|
expect(val).toBe("start")
|
||||||
expect(yield 1 + 1).toBe(4)
|
expect(yield 1 + 1).toBe(4)
|
||||||
expect(yield 2 + 2).toBe(8)
|
expect(yield 2 + 2).toBe(8)
|
||||||
yield asyncDelay(500)
|
yield asyncDelay(500)
|
||||||
expect(yield 3 + 3).toBe(12)
|
expect(yield 3 + 3).toBe(12)
|
||||||
expect(yield 4 + 4).toBe(16)
|
expect(yield 4 + 4).toBe(16)
|
||||||
return 8 + 8
|
return 8 + 8
|
||||||
})('start')
|
})("start")
|
||||||
const callback = function({value, done}) {
|
const callback = function({ value, done }) {
|
||||||
return {value: 2 * value, done}
|
return { value: 2 * value, done }
|
||||||
}
|
}
|
||||||
const gen = SD.Task.asGenerator({generator: promiseGenerator, callback})
|
const gen = SD.Task.asGenerator({ generator: promiseGenerator, callback })
|
||||||
expect(await SD.Task.enqueue(gen)).toBe(32)
|
expect(await SD.Task.enqueue(gen)).toBe(32)
|
||||||
})
|
})
|
||||||
it('should be able to chain handlers', async function() {
|
it("should be able to chain handlers", async function() {
|
||||||
expect(typeof SD.Task.enqueue).toBe('function')
|
expect(typeof SD.Task.enqueue).toBe("function")
|
||||||
const promiseGenerator = (function*(val) {
|
const promiseGenerator = (function*(val) {
|
||||||
expect(val).toBe('start')
|
expect(val).toBe("start")
|
||||||
expect(yield {test: '1'}).toEqual({test: '1', foo: 'bar'})
|
expect(yield { test: "1" }).toEqual({ test: "1", foo: "bar" })
|
||||||
expect(yield 2 + 2).toEqual(8)
|
expect(yield 2 + 2).toEqual(8)
|
||||||
yield asyncDelay(500)
|
yield asyncDelay(500)
|
||||||
expect(yield 3 + 3).toEqual(12)
|
expect(yield 3 + 3).toEqual(12)
|
||||||
expect(yield {test: 4}).toEqual({test: 8, foo: 'bar'})
|
expect(yield { test: 4 }).toEqual({ test: 8, foo: "bar" })
|
||||||
return {test: 8}
|
return { test: 8 }
|
||||||
})('start')
|
})("start")
|
||||||
const gen1 = SD.Task.asGenerator({generator: promiseGenerator, callback: function({value, done}) {
|
const gen1 = SD.Task.asGenerator({
|
||||||
|
generator: promiseGenerator,
|
||||||
|
callback: function({ value, done }) {
|
||||||
if (typeof value === "object") {
|
if (typeof value === "object") {
|
||||||
value['foo'] = 'bar'
|
value["foo"] = "bar"
|
||||||
}
|
}
|
||||||
return {value, done}
|
return { value, done }
|
||||||
}})
|
},
|
||||||
const gen2 = SD.Task.asGenerator({generator: gen1, callback: function({value, done}) {
|
})
|
||||||
if (typeof value === 'number') {
|
const gen2 = SD.Task.asGenerator({
|
||||||
|
generator: gen1,
|
||||||
|
callback: function({ value, done }) {
|
||||||
|
if (typeof value === "number") {
|
||||||
value = 2 * value
|
value = 2 * value
|
||||||
}
|
}
|
||||||
if (typeof value === 'object' && typeof value.test === 'number') {
|
if (typeof value === "object" && typeof value.test === "number") {
|
||||||
value.test = 2 * value.test
|
value.test = 2 * value.test
|
||||||
}
|
}
|
||||||
return {value, done}
|
return { value, done }
|
||||||
}})
|
},
|
||||||
expect(await SD.Task.enqueue(gen2)).toEqual({test:32, foo: 'bar'})
|
|
||||||
})
|
})
|
||||||
describe('ServiceContainer', function() {
|
expect(await SD.Task.enqueue(gen2)).toEqual({ test: 32, foo: "bar" })
|
||||||
it('should be able to register providers', function() {
|
})
|
||||||
|
describe("ServiceContainer", function() {
|
||||||
|
it("should be able to register providers", function() {
|
||||||
const cont = new ServiceContainer(
|
const cont = new ServiceContainer(
|
||||||
function foo() {
|
function foo() {
|
||||||
this.bar = ''
|
this.bar = ""
|
||||||
},
|
},
|
||||||
function bar() {
|
function bar() {
|
||||||
return () => 0
|
return () => 0
|
||||||
},
|
},
|
||||||
{ name: 'zero', definition: 0 },
|
{ name: "zero", definition: 0 },
|
||||||
{ name: 'ctx', definition: () => Object.create(null), singleton: true },
|
{ name: "ctx", definition: () => Object.create(null), singleton: true },
|
||||||
{ name: 'test',
|
{
|
||||||
|
name: "test",
|
||||||
definition: (ctx, missing, one, foo) => {
|
definition: (ctx, missing, one, foo) => {
|
||||||
expect(ctx).toEqual({ran: true})
|
expect(ctx).toEqual({ ran: true })
|
||||||
expect(one).toBe(1)
|
expect(one).toBe(1)
|
||||||
expect(typeof foo).toBe('object')
|
expect(typeof foo).toBe("object")
|
||||||
expect(foo.bar).toBeDefined()
|
expect(foo.bar).toBeDefined()
|
||||||
expect(typeof missing).toBe('undefined')
|
expect(typeof missing).toBe("undefined")
|
||||||
return {foo: 'bar'}
|
return { foo: "bar" }
|
||||||
}, dependencies: ['ctx', 'missing', 'one', 'foo']
|
},
|
||||||
|
dependencies: ["ctx", "missing", "one", "foo"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const fooObj = cont.get('foo')
|
const fooObj = cont.get("foo")
|
||||||
expect(typeof fooObj).toBe('object')
|
expect(typeof fooObj).toBe("object")
|
||||||
fooObj.ran = true
|
fooObj.ran = true
|
||||||
|
|
||||||
const ctx = cont.get('ctx')
|
const ctx = cont.get("ctx")
|
||||||
expect(ctx).toEqual({})
|
expect(ctx).toEqual({})
|
||||||
ctx.ran = true
|
ctx.ran = true
|
||||||
|
|
||||||
const bar = cont.get('bar')
|
const bar = cont.get("bar")
|
||||||
expect(typeof bar).toBe('function')
|
expect(typeof bar).toBe("function")
|
||||||
expect(bar()).toBe(0)
|
expect(bar()).toBe(0)
|
||||||
|
|
||||||
cont.register({name: 'one', definition: 1})
|
cont.register({ name: "one", definition: 1 })
|
||||||
const test = cont.get('test')
|
const test = cont.get("test")
|
||||||
expect(typeof test).toBe('object')
|
expect(typeof test).toBe("object")
|
||||||
expect(test.foo).toBe('bar')
|
expect(test.foo).toBe("bar")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should be able to stream data in chunks', async function() {
|
it("should be able to stream data in chunks", async function() {
|
||||||
expect(SD.isServerAvailable()).toBeTrue()
|
expect(SD.isServerAvailable()).toBeTrue()
|
||||||
const nbr_steps = 15
|
const nbr_steps = 15
|
||||||
let res = await fetch('/render', {
|
let res = await fetch("/render", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"negative_prompt": "",
|
negative_prompt: "",
|
||||||
"width": 128,
|
width: 128,
|
||||||
"height": 128,
|
height: 128,
|
||||||
"seed": Math.floor(Math.random() * 10000000),
|
seed: Math.floor(Math.random() * 10000000),
|
||||||
|
|
||||||
"sampler": "plms",
|
sampler: "plms",
|
||||||
"use_stable_diffusion_model": "sd-v1-4",
|
use_stable_diffusion_model: "sd-v1-4",
|
||||||
"num_inference_steps": nbr_steps,
|
num_inference_steps: nbr_steps,
|
||||||
"guidance_scale": 7.5,
|
guidance_scale: 7.5,
|
||||||
|
|
||||||
"numOutputsParallel": 1,
|
numOutputsParallel: 1,
|
||||||
"stream_image_progress": true,
|
stream_image_progress: true,
|
||||||
"show_only_filtered_image": true,
|
show_only_filtered_image: true,
|
||||||
"output_format": "jpeg",
|
output_format: "jpeg",
|
||||||
|
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
expect(res.ok).toBeTruthy()
|
expect(res.ok).toBeTruthy()
|
||||||
const renderRequest = await res.json()
|
const renderRequest = await res.json()
|
||||||
expect(typeof renderRequest.stream).toBe('string')
|
expect(typeof renderRequest.stream).toBe("string")
|
||||||
expect(renderRequest.task).toBeDefined()
|
expect(renderRequest.task).toBeDefined()
|
||||||
|
|
||||||
// Wait for server status to update.
|
// Wait for server status to update.
|
||||||
await SD.waitUntil(() => {
|
await SD.waitUntil(
|
||||||
console.log('Waiting for %s to be received...', renderRequest.task)
|
() => {
|
||||||
return (!SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)])
|
console.log("Waiting for %s to be received...", renderRequest.task)
|
||||||
}, 250, 10 * 60 * 1000)
|
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)]
|
||||||
|
},
|
||||||
|
250,
|
||||||
|
10 * 60 * 1000
|
||||||
|
)
|
||||||
// Wait for task to start on server.
|
// Wait for task to start on server.
|
||||||
await SD.waitUntil(() => {
|
await SD.waitUntil(() => {
|
||||||
console.log('Waiting for %s to start...', renderRequest.task)
|
console.log("Waiting for %s to start...", renderRequest.task)
|
||||||
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== 'pending'
|
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== "pending"
|
||||||
}, 250)
|
}, 250)
|
||||||
|
|
||||||
const reader = new SD.ChunkedStreamReader(renderRequest.stream)
|
const reader = new SD.ChunkedStreamReader(renderRequest.stream)
|
||||||
@ -217,24 +229,24 @@ describe('stable-diffusion-ui', function() {
|
|||||||
if (!value || value.length <= 0) {
|
if (!value || value.length <= 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return reader.readStreamAsJSON(value.join(''))
|
return reader.readStreamAsJSON(value.join(""))
|
||||||
}
|
}
|
||||||
reader.onNext = function({done, value}) {
|
reader.onNext = function({ done, value }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
if (typeof value === 'object' && 'status' in value) {
|
if (typeof value === "object" && "status" in value) {
|
||||||
done = true
|
done = true
|
||||||
}
|
}
|
||||||
return {done, value}
|
return { done, value }
|
||||||
}
|
}
|
||||||
let lastUpdate = undefined
|
let lastUpdate = undefined
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
let complete = false
|
let complete = false
|
||||||
//for await (const stepUpdate of reader) {
|
//for await (const stepUpdate of reader) {
|
||||||
for await (const stepUpdate of reader.open()) {
|
for await (const stepUpdate of reader.open()) {
|
||||||
console.log('ChunkedStreamReader received ', stepUpdate)
|
console.log("ChunkedStreamReader received ", stepUpdate)
|
||||||
lastUpdate = stepUpdate
|
lastUpdate = stepUpdate
|
||||||
if (complete) {
|
if (complete) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(1)
|
expect(stepUpdate.output).toHaveSize(1)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.total_steps).toBe(nbr_steps)
|
expect(stepUpdate.total_steps).toBe(nbr_steps)
|
||||||
@ -246,37 +258,39 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(let i=1; i <= 5; ++i) {
|
for (let i = 1; i <= 5; ++i) {
|
||||||
res = await fetch(renderRequest.stream)
|
res = await fetch(renderRequest.stream)
|
||||||
expect(res.ok).toBeTruthy()
|
expect(res.ok).toBeTruthy()
|
||||||
const cachedResponse = await res.json()
|
const cachedResponse = await res.json()
|
||||||
console.log('Cache test %s received %o', i, cachedResponse)
|
console.log("Cache test %s received %o", i, cachedResponse)
|
||||||
expect(lastUpdate).toEqual(cachedResponse)
|
expect(lastUpdate).toEqual(cachedResponse)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('should be able to make renders', function() {
|
describe("should be able to make renders", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
expect(SD.isServerAvailable()).toBeTrue()
|
expect(SD.isServerAvailable()).toBeTrue()
|
||||||
})
|
})
|
||||||
it('basic inline request', async function() {
|
it("basic inline request", async function() {
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
let complete = false
|
let complete = false
|
||||||
const result = await SD.render({
|
const result = await SD.render(
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
{
|
||||||
"width": 128,
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"height": 128,
|
width: 128,
|
||||||
"num_inference_steps": 10,
|
height: 128,
|
||||||
"show_only_filtered_image": false,
|
num_inference_steps: 10,
|
||||||
|
show_only_filtered_image: false,
|
||||||
//"use_face_correction": 'GFPGANv1.3',
|
//"use_face_correction": 'GFPGANv1.3',
|
||||||
"use_upscale": "RealESRGAN_x4plus",
|
use_upscale: "RealESRGAN_x4plus",
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
}, function(event) {
|
},
|
||||||
|
function(event) {
|
||||||
console.log(this, event)
|
console.log(this, event)
|
||||||
if ('update' in event) {
|
if ("update" in event) {
|
||||||
const stepUpdate = event.update
|
const stepUpdate = event.update
|
||||||
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(2)
|
expect(stepUpdate.output).toHaveSize(2)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.step).toBe(stepCount)
|
expect(stepUpdate.step).toBe(stepCount)
|
||||||
@ -287,29 +301,33 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
console.log(result)
|
console.log(result)
|
||||||
expect(result.status).toBe('succeeded')
|
expect(result.status).toBe("succeeded")
|
||||||
expect(result.output).toHaveSize(2)
|
expect(result.output).toHaveSize(2)
|
||||||
})
|
})
|
||||||
it('post and reader request', async function() {
|
it("post and reader request", async function() {
|
||||||
const renderTask = new SD.RenderTask({
|
const renderTask = new SD.RenderTask({
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"width": 128,
|
width: 128,
|
||||||
"height": 128,
|
height: 128,
|
||||||
"seed": SD.MAX_SEED_VALUE,
|
seed: SD.MAX_SEED_VALUE,
|
||||||
"num_inference_steps": 10,
|
num_inference_steps: 10,
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
})
|
})
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.init)
|
expect(renderTask.status).toBe(SD.TaskStatus.init)
|
||||||
|
|
||||||
const timeout = -1
|
const timeout = -1
|
||||||
const renderRequest = await renderTask.post(timeout)
|
const renderRequest = await renderTask.post(timeout)
|
||||||
expect(typeof renderRequest.stream).toBe('string')
|
expect(typeof renderRequest.stream).toBe("string")
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.waiting)
|
expect(renderTask.status).toBe(SD.TaskStatus.waiting)
|
||||||
expect(renderTask.streamUrl).toBe(renderRequest.stream)
|
expect(renderTask.streamUrl).toBe(renderRequest.stream)
|
||||||
|
|
||||||
await renderTask.waitUntil({state: SD.TaskStatus.processing, callback: () => console.log('Waiting for render task to start...') })
|
await renderTask.waitUntil({
|
||||||
|
state: SD.TaskStatus.processing,
|
||||||
|
callback: () => console.log("Waiting for render task to start..."),
|
||||||
|
})
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.processing)
|
expect(renderTask.status).toBe(SD.TaskStatus.processing)
|
||||||
|
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
@ -318,7 +336,7 @@ describe('stable-diffusion-ui', function() {
|
|||||||
for await (const stepUpdate of renderTask.reader.open()) {
|
for await (const stepUpdate of renderTask.reader.open()) {
|
||||||
console.log(stepUpdate)
|
console.log(stepUpdate)
|
||||||
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(1)
|
expect(stepUpdate.output).toHaveSize(1)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.step).toBe(stepCount)
|
expect(stepUpdate.step).toBe(stepCount)
|
||||||
@ -330,28 +348,28 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.completed)
|
expect(renderTask.status).toBe(SD.TaskStatus.completed)
|
||||||
expect(renderTask.result.status).toBe('succeeded')
|
expect(renderTask.result.status).toBe("succeeded")
|
||||||
expect(renderTask.result.output).toHaveSize(1)
|
expect(renderTask.result.output).toHaveSize(1)
|
||||||
})
|
})
|
||||||
it('queued request', async function() {
|
it("queued request", async function() {
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
let complete = false
|
let complete = false
|
||||||
const renderTask = new SD.RenderTask({
|
const renderTask = new SD.RenderTask({
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"width": 128,
|
width: 128,
|
||||||
"height": 128,
|
height: 128,
|
||||||
"num_inference_steps": 10,
|
num_inference_steps: 10,
|
||||||
"show_only_filtered_image": false,
|
show_only_filtered_image: false,
|
||||||
//"use_face_correction": 'GFPGANv1.3',
|
//"use_face_correction": 'GFPGANv1.3',
|
||||||
"use_upscale": "RealESRGAN_x4plus",
|
use_upscale: "RealESRGAN_x4plus",
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
})
|
})
|
||||||
await renderTask.enqueue(function(event) {
|
await renderTask.enqueue(function(event) {
|
||||||
console.log(this, event)
|
console.log(this, event)
|
||||||
if ('update' in event) {
|
if ("update" in event) {
|
||||||
const stepUpdate = event.update
|
const stepUpdate = event.update
|
||||||
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(2)
|
expect(stepUpdate.output).toHaveSize(2)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.step).toBe(stepCount)
|
expect(stepUpdate.step).toBe(stepCount)
|
||||||
@ -364,12 +382,12 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(renderTask.result)
|
console.log(renderTask.result)
|
||||||
expect(renderTask.result.status).toBe('succeeded')
|
expect(renderTask.result.status).toBe("succeeded")
|
||||||
expect(renderTask.result.output).toHaveSize(2)
|
expect(renderTask.result.output).toHaveSize(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('# Special cases', function() {
|
describe("# Special cases", function() {
|
||||||
it('should throw an exception on set for invalid sessionId', function() {
|
it("should throw an exception on set for invalid sessionId", function() {
|
||||||
expect(function() {
|
expect(function() {
|
||||||
SD.sessionId = undefined
|
SD.sessionId = undefined
|
||||||
}).toThrowError("Can't set sessionId to undefined.")
|
}).toThrowError("Can't set sessionId to undefined.")
|
||||||
@ -386,16 +404,17 @@ if (!PLUGINS.SELFTEST) {
|
|||||||
PLUGINS.SELFTEST = {}
|
PLUGINS.SELFTEST = {}
|
||||||
}
|
}
|
||||||
loadUIPlugins().then(function() {
|
loadUIPlugins().then(function() {
|
||||||
console.log('loadCompleted', loadEvent)
|
console.log("loadCompleted", loadEvent)
|
||||||
describe('@Plugins', function() {
|
describe("@Plugins", function() {
|
||||||
it('exposes hooks to overide', function() {
|
it("exposes hooks to overide", function() {
|
||||||
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe('object')
|
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe("object")
|
||||||
expect(typeof PLUGINS.TASK_CREATE).toBe('object')
|
expect(typeof PLUGINS.TASK_CREATE).toBe("object")
|
||||||
})
|
})
|
||||||
describe('supports selftests', function() { // Hook to allow plugins to define tests.
|
describe("supports selftests", function() {
|
||||||
|
// Hook to allow plugins to define tests.
|
||||||
const pluginsTests = Object.keys(PLUGINS.SELFTEST).filter((key) => PLUGINS.SELFTEST.hasOwnProperty(key))
|
const pluginsTests = Object.keys(PLUGINS.SELFTEST).filter((key) => PLUGINS.SELFTEST.hasOwnProperty(key))
|
||||||
if (!pluginsTests || pluginsTests.length <= 0) {
|
if (!pluginsTests || pluginsTests.length <= 0) {
|
||||||
it('but nothing loaded...', function() {
|
it("but nothing loaded...", function() {
|
||||||
expect(true).toBeTruthy()
|
expect(true).toBeTruthy()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
(function() {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
///////////////////// Function section
|
///////////////////// Function section
|
||||||
@ -18,146 +18,133 @@
|
|||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
function getCurrentTime() {
|
function getCurrentTime() {
|
||||||
const now = new Date();
|
const now = new Date()
|
||||||
let hours = now.getHours();
|
let hours = now.getHours()
|
||||||
let minutes = now.getMinutes();
|
let minutes = now.getMinutes()
|
||||||
let seconds = now.getSeconds();
|
let seconds = now.getSeconds()
|
||||||
|
|
||||||
hours = hours < 10 ? `0${hours}` : hours;
|
hours = hours < 10 ? `0${hours}` : hours
|
||||||
minutes = minutes < 10 ? `0${minutes}` : minutes;
|
minutes = minutes < 10 ? `0${minutes}` : minutes
|
||||||
seconds = seconds < 10 ? `0${seconds}` : seconds;
|
seconds = seconds < 10 ? `0${seconds}` : seconds
|
||||||
|
|
||||||
return `${hours}:${minutes}:${seconds}`;
|
return `${hours}:${minutes}:${seconds}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLogMessage(message) {
|
function addLogMessage(message) {
|
||||||
const logContainer = document.getElementById('merge-log');
|
const logContainer = document.getElementById("merge-log")
|
||||||
logContainer.innerHTML += `<i>${getCurrentTime()}</i> ${message}<br>`;
|
logContainer.innerHTML += `<i>${getCurrentTime()}</i> ${message}<br>`
|
||||||
|
|
||||||
// Scroll to the bottom of the log
|
// Scroll to the bottom of the log
|
||||||
logContainer.scrollTop = logContainer.scrollHeight;
|
logContainer.scrollTop = logContainer.scrollHeight
|
||||||
|
|
||||||
document.querySelector('#merge-log-container').style.display = 'block'
|
document.querySelector("#merge-log-container").style.display = "block"
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLogSeparator() {
|
function addLogSeparator() {
|
||||||
const logContainer = document.getElementById('merge-log');
|
const logContainer = document.getElementById("merge-log")
|
||||||
logContainer.innerHTML += '<hr>'
|
logContainer.innerHTML += "<hr>"
|
||||||
|
|
||||||
logContainer.scrollTop = logContainer.scrollHeight;
|
logContainer.scrollTop = logContainer.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawDiagram(fn) {
|
function drawDiagram(fn) {
|
||||||
const SIZE = 300
|
const SIZE = 300
|
||||||
const canvas = document.getElementById('merge-canvas');
|
const canvas = document.getElementById("merge-canvas")
|
||||||
canvas.height = canvas.width = SIZE
|
canvas.height = canvas.width = SIZE
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d")
|
||||||
|
|
||||||
// Draw coordinate system
|
// Draw coordinate system
|
||||||
ctx.scale(1, -1);
|
ctx.scale(1, -1)
|
||||||
ctx.translate(0, -canvas.height);
|
ctx.translate(0, -canvas.height)
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1
|
||||||
ctx.beginPath();
|
ctx.beginPath()
|
||||||
|
|
||||||
ctx.strokeStyle = 'white'
|
ctx.strokeStyle = "white"
|
||||||
ctx.moveTo(0,0); ctx.lineTo(0,SIZE); ctx.lineTo(SIZE,SIZE); ctx.lineTo(SIZE,0); ctx.lineTo(0,0); ctx.lineTo(SIZE,SIZE);
|
ctx.moveTo(0, 0)
|
||||||
|
ctx.lineTo(0, SIZE)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
|
ctx.lineTo(SIZE, 0)
|
||||||
|
ctx.lineTo(0, 0)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.setLineDash([1,2])
|
ctx.setLineDash([1, 2])
|
||||||
const n = SIZE / 10
|
const n = SIZE / 10
|
||||||
for (let i=n; i<SIZE; i+=n) {
|
for (let i = n; i < SIZE; i += n) {
|
||||||
ctx.moveTo(0,i)
|
ctx.moveTo(0, i)
|
||||||
ctx.lineTo(SIZE,i)
|
ctx.lineTo(SIZE, i)
|
||||||
ctx.moveTo(i,0)
|
ctx.moveTo(i, 0)
|
||||||
ctx.lineTo(i,SIZE)
|
ctx.lineTo(i, SIZE)
|
||||||
}
|
}
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.setLineDash([])
|
ctx.setLineDash([])
|
||||||
ctx.beginPath();
|
ctx.beginPath()
|
||||||
ctx.strokeStyle = 'black'
|
ctx.strokeStyle = "black"
|
||||||
ctx.lineWidth = 3;
|
ctx.lineWidth = 3
|
||||||
// Plot function
|
// Plot function
|
||||||
const numSamples = 20;
|
const numSamples = 20
|
||||||
for (let i = 0; i <= numSamples; i++) {
|
for (let i = 0; i <= numSamples; i++) {
|
||||||
const x = i / numSamples;
|
const x = i / numSamples
|
||||||
const y = fn(x);
|
const y = fn(x)
|
||||||
|
|
||||||
const canvasX = x * SIZE;
|
const canvasX = x * SIZE
|
||||||
const canvasY = y * SIZE;
|
const canvasY = y * SIZE
|
||||||
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
ctx.moveTo(canvasX, canvasY);
|
ctx.moveTo(canvasX, canvasY)
|
||||||
} else {
|
} else {
|
||||||
ctx.lineTo(canvasX, canvasY);
|
ctx.lineTo(canvasX, canvasY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
// Plot alpha values (yellow boxes)
|
// Plot alpha values (yellow boxes)
|
||||||
let start = parseFloat( document.querySelector('#merge-start').value )
|
let start = parseFloat(document.querySelector("#merge-start").value)
|
||||||
let step = parseFloat( document.querySelector('#merge-step').value )
|
let step = parseFloat(document.querySelector("#merge-step").value)
|
||||||
let iterations = document.querySelector('#merge-count').value>>0
|
let iterations = document.querySelector("#merge-count").value >> 0
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.fillStyle = "yellow"
|
ctx.fillStyle = "yellow"
|
||||||
for (let i=0; i< iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
const alpha = ( start + i * step ) / 100
|
const alpha = (start + i * step) / 100
|
||||||
const x = alpha*SIZE
|
const x = alpha * SIZE
|
||||||
const y = fn(alpha) * SIZE
|
const y = fn(alpha) * SIZE
|
||||||
if (x <= SIZE) {
|
if (x <= SIZE) {
|
||||||
ctx.rect(x-3,y-3,6,6)
|
ctx.rect(x - 3, y - 3, 6, 6)
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
} else {
|
} else {
|
||||||
ctx.strokeStyle = 'red'
|
ctx.strokeStyle = "red"
|
||||||
ctx.moveTo(0,0); ctx.lineTo(0,SIZE); ctx.lineTo(SIZE,SIZE); ctx.lineTo(SIZE,0); ctx.lineTo(0,0); ctx.lineTo(SIZE,SIZE);
|
ctx.moveTo(0, 0)
|
||||||
|
ctx.lineTo(0, SIZE)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
|
ctx.lineTo(SIZE, 0)
|
||||||
|
ctx.lineTo(0, 0)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
addLogMessage('<i>Warning: maximum ratio is ≥ 100%</i>')
|
addLogMessage("<i>Warning: maximum ratio is ≥ 100%</i>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateChart() {
|
function updateChart() {
|
||||||
let fn = (x) => x
|
let fn = (x) => x
|
||||||
switch (document.querySelector('#merge-interpolation').value) {
|
switch (document.querySelector("#merge-interpolation").value) {
|
||||||
case 'SmoothStep':
|
case "SmoothStep":
|
||||||
fn = smoothstep
|
fn = smoothstep
|
||||||
break
|
break
|
||||||
case 'SmootherStep':
|
case "SmootherStep":
|
||||||
fn = smootherstep
|
fn = smootherstep
|
||||||
break
|
break
|
||||||
case 'SmoothestStep':
|
case "SmoothestStep":
|
||||||
fn = smootheststep
|
fn = smootheststep
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
drawDiagram(fn)
|
drawDiagram(fn)
|
||||||
}
|
}
|
||||||
|
createTab({
|
||||||
/////////////////////// Tab implementation
|
id: "merge",
|
||||||
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', `
|
icon: "fa-code-merge",
|
||||||
<span id="tab-merge" class="tab">
|
label: "Merge models",
|
||||||
<span><i class="fa fa-code-merge icon"></i> Merge models</span>
|
css: `
|
||||||
</span>
|
|
||||||
`)
|
|
||||||
|
|
||||||
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
|
|
||||||
<div id="tab-content-merge" class="tab-content">
|
|
||||||
<div id="merge" class="tab-content-inner">
|
|
||||||
Loading..
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
|
|
||||||
const tabMerge = document.querySelector('#tab-merge')
|
|
||||||
if (tabMerge) {
|
|
||||||
linkTabContents(tabMerge)
|
|
||||||
}
|
|
||||||
const merge = document.querySelector('#merge')
|
|
||||||
if (!merge) {
|
|
||||||
// merge tab not found, dont exec plugin code.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('body').insertAdjacentHTML('beforeend', `
|
|
||||||
<style>
|
|
||||||
#tab-content-merge .tab-content-inner {
|
#tab-content-merge .tab-content-inner {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 10pt;
|
padding: 10pt;
|
||||||
@ -233,11 +220,8 @@
|
|||||||
}
|
}
|
||||||
.merge-container #merge-warning {
|
.merge-container #merge-warning {
|
||||||
color: rgb(153, 153, 153);
|
color: rgb(153, 153, 153);
|
||||||
}
|
}`,
|
||||||
</style>
|
content: `
|
||||||
`)
|
|
||||||
|
|
||||||
merge.innerHTML = `
|
|
||||||
<div class="merge-container panel-box">
|
<div class="merge-container panel-box">
|
||||||
<div class="merge-input">
|
<div class="merge-input">
|
||||||
<p><label for="#mergeModelA">Select Model A:</label></p>
|
<p><label for="#mergeModelA">Select Model A:</label></p>
|
||||||
@ -327,21 +311,25 @@
|
|||||||
<div class="merge-buttons">
|
<div class="merge-buttons">
|
||||||
<button id="merge-button" class="primaryButton">Merge models</button>
|
<button id="merge-button" class="primaryButton">Merge models</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`,
|
||||||
|
onOpen: ({ firstOpen }) => {
|
||||||
|
if (!firstOpen) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const tabSettingsSingle = document.querySelector('#tab-merge-opts-single')
|
const tabSettingsSingle = document.querySelector("#tab-merge-opts-single")
|
||||||
const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch')
|
const tabSettingsBatch = document.querySelector("#tab-merge-opts-batch")
|
||||||
linkTabContents(tabSettingsSingle)
|
linkTabContents(tabSettingsSingle)
|
||||||
linkTabContents(tabSettingsBatch)
|
linkTabContents(tabSettingsBatch)
|
||||||
|
|
||||||
console.log('Activate')
|
console.log("Activate")
|
||||||
let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion')
|
let mergeModelAField = new ModelDropdown(document.querySelector("#mergeModelA"), "stable-diffusion")
|
||||||
let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion')
|
let mergeModelBField = new ModelDropdown(document.querySelector("#mergeModelB"), "stable-diffusion")
|
||||||
updateChart()
|
updateChart()
|
||||||
|
|
||||||
// slider
|
// slider
|
||||||
const singleMergeRatioField = document.querySelector('#single-merge-ratio')
|
const singleMergeRatioField = document.querySelector("#single-merge-ratio")
|
||||||
const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider')
|
const singleMergeRatioSlider = document.querySelector("#single-merge-ratio-slider")
|
||||||
|
|
||||||
function updateSingleMergeRatio() {
|
function updateSingleMergeRatio() {
|
||||||
singleMergeRatioField.value = singleMergeRatioSlider.value / 10
|
singleMergeRatioField.value = singleMergeRatioSlider.value / 10
|
||||||
@ -359,21 +347,21 @@
|
|||||||
singleMergeRatioSlider.dispatchEvent(new Event("change"))
|
singleMergeRatioSlider.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
singleMergeRatioSlider.addEventListener('input', updateSingleMergeRatio)
|
singleMergeRatioSlider.addEventListener("input", updateSingleMergeRatio)
|
||||||
singleMergeRatioField.addEventListener('input', updateSingleMergeRatioSlider)
|
singleMergeRatioField.addEventListener("input", updateSingleMergeRatioSlider)
|
||||||
updateSingleMergeRatio()
|
updateSingleMergeRatio()
|
||||||
|
|
||||||
document.querySelector('.merge-config').addEventListener('change', updateChart)
|
document.querySelector(".merge-config").addEventListener("change", updateChart)
|
||||||
|
|
||||||
document.querySelector('#merge-button').addEventListener('click', async function(e) {
|
document.querySelector("#merge-button").addEventListener("click", async function(e) {
|
||||||
// Build request template
|
// Build request template
|
||||||
let model0 = mergeModelAField.value
|
let model0 = mergeModelAField.value
|
||||||
let model1 = mergeModelBField.value
|
let model1 = mergeModelBField.value
|
||||||
let request = { model0: model0, model1: model1 }
|
let request = { model0: model0, model1: model1 }
|
||||||
request['use_fp16'] = document.querySelector('#merge-fp').value == 'fp16'
|
request["use_fp16"] = document.querySelector("#merge-fp").value == "fp16"
|
||||||
let iterations = document.querySelector('#merge-count').value>>0
|
let iterations = document.querySelector("#merge-count").value >> 0
|
||||||
let start = parseFloat( document.querySelector('#merge-start').value )
|
let start = parseFloat(document.querySelector("#merge-start").value)
|
||||||
let step = parseFloat( document.querySelector('#merge-step').value )
|
let step = parseFloat(document.querySelector("#merge-step").value)
|
||||||
|
|
||||||
if (isTabActive(tabSettingsSingle)) {
|
if (isTabActive(tabSettingsSingle)) {
|
||||||
start = parseFloat(singleMergeRatioField.value)
|
start = parseFloat(singleMergeRatioField.value)
|
||||||
@ -385,74 +373,82 @@
|
|||||||
addLogMessage(`step = ${step}%`)
|
addLogMessage(`step = ${step}%`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start + (iterations-1) * step >= 100) {
|
if (start + (iterations - 1) * step >= 100) {
|
||||||
addLogMessage('<i>Aborting: maximum ratio is ≥ 100%</i>')
|
addLogMessage("<i>Aborting: maximum ratio is ≥ 100%</i>")
|
||||||
addLogMessage('Reduce the number of variations or the step size')
|
addLogMessage("Reduce the number of variations or the step size")
|
||||||
addLogSeparator()
|
addLogSeparator()
|
||||||
document.querySelector('#merge-count').focus()
|
document.querySelector("#merge-count").focus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.querySelector('#merge-filename').value == "") {
|
if (document.querySelector("#merge-filename").value == "") {
|
||||||
addLogMessage('<i>Aborting: No output file name specified</i>')
|
addLogMessage("<i>Aborting: No output file name specified</i>")
|
||||||
addLogSeparator()
|
addLogSeparator()
|
||||||
document.querySelector('#merge-filename').focus()
|
document.querySelector("#merge-filename").focus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable merge button
|
// Disable merge button
|
||||||
e.target.disabled=true
|
e.target.disabled = true
|
||||||
e.target.classList.add('disabled')
|
e.target.classList.add("disabled")
|
||||||
let cursor = $("body").css("cursor");
|
let cursor = $("body").css("cursor")
|
||||||
let label = document.querySelector('#merge-button').innerHTML
|
let label = document.querySelector("#merge-button").innerHTML
|
||||||
$("body").css("cursor", "progress");
|
$("body").css("cursor", "progress")
|
||||||
document.querySelector('#merge-button').innerHTML = 'Merging models ...'
|
document.querySelector("#merge-button").innerHTML = "Merging models ..."
|
||||||
|
|
||||||
addLogMessage("Merging models")
|
addLogMessage("Merging models")
|
||||||
addLogMessage("Model A: "+model0)
|
addLogMessage("Model A: " + model0)
|
||||||
addLogMessage("Model B: "+model1)
|
addLogMessage("Model B: " + model1)
|
||||||
|
|
||||||
// Batch main loop
|
// Batch main loop
|
||||||
for (let i=0; i<iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
let alpha = ( start + i * step ) / 100
|
let alpha = (start + i * step) / 100
|
||||||
switch (document.querySelector('#merge-interpolation').value) {
|
|
||||||
case 'SmoothStep':
|
if (isTabActive(tabSettingsBatch)) {
|
||||||
|
switch (document.querySelector("#merge-interpolation").value) {
|
||||||
|
case "SmoothStep":
|
||||||
alpha = smoothstep(alpha)
|
alpha = smoothstep(alpha)
|
||||||
break
|
break
|
||||||
case 'SmootherStep':
|
case "SmootherStep":
|
||||||
alpha = smootherstep(alpha)
|
alpha = smootherstep(alpha)
|
||||||
break
|
break
|
||||||
case 'SmoothestStep':
|
case "SmoothestStep":
|
||||||
alpha = smootheststep(alpha)
|
alpha = smootheststep(alpha)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
addLogMessage(`merging batch job ${i+1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
|
}
|
||||||
|
addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
|
||||||
|
|
||||||
request['out_path'] = document.querySelector('#merge-filename').value
|
request["out_path"] = document.querySelector("#merge-filename").value
|
||||||
request['out_path'] += '-' + alpha.toFixed(5) + '.' + document.querySelector('#merge-format').value
|
request["out_path"] += "-" + alpha.toFixed(5) + "." + document.querySelector("#merge-format").value
|
||||||
addLogMessage(` filename: ${request['out_path']}`)
|
addLogMessage(` filename: ${request["out_path"]}`)
|
||||||
|
|
||||||
request['ratio'] = alpha
|
// sdkit documentation: "ratio - the ratio of the second model. 1 means only the second model will be used."
|
||||||
let res = await fetch('/model/merge', {
|
request["ratio"] = 1-alpha
|
||||||
method: 'POST',
|
let res = await fetch("/model/merge", {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
method: "POST",
|
||||||
body: JSON.stringify(request) })
|
headers: { "Content-Type": "application/json" },
|
||||||
const data = await res.json();
|
body: JSON.stringify(request),
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
addLogMessage(JSON.stringify(data))
|
addLogMessage(JSON.stringify(data))
|
||||||
}
|
}
|
||||||
addLogMessage("<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder.")
|
addLogMessage(
|
||||||
|
"<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder."
|
||||||
|
)
|
||||||
addLogSeparator()
|
addLogSeparator()
|
||||||
// Re-enable merge button
|
// Re-enable merge button
|
||||||
$("body").css("cursor", cursor);
|
$("body").css("cursor", cursor)
|
||||||
document.querySelector('#merge-button').innerHTML = label
|
document.querySelector("#merge-button").innerHTML = label
|
||||||
e.target.disabled=false
|
e.target.disabled = false
|
||||||
e.target.classList.remove('disabled')
|
e.target.classList.remove("disabled")
|
||||||
|
|
||||||
// Update model list
|
// Update model list
|
||||||
stableDiffusionModelField.innerHTML = ''
|
stableDiffusionModelField.innerHTML = ""
|
||||||
vaeModelField.innerHTML = ''
|
vaeModelField.innerHTML = ""
|
||||||
hypernetworkModelField.innerHTML = ''
|
hypernetworkModelField.innerHTML = ""
|
||||||
await getModels()
|
await getModels()
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
})()
|
})()
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
(function () {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
var styleSheet = document.createElement("style");
|
var styleSheet = document.createElement("style")
|
||||||
styleSheet.textContent = `
|
styleSheet.textContent = `
|
||||||
.modifier-card-tiny.modifier-toggle-inactive {
|
.modifier-card-tiny.modifier-toggle-inactive {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 2px dashed red;
|
border: 2px dashed red;
|
||||||
opacity:0.2;
|
opacity:0.2;
|
||||||
}
|
}
|
||||||
`;
|
`
|
||||||
document.head.appendChild(styleSheet);
|
document.head.appendChild(styleSheet)
|
||||||
|
|
||||||
// observe for changes in tag list
|
// observe for changes in tag list
|
||||||
var observer = new MutationObserver(function (mutations) {
|
var observer = new MutationObserver(function(mutations) {
|
||||||
// mutations.forEach(function (mutation) {
|
// mutations.forEach(function (mutation) {
|
||||||
if (editorModifierTagsList.childNodes.length > 0) {
|
if (editorModifierTagsList.childNodes.length > 0) {
|
||||||
ModifierToggle()
|
ModifierToggle()
|
||||||
@ -21,32 +21,32 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(editorModifierTagsList, {
|
observer.observe(editorModifierTagsList, {
|
||||||
childList: true
|
childList: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function ModifierToggle() {
|
function ModifierToggle() {
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
i.oncontextmenu = (e) => {
|
i.oncontextmenu = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (i.parentElement.classList.contains('modifier-toggle-inactive')) {
|
if (i.parentElement.classList.contains("modifier-toggle-inactive")) {
|
||||||
i.parentElement.classList.remove('modifier-toggle-inactive')
|
i.parentElement.classList.remove("modifier-toggle-inactive")
|
||||||
}
|
} else {
|
||||||
else
|
i.parentElement.classList.add("modifier-toggle-inactive")
|
||||||
{
|
|
||||||
i.parentElement.classList.add('modifier-toggle-inactive')
|
|
||||||
}
|
}
|
||||||
// refresh activeTags
|
// refresh activeTags
|
||||||
let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].dataset.fullName
|
let modifierName = i.parentElement
|
||||||
activeTags = activeTags.map(obj => {
|
.getElementsByClassName("modifier-card-label")[0]
|
||||||
|
.getElementsByTagName("p")[0].dataset.fullName
|
||||||
|
activeTags = activeTags.map((obj) => {
|
||||||
if (trimModifiers(obj.name) === trimModifiers(modifierName)) {
|
if (trimModifiers(obj.name) === trimModifiers(modifierName)) {
|
||||||
return {...obj, inactive: (obj.element.classList.contains('modifier-toggle-inactive'))};
|
return { ...obj, inactive: obj.element.classList.contains("modifier-toggle-inactive") }
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj
|
||||||
});
|
})
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,64 +1,53 @@
|
|||||||
(function() {
|
;(function() {
|
||||||
// Register selftests when loaded by jasmine.
|
// Register selftests when loaded by jasmine.
|
||||||
if (typeof PLUGINS?.SELFTEST === 'object') {
|
if (typeof PLUGINS?.SELFTEST === "object") {
|
||||||
PLUGINS.SELFTEST["release-notes"] = function() {
|
PLUGINS.SELFTEST["release-notes"] = function() {
|
||||||
it('should be able to fetch CHANGES.md', async function() {
|
it("should be able to fetch CHANGES.md", async function() {
|
||||||
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/main/CHANGES.md`)
|
let releaseNotes = await fetch(
|
||||||
|
`https://raw.githubusercontent.com/easydiffusion/easydiffusion/main/CHANGES.md`
|
||||||
|
)
|
||||||
expect(releaseNotes.status).toBe(200)
|
expect(releaseNotes.status).toBe(200)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', `
|
createTab({
|
||||||
<span id="tab-news" class="tab">
|
id: "news",
|
||||||
<span><i class="fa fa-bolt icon"></i> What's new?</span>
|
icon: "fa-bolt",
|
||||||
</span>
|
label: "What's new",
|
||||||
`)
|
css: `
|
||||||
|
|
||||||
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
|
|
||||||
<div id="tab-content-news" class="tab-content">
|
|
||||||
<div id="news" class="tab-content-inner">
|
|
||||||
Loading..
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
|
|
||||||
const tabNews = document.querySelector('#tab-news')
|
|
||||||
if (tabNews) {
|
|
||||||
linkTabContents(tabNews)
|
|
||||||
}
|
|
||||||
const news = document.querySelector('#news')
|
|
||||||
if (!news) {
|
|
||||||
// news tab not found, dont exec plugin code.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('body').insertAdjacentHTML('beforeend', `
|
|
||||||
<style>
|
|
||||||
#tab-content-news .tab-content-inner {
|
#tab-content-news .tab-content-inner {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 10pt;
|
padding: 10pt;
|
||||||
}
|
}
|
||||||
</style>
|
`,
|
||||||
`)
|
onOpen: async ({ firstOpen }) => {
|
||||||
|
if (firstOpen) {
|
||||||
|
const loadMarkedScriptPromise = loadScript("/media/js/marked.min.js")
|
||||||
|
|
||||||
loadScript('/media/js/marked.min.js').then(async function() {
|
let appConfig = await fetch("/get/app_config")
|
||||||
let appConfig = await fetch('/get/app_config')
|
|
||||||
if (!appConfig.ok) {
|
if (!appConfig.ok) {
|
||||||
console.error('[release-notes] Failed to get app_config.')
|
console.error("[release-notes] Failed to get app_config.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
appConfig = await appConfig.json()
|
appConfig = await appConfig.json()
|
||||||
|
|
||||||
const updateBranch = appConfig.update_branch || 'main'
|
const updateBranch = appConfig.update_branch || "main"
|
||||||
|
|
||||||
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`)
|
let releaseNotes = await fetch(
|
||||||
|
`https://raw.githubusercontent.com/easydiffusion/easydiffusion/${updateBranch}/CHANGES.md`
|
||||||
|
)
|
||||||
if (!releaseNotes.ok) {
|
if (!releaseNotes.ok) {
|
||||||
console.error('[release-notes] Failed to get CHANGES.md.')
|
console.error("[release-notes] Failed to get CHANGES.md.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
releaseNotes = await releaseNotes.text()
|
releaseNotes = await releaseNotes.text()
|
||||||
news.innerHTML = marked.parse(releaseNotes)
|
|
||||||
|
await loadMarkedScriptPromise
|
||||||
|
|
||||||
|
return marked.parse(releaseNotes)
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})()
|
})()
|
@ -1,6 +1,7 @@
|
|||||||
/* SD-UI Selftest Plugin.js
|
/* SD-UI Selftest Plugin.js
|
||||||
*/
|
*/
|
||||||
(function() { "use strict"
|
;(function() {
|
||||||
|
"use strict"
|
||||||
const ID_PREFIX = "selftest-plugin"
|
const ID_PREFIX = "selftest-plugin"
|
||||||
|
|
||||||
const links = document.getElementById("community-links")
|
const links = document.getElementById("community-links")
|
||||||
@ -10,16 +11,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add link to Jasmine SpecRunner
|
// Add link to Jasmine SpecRunner
|
||||||
const pluginLink = document.createElement('li')
|
const pluginLink = document.createElement("li")
|
||||||
const options = {
|
const options = {
|
||||||
'stopSpecOnExpectationFailure': "true",
|
stopSpecOnExpectationFailure: "true",
|
||||||
'stopOnSpecFailure': 'false',
|
stopOnSpecFailure: "false",
|
||||||
'random': 'false',
|
random: "false",
|
||||||
'hideDisabled': 'false'
|
hideDisabled: "false",
|
||||||
}
|
}
|
||||||
const optStr = Object.entries(options).map(([key, val]) => `${key}=${val}`).join('&')
|
const optStr = Object.entries(options)
|
||||||
|
.map(([key, val]) => `${key}=${val}`)
|
||||||
|
.join("&")
|
||||||
pluginLink.innerHTML = `<a id="${ID_PREFIX}-starttest" href="${location.protocol}/plugins/core/SpecRunner.html?${optStr}" target="_blank"><i class="fa-solid fa-vial-circle-check"></i> Start SelfTest</a>`
|
pluginLink.innerHTML = `<a id="${ID_PREFIX}-starttest" href="${location.protocol}/plugins/core/SpecRunner.html?${optStr}" target="_blank"><i class="fa-solid fa-vial-circle-check"></i> Start SelfTest</a>`
|
||||||
links.appendChild(pluginLink)
|
links.appendChild(pluginLink)
|
||||||
|
|
||||||
console.log('%s loaded!', ID_PREFIX)
|
console.log("%s loaded!", ID_PREFIX)
|
||||||
})()
|
})()
|
326
ui/plugins/ui/tiled-image-download.plugin.js
Normal file
326
ui/plugins/ui/tiled-image-download.plugin.js
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
;(function(){
|
||||||
|
"use strict";
|
||||||
|
const PAPERSIZE = [
|
||||||
|
{id: "a3p", width: 297, height: 420, unit: "mm"},
|
||||||
|
{id: "a3l", width: 420, height: 297, unit: "mm"},
|
||||||
|
{id: "a4p", width: 210, height: 297, unit: "mm"},
|
||||||
|
{id: "a4l", width: 297, height: 210, unit: "mm"},
|
||||||
|
{id: "ll", width: 279, height: 216, unit: "mm"},
|
||||||
|
{id: "lp", width: 216, height: 279, unit: "mm"},
|
||||||
|
{id: "hd", width: 1920, height: 1080, unit: "pixels"},
|
||||||
|
{id: "4k", width: 3840, height: 2160, unit: "pixels"},
|
||||||
|
]
|
||||||
|
|
||||||
|
// ---- Register plugin
|
||||||
|
PLUGINS['IMAGE_INFO_BUTTONS'].push({
|
||||||
|
html: '<i class="fa-solid fa-table-cells-large"></i> Download tiled image',
|
||||||
|
on_click: onDownloadTiledImage,
|
||||||
|
filter: (req, img) => req.tiling != "none",
|
||||||
|
})
|
||||||
|
|
||||||
|
var thisImage
|
||||||
|
|
||||||
|
function onDownloadTiledImage(req, img) {
|
||||||
|
document.getElementById("download-tiled-image-dialog").showModal()
|
||||||
|
thisImage = new Image()
|
||||||
|
thisImage.src = img.src
|
||||||
|
thisImage.dataset["prompt"] = img.dataset["prompt"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Add HTML
|
||||||
|
document.getElementById('container').lastElementChild.insertAdjacentHTML("afterend",
|
||||||
|
`<dialog id="download-tiled-image-dialog">
|
||||||
|
<h1>Download tiled image</h1>
|
||||||
|
<div class="download-tiled-image dtim-container">
|
||||||
|
<div class="download-tiled-image-top">
|
||||||
|
<div class="tab-container">
|
||||||
|
<span id="tab-image-tiles" class="tab active">
|
||||||
|
<span>Number of tiles</small></span>
|
||||||
|
</span>
|
||||||
|
<span id="tab-image-size" class="tab">
|
||||||
|
<span>Image dimensions</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div id="tab-content-image-tiles" class="tab-content active">
|
||||||
|
<div class="tab-content-inner">
|
||||||
|
<label for="dtim1-width">Width:</label> <input id="dtim1-width" min="1" max="99" type="number" value="2">
|
||||||
|
<label for="dtim1-height">Height:</label> <input id="dtim1-height" min="1" max="99" type="number" value="2">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="tab-content-image-size" class="tab-content">
|
||||||
|
<div class="tab-content-inner">
|
||||||
|
<div class="method-2-options">
|
||||||
|
<label for="dtim2-width">Width:</label> <input id="dtim2-width" size="3" value="1920">
|
||||||
|
<label for="dtim2-height">Height:</label> <input id="dtim2-height" size="3" value="1080">
|
||||||
|
<select id="dtim2-unit">
|
||||||
|
<option>pixels</option>
|
||||||
|
<option>mm</option>
|
||||||
|
<option>inches</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="method-2-dpi">
|
||||||
|
<label for="dtim2-dpi">DPI:</label> <input id="dtim2-dpi" size="3" value="72">
|
||||||
|
</div>
|
||||||
|
<div class="method-2-paper">
|
||||||
|
<i>Some standard sizes:</i><br>
|
||||||
|
<button id="dtim2-a3p">A3 portrait</button><button id="dtim2-a3l">A3 landscape</button><br>
|
||||||
|
<button id="dtim2-a4p">A4 portrait</button><button id="dtim2-a4l">A4 landscape</button><br>
|
||||||
|
<button id="dtim2-lp">Letter portrait</button><button id="dtim2-ll">Letter landscape</button><br>
|
||||||
|
<button id="dtim2-hd">Full HD</button><button id="dtim2-4k">4K</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="download-tiled-image-placement">
|
||||||
|
<div class="tab-container">
|
||||||
|
<span id="tab-image-placement" class="tab active">
|
||||||
|
<span>Tile placement</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div id="tab-content-image-placement" class="tab-content active">
|
||||||
|
<div class="tab-content-inner">
|
||||||
|
<img id="dtim-1tl" class="active" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlUlEQVQ4y7VUMU7DQBCckpYCJEpS0ByhcecuUZQUtvIHGku0vICSDtHkA9eltCylOEBKFInCRworXToK3kDJ7jpn2SYmgGESeWyPRuudvTugHTyC72momKDGMMJDLIhmgK+nWmuPXxtlxkhjExszRKqU6uRuTW7TYTwh6HTpR25+JLcngBJ5jL5wIecqu9nFbid3t27N7vhrtypqV2SfP4zc5pfu/Msb3P6U4fru1eXpVg7tcmnDZ1gb0s1ceAEcSPI3uM2B9xLf7Z3YLlfJ/WCppF1QbbqxeW0brlztjXzprBhJrW8nu4HWGlt/xz1qcrervfmT2ma3WxpTjfK5ZUioNg+VsUL+tiXuI8YJLrd8KHyENyaqPWC8QGiwwlJ4LtyvNtb9vFKrqZXXeebkrEiN3ZUNXHJnO3aJkxt2aH2gDRNTLdyzJvee1CZXUTSJrhA55itlfszUdqDrxCQmGIEu9KfFFCRJYnpIgyB4JJlPWM6cY6MjN+UW5MjdM7FKavF/pFbfRD9zv8rjBa6FT5EJn0HoA8lOiD4+8B3mAAAAAElFTkSuQmCC" />
|
||||||
|
<img id="dtim-1tr" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABoUlEQVQ4y61UsU7CUBQ9o6uGmJgwNSQOLS7dOndrGfwEDWnC4tQNB8duxoUfYGN86fZgIWy+iVUnwwYf4Oi9lxa0tFSpB9IDXM6775773gUaYWjbtoO+IrI1VsIKnjt2CYCllJqir7Wt7SlWWmn+m+t53sQbU5hAamtJrxRr/mppUnuZOgszOlgJK7gCUS93YVbzKqx2q9U2q71Mbf1Qbxc/qqadu7y509W7nX8Pt/K6JwwKO+HCGLRNKPy4oA9mkYUnwGeSJM9IBDknOJN+PNV2rEy9XyXLvaGcktuY0FBux9AP5rVYd96SofCsWFje0NJwUd2rUse/UTfLPTspd83iFFZZYY4xbKoRsKlmaypjjoaICA+4ZYrO8SJ8mfEV0PF9P0Tb94U3wj7eheaHJ3VZLKxbcs4P6uartz/nMYlKbFnzYtWe5ze0wtSjDd1Ph7iReheucS0aRYM78pwoiiDPUc6Dpg19S9N0ipYOgiClw5TqgN6I6aGD7S2RkcsbppnKbLPnPHt7VZOpxvN/cq1cHf9BbeFeqIsL4Wt8CN/gC1XPfwv6U6jJAAAAAElFTkSuQmCC" /><br>
|
||||||
|
<img id="dtim-1bl" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjElEQVQ4y7WUsU7DMBCG/5GVoUiMMLC4ZcmWrVXVDq7yDiyRuvIEjGyIpS/graMVKYMBqVUkhoYOUTc2Bp6BEd+5qdI2SasGLpE/KZc/d76LD/i6JrvFPfMKGfMGDOCTGUMwA/SYA8yL7iEM8w2yzH1AHderh9D1alGuXmmtBaVGchFgQe8J69bOHZnIyCHsYu8AUkZRZLpYSClf0dAm4zCchGOEOWkNW7ggAO0+2QcY/SUS5ozZ+6uqNVHHR9Q8q1Bnm9+B1B17HdGxbDe1zn7sA1V7DskucbfmObOFb0LThrZTsjlGzHcw0iXcU6woQxFv9i3rah5UdSxvSa/+EMn6hp6iroy9+s/YL2mSjlxRE1fUkX2wZNqiPjrDTwmfDnbsjNeH0q9Y9ZRN2diJjeliJ+ksj+2v3W5j3d2N+R5b4T/fcjuvqppMvqfsdbqaxF7VCc3Vho9eUd0pZi6q1cpThemwdYB9NVVK2cy1NsLYmaqNNmZgZ6sQa7VPmWuavQG9ZkkjV+u42QH8BWe+iD71TSARAAAAAElFTkSuQmCC" />
|
||||||
|
<img id="dtim-1br" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABkklEQVQ4y7VULU/DUBQ9EgtZSEimliWIt2Lmnq5rJ/gJkKXJDGquCGQdwewPzE2+PPeGWeaomgVF6tgPmOTe2zWkox8LC6dLT/ZuTs9957UXPQbuhTxcCF/jU/gGYBpgLH/7yIQNYuHXctniS9hhWlU+Wh03qVX1wwt1+eGKyoZXl8iYFbdmlFIj4N1au0THBUFgLTLrAvphSjcXUPk0RLNocodbpiiC3GcFT4C+7/shur4vvBX28SG0+o/UTHNqrZnXe3uVrW3oKtScuVeUvZYTK3lvyq21pBYRHihzxjlehC/3fHXqgQ5SArapAI9C63w1XR3GUuw75neuN6otV++78SOyza9Dv/lAj1Yf5T061XsQrjnUdSpMoYZ5qLSQvgG7JEmekQgOOWk9sV2l6kxqT4V3Nw1zb7IMyVsvBIcb6+w3lpfn1V+Jw1Awr5tMOq/XqzVdf1Mr9tZ7701JzZ+ia1ab352z6mc6cGO52hiapWPnlFM0U51ximbqUGu90KSOabTyyCWi5UyYO5/n6pPwDYr8fwvXgN7jAAAAAElFTkSuQmCC" /> <br>
|
||||||
|
<img id="dtim-1center" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUBAQGPjY3Rz89DQ0O0sbHz8vJZJoFwbm6+vLxBHV10MqY6ODifnZ1XV1cdHR3c2trZIbFLAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCElEQVQ4y7VVPWvjQBCVnZXt5JRT1LsQwwnS7YHrgLg6hTBsva4MSeUurX5CqqtTuXCVn6CAf0DgimvvJ4iFredmd2VZcqTYIWSap2V4+/HezMgDSAdgIw0cFhVeMQAPIAuUWfF44jAdOix8k8YsmCNXyEuGynzkQ4u68BE8LbKJsJGFDkvfYe6Lufe5ePi7unywUePY4Suhl4iMCaGlFOWQQGiRhw5Tc3b9MIiZe1Deehhkg3nXu5MirGW5NmnmkNjX9EFs1VKN7djgNg+bovae3a15cpg+ZIfquGP97J2h0viJ5cT6SoZaX+lspYyoX2doFBXjlyiaRtHV+XL5++5u+TiITExXQRR5XGeBQIkoyouNjadQSqm1Tn2pmg8bbe5NelFb0nQMKL25XxO7tsSoxmu/q80XbdXK/eZrs/2iR/OR4a53m1eO8ayPfWDJqHU2VuwywNbVnt6czflbttucVGOkmUbdUI2WSG0g0ZvepuNbiu22IM1NPAZ2uV2x7csnDf3FywFdhQPmttYUT6kkubv5D4+bWlOoFNWa5BRJHiJNDzTDQzUd21fqoaHdPaaO1/kp/d1mwwkdunfsIx2K7Q6lapmDIrVihqAsG41qiZ0tbq7ZFhw6jKsWtHPtz+z128zGT4c3z2cVXs5ujjn2773k9472j5vtf/pkYp2TKe/7E00A/gO7G7pwJRGqtAAAAABJRU5ErkJggg==" />
|
||||||
|
<img id="dtim-4center" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUBAQGbmppqLZZ8fHzRz8+LPMZ0MqXs6upTUVE+G1q5ublubm6iRedQInH9/Pze3NzAiv2WAAAACXBIWXMAAAsTAAALEwEAmpwYAAABuUlEQVQ4y6VVvU7CUBSmCEVQao0vQDrcWWcWhiYkDsICc3OS3pmhSbfiI7C6MfgAJo4sPoCDcSdhMGFDBhI24zn3tBjh2DTy3eEj9+P0/Pa05BtMrZQbzG7KJaZFIyLqrS82hqfOnszXnUquLFv3dvLOeiP57uT69teFfHcP5CKJ+QdyN0YsF5Ul8WpUI06SjmM4Ks0Mrk+Zn86Y70+YS8fha0VY2K8GL1XmwDHXnxj54y1GXgbwwNOBrT2lPNWiyFHIEitrhYdkDaB1ay/vsjY6yiiCJJMVykq0pkt+OPoQH44gGX8IskqtFYjWgODQQAwt8y1aAygPdN8GCkHt5LSoaTH75ylnRT0ON5cEtz4nvL81Dc8nlrm+wmEax9t4YQ/0MNRhn1iHYcuJt+PxOMoib5p8uGpySzAfUxbQKqcsSov9VmlRqSPyOHBR6Q9Sx9haKci3lvsNPGtyv8Hogc3c+nkFl3iwasNBOBz0q8yBk8RJvMRXsE3HrT8YTDK2Zs942kc29I6npbZi5ilZjZg/DpaHL++WqNDiypfzt+LfO3VTaO39c6dmsp9nPZJ9Z18i19r8+hJ9A3EAErhB3eXkAAAAAElFTkSuQmCC" /> <br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dtim-ok">
|
||||||
|
<button class="primaryButton" id="dti-ok">Download</button>
|
||||||
|
</div>
|
||||||
|
<div class="dtim-newtab">
|
||||||
|
<button class="primaryButton" id="dti-newtab">Open in new tab</button>
|
||||||
|
</div>
|
||||||
|
<div class="dtim-cancel">
|
||||||
|
<button class="primaryButton" id="dti-cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>`)
|
||||||
|
|
||||||
|
let downloadTiledImageDialog = document.getElementById("download-tiled-image-dialog")
|
||||||
|
let dtim1_width = document.getElementById("dtim1-width")
|
||||||
|
let dtim1_height = document.getElementById("dtim1-height")
|
||||||
|
let dtim2_width = document.getElementById("dtim2-width")
|
||||||
|
let dtim2_height = document.getElementById("dtim2-height")
|
||||||
|
let dtim2_unit = document.getElementById("dtim2-unit")
|
||||||
|
let dtim2_dpi = document.getElementById("dtim2-dpi")
|
||||||
|
let tabTiledTilesOptions = document.getElementById("tab-image-tiles")
|
||||||
|
let tabTiledSizeOptions = document.getElementById("tab-image-size")
|
||||||
|
|
||||||
|
linkTabContents(tabTiledTilesOptions)
|
||||||
|
linkTabContents(tabTiledSizeOptions)
|
||||||
|
|
||||||
|
prettifyInputs(downloadTiledImageDialog)
|
||||||
|
|
||||||
|
// ---- Predefined image dimensions
|
||||||
|
PAPERSIZE.forEach( function(p) {
|
||||||
|
document.getElementById("dtim2-" + p.id).addEventListener("click", (e) => {
|
||||||
|
dtim2_unit.value = p.unit
|
||||||
|
dtim2_width.value = p.width
|
||||||
|
dtim2_height.value = p.height
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ---- Close popup
|
||||||
|
document.getElementById("dti-cancel").addEventListener("click", (e) => downloadTiledImageDialog.close())
|
||||||
|
downloadTiledImageDialog.addEventListener('click', function (event) {
|
||||||
|
var rect = downloadTiledImageDialog.getBoundingClientRect();
|
||||||
|
var isInDialog=(rect.top <= event.clientY && event.clientY <= rect.top + rect.height
|
||||||
|
&& rect.left <= event.clientX && event.clientX <= rect.left + rect.width);
|
||||||
|
if (!isInDialog) {
|
||||||
|
downloadTiledImageDialog.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---- Stylesheet
|
||||||
|
const styleSheet = document.createElement("style")
|
||||||
|
styleSheet.textContent = `
|
||||||
|
dialog {
|
||||||
|
background: var(--background-color2);
|
||||||
|
color: var(--text-color);
|
||||||
|
border-radius: 7px;
|
||||||
|
border: 1px solid var(--background-color3);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-2-dpi {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-2-paper button {
|
||||||
|
width: 10em;
|
||||||
|
padding: 4px;
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-tiled-image .tab-content {
|
||||||
|
background: var(--background-color1);
|
||||||
|
border-radius: 3pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dtim-container { display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
gap: 1em 0px;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-template-areas:
|
||||||
|
"dtim-tab dtim-tab dtim-plc"
|
||||||
|
"dtim-ok dtim-newtab dtim-cancel";
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-tiled-image-top {
|
||||||
|
justify-self: center;
|
||||||
|
grid-area: dtim-tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-tiled-image-placement {
|
||||||
|
justify-self: center;
|
||||||
|
grid-area: dtim-plc;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dtim-ok {
|
||||||
|
justify-self: center;
|
||||||
|
align-self: start;
|
||||||
|
grid-area: dtim-ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dtim-newtab {
|
||||||
|
justify-self: center;
|
||||||
|
align-self: start;
|
||||||
|
grid-area: dtim-newtab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dtim-cancel {
|
||||||
|
justify-self: center;
|
||||||
|
align-self: start;
|
||||||
|
grid-area: dtim-cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-content-image-placement img {
|
||||||
|
margin: 4px;
|
||||||
|
opacity: 0.3;
|
||||||
|
border: solid 2px var(--background-color1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-content-image-placement img:hover {
|
||||||
|
margin: 4px;
|
||||||
|
opacity: 1;
|
||||||
|
border: solid 2px var(--accent-color);
|
||||||
|
filter: brightness(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-content-image-placement img.active {
|
||||||
|
margin: 4px;
|
||||||
|
opacity: 1;
|
||||||
|
border: solid 2px var(--background-color1);
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
document.head.appendChild(styleSheet)
|
||||||
|
|
||||||
|
// ---- Placement widget
|
||||||
|
|
||||||
|
function updatePlacementWidget(event) {
|
||||||
|
document.querySelector("#tab-content-image-placement img.active").classList.remove("active")
|
||||||
|
event.target.classList.add("active")
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll("#tab-content-image-placement img").forEach(
|
||||||
|
(i) => i.addEventListener("click", updatePlacementWidget)
|
||||||
|
)
|
||||||
|
|
||||||
|
function getPlacement() {
|
||||||
|
return document.querySelector("#tab-content-image-placement img.active").id.substr(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Make the image
|
||||||
|
function downloadTiledImage(image, width, height, offsetX=0, offsetY=0, new_tab=false) {
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
canvas.width = width
|
||||||
|
canvas.height = height
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
|
||||||
|
const w = image.width
|
||||||
|
const h = image.height
|
||||||
|
|
||||||
|
for (var x = offsetX; x < width; x += w) {
|
||||||
|
for (var y = offsetY; y < height; y += h) {
|
||||||
|
context.drawImage(image, x, y, w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (new_tab) {
|
||||||
|
var newTab = window.open("")
|
||||||
|
newTab.document.write(`<html><head><title>${width}×${height}, "${image.dataset["prompt"]}"</title></head><body><img src="${canvas.toDataURL()}"></body></html>`)
|
||||||
|
} else {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = canvas.toDataURL()
|
||||||
|
link.download = image.dataset["prompt"].replace(/[^a-zA-Z0-9]+/g, "-").substr(0,22)+crypto.randomUUID()+".png"
|
||||||
|
link.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDownloadTiledImageClick(e, newtab=false) {
|
||||||
|
var width, height, offsetX, offsetY
|
||||||
|
|
||||||
|
if (isTabActive(tabTiledTilesOptions)) {
|
||||||
|
width = thisImage.width * dtim1_width.value
|
||||||
|
height = thisImage.height * dtim1_height.value
|
||||||
|
} else {
|
||||||
|
if ( dtim2_unit.value == "pixels" ) {
|
||||||
|
width = dtim2_width.value
|
||||||
|
height= dtim2_height.value
|
||||||
|
} else if ( dtim2_unit.value == "mm" ) {
|
||||||
|
width = Math.floor( dtim2_width.value * dtim2_dpi.value / 25.4 )
|
||||||
|
height = Math.floor( dtim2_height.value * dtim2_dpi.value / 25.4 )
|
||||||
|
} else { // inch
|
||||||
|
width = Math.floor( dtim2_width.value * dtim2_dpi.value )
|
||||||
|
height = Math.floor( dtim2_height.value * dtim2_dpi.value )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var placement = getPlacement()
|
||||||
|
if (placement == "1tl") {
|
||||||
|
offsetX = 0
|
||||||
|
offsetY = 0
|
||||||
|
} else if (placement == "1tr") {
|
||||||
|
offsetX = width - thisImage.width * Math.ceil( width / thisImage.width )
|
||||||
|
offsetY = 0
|
||||||
|
} else if (placement == "1bl") {
|
||||||
|
offsetX = 0
|
||||||
|
offsetY = height - thisImage.height * Math.ceil( height / thisImage.height )
|
||||||
|
} else if (placement == "1br") {
|
||||||
|
offsetX = width - thisImage.width * Math.ceil( width / thisImage.width )
|
||||||
|
offsetY = height - thisImage.height * Math.ceil( height / thisImage.height )
|
||||||
|
} else if (placement == "4center") {
|
||||||
|
offsetX = width/2 - thisImage.width * Math.ceil( width/2 / thisImage.width )
|
||||||
|
offsetY = height/2 - thisImage.height * Math.ceil( height/2 / thisImage.height )
|
||||||
|
} else if (placement == "1center") {
|
||||||
|
offsetX = width/2 - thisImage.width/2 - thisImage.width * Math.ceil( (width/2 - thisImage.width/2) / thisImage.width )
|
||||||
|
offsetY = height/2 - thisImage.height/2 - thisImage.height * Math.ceil( (height/2 - thisImage.height/2) / thisImage.height )
|
||||||
|
}
|
||||||
|
downloadTiledImage(thisImage, width, height, offsetX, offsetY, newtab)
|
||||||
|
downloadTiledImageDialog.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("dti-ok").addEventListener("click", onDownloadTiledImageClick)
|
||||||
|
document.getElementById("dti-newtab").addEventListener("click", (e) => onDownloadTiledImageClick(e,true))
|
||||||
|
|
||||||
|
})()
|
Loading…
x
Reference in New Issue
Block a user